重新导入obi
This commit is contained in:
8
Assets/Obi/Scripts/RopeAndRod/Actors.meta
Normal file
8
Assets/Obi/Scripts/RopeAndRod/Actors.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: abc784de05f944701a4dff27d147b35f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
553
Assets/Obi/Scripts/RopeAndRod/Actors/ObiBone.cs
Normal file
553
Assets/Obi/Scripts/RopeAndRod/Actors/ObiBone.cs
Normal file
@@ -0,0 +1,553 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
[AddComponentMenu("Physics/Obi/Obi Bone", 882)]
|
||||
[ExecuteInEditMode]
|
||||
[DisallowMultipleComponent]
|
||||
public class ObiBone : ObiActor, IStretchShearConstraintsUser, IBendTwistConstraintsUser, ISkinConstraintsUser
|
||||
{
|
||||
[Serializable]
|
||||
public class BonePropertyCurve
|
||||
{
|
||||
[Min(0)]
|
||||
public float multiplier;
|
||||
public AnimationCurve curve;
|
||||
|
||||
public BonePropertyCurve(float multiplier, float curveValue)
|
||||
{
|
||||
this.multiplier = multiplier;
|
||||
this.curve = new AnimationCurve(new Keyframe(0, curveValue), new Keyframe(1, curveValue));
|
||||
}
|
||||
|
||||
public float Evaluate(float time)
|
||||
{
|
||||
return curve.Evaluate(time) * multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class IgnoredBone
|
||||
{
|
||||
public Transform bone;
|
||||
public bool ignoreChildren;
|
||||
}
|
||||
|
||||
[NonSerialized] protected ObiBoneBlueprint m_BoneBlueprint;
|
||||
|
||||
[SerializeField] protected bool m_SelfCollisions = false;
|
||||
|
||||
[SerializeField] protected BonePropertyCurve _radius = new BonePropertyCurve(0.1f,1);
|
||||
[SerializeField] protected BonePropertyCurve _mass = new BonePropertyCurve(0.1f,1);
|
||||
[SerializeField] protected BonePropertyCurve _rotationalMass = new BonePropertyCurve(0.1f,1);
|
||||
|
||||
// skin constraints:
|
||||
[SerializeField] protected bool _skinConstraintsEnabled = true;
|
||||
[SerializeField] protected BonePropertyCurve _skinCompliance = new BonePropertyCurve(0.01f, 1);
|
||||
[SerializeField] protected BonePropertyCurve _skinRadius = new BonePropertyCurve(0.1f, 1);
|
||||
|
||||
// distance constraints:
|
||||
[SerializeField] protected bool _stretchShearConstraintsEnabled = true;
|
||||
[SerializeField] protected BonePropertyCurve _stretchCompliance = new BonePropertyCurve(0, 1);
|
||||
[SerializeField] protected BonePropertyCurve _shear1Compliance = new BonePropertyCurve(0, 1);
|
||||
[SerializeField] protected BonePropertyCurve _shear2Compliance = new BonePropertyCurve(0, 1);
|
||||
|
||||
// bend constraints:
|
||||
[SerializeField] protected bool _bendTwistConstraintsEnabled = true;
|
||||
[SerializeField] protected BonePropertyCurve _torsionCompliance = new BonePropertyCurve(0, 1);
|
||||
[SerializeField] protected BonePropertyCurve _bend1Compliance = new BonePropertyCurve(0, 1);
|
||||
[SerializeField] protected BonePropertyCurve _bend2Compliance = new BonePropertyCurve(0, 1);
|
||||
[SerializeField] protected BonePropertyCurve _plasticYield = new BonePropertyCurve(0, 1);
|
||||
[SerializeField] protected BonePropertyCurve _plasticCreep = new BonePropertyCurve(0, 1);
|
||||
|
||||
[Tooltip("Filter used for collision detection.")]
|
||||
[SerializeField] private int filter = ObiUtils.MakeFilter(ObiUtils.CollideWithEverything, 1);
|
||||
|
||||
public bool fixRoot = true;
|
||||
public bool stretchBones = true;
|
||||
public List<IgnoredBone> ignored = new List<IgnoredBone>();
|
||||
|
||||
/// <summary>
|
||||
/// Collision filter value used by fluid particles.
|
||||
/// </summary>
|
||||
public int Filter
|
||||
{
|
||||
set
|
||||
{
|
||||
if (filter != value)
|
||||
{
|
||||
filter = value;
|
||||
UpdateFilter();
|
||||
}
|
||||
}
|
||||
get { return filter; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether particles in this actor colide with particles using the same phase value.
|
||||
/// </summary>
|
||||
public bool selfCollisions
|
||||
{
|
||||
get { return m_SelfCollisions; }
|
||||
set { if (value != m_SelfCollisions) { m_SelfCollisions = value; SetSelfCollisions(m_SelfCollisions); } }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Particle radius distribution over this bone hierarchy length.
|
||||
/// </summary>
|
||||
public BonePropertyCurve radius
|
||||
{
|
||||
get { return _radius; }
|
||||
set { _radius = value; UpdateRadius(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mass distribution over this bone hierarchy length.
|
||||
/// </summary>
|
||||
public BonePropertyCurve mass
|
||||
{
|
||||
get { return _mass; }
|
||||
set { _mass = value; UpdateMasses(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotational mass distribution over this bone hierarchy length.
|
||||
/// </summary>
|
||||
public BonePropertyCurve rotationalMass
|
||||
{
|
||||
get { return _rotationalMass; }
|
||||
set { _rotationalMass = value; UpdateMasses(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this actor's skin constraints are enabled.
|
||||
/// </summary>
|
||||
public bool skinConstraintsEnabled
|
||||
{
|
||||
get { return _skinConstraintsEnabled; }
|
||||
set { if (value != _skinConstraintsEnabled) { _skinConstraintsEnabled = value; SetConstraintsDirty(Oni.ConstraintType.Skin); } }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compliance of this actor's skin constraints.
|
||||
/// </summary>
|
||||
public BonePropertyCurve skinCompliance
|
||||
{
|
||||
get { return _skinCompliance; }
|
||||
set { _skinCompliance = value; SetConstraintsDirty(Oni.ConstraintType.Skin); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compliance of this actor's skin radius
|
||||
/// </summary>
|
||||
public BonePropertyCurve skinRadius
|
||||
{
|
||||
get { return _skinRadius; }
|
||||
set { _skinRadius = value; SetConstraintsDirty(Oni.ConstraintType.Skin); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this actor's stretch/shear constraints are enabled.
|
||||
/// </summary>
|
||||
public bool stretchShearConstraintsEnabled
|
||||
{
|
||||
get { return _stretchShearConstraintsEnabled; }
|
||||
set { if (value != _stretchShearConstraintsEnabled) { _stretchShearConstraintsEnabled = value; SetConstraintsDirty(Oni.ConstraintType.StretchShear); } }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compliance of this actor's stretch/shear constraints, along their length.
|
||||
/// </summary>
|
||||
public BonePropertyCurve stretchCompliance
|
||||
{
|
||||
get { return _stretchCompliance; }
|
||||
set { _stretchCompliance = value; SetConstraintsDirty(Oni.ConstraintType.StretchShear); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shearing compliance of this actor's stretch/shear constraints, along the first axis orthogonal to their length.
|
||||
/// </summary>
|
||||
public BonePropertyCurve shear1Compliance
|
||||
{
|
||||
get { return _shear1Compliance; }
|
||||
set { _shear1Compliance = value; SetConstraintsDirty(Oni.ConstraintType.StretchShear); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shearing compliance of this actor's stretch/shear constraints, along the second axis orthogonal to their length.
|
||||
/// </summary>
|
||||
public BonePropertyCurve shear2Compliance
|
||||
{
|
||||
get { return _shear2Compliance; }
|
||||
set { _shear2Compliance = value; SetConstraintsDirty(Oni.ConstraintType.StretchShear); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this actor's bend/twist constraints are enabled.
|
||||
/// </summary>
|
||||
public bool bendTwistConstraintsEnabled
|
||||
{
|
||||
get { return _bendTwistConstraintsEnabled; }
|
||||
set { if (value != _bendTwistConstraintsEnabled) { _bendTwistConstraintsEnabled = value; SetConstraintsDirty(Oni.ConstraintType.BendTwist); } }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Torsional compliance of this actor's bend/twist constraints along their length.
|
||||
/// </summary>
|
||||
public BonePropertyCurve torsionCompliance
|
||||
{
|
||||
get { return _torsionCompliance; }
|
||||
set { _torsionCompliance = value; SetConstraintsDirty(Oni.ConstraintType.BendTwist); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bending compliance of this actor's bend/twist constraints along the first axis orthogonal to their length.
|
||||
/// </summary>
|
||||
public BonePropertyCurve bend1Compliance
|
||||
{
|
||||
get { return _bend1Compliance; }
|
||||
set { _bend1Compliance = value; SetConstraintsDirty(Oni.ConstraintType.BendTwist); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bending compliance of this actor's bend/twist constraints along the second axis orthogonal to their length.
|
||||
/// </summary>
|
||||
public BonePropertyCurve bend2Compliance
|
||||
{
|
||||
get { return _bend2Compliance; }
|
||||
set { _bend2Compliance = value; SetConstraintsDirty(Oni.ConstraintType.BendTwist); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Threshold for plastic behavior.
|
||||
/// </summary>
|
||||
/// Once bending goes above this value, a percentage of the deformation (determined by <see cref="plasticCreep"/>) will be permanently absorbed into the rod's rest shape.
|
||||
public BonePropertyCurve plasticYield
|
||||
{
|
||||
get { return _plasticYield; }
|
||||
set { _plasticYield = value; SetConstraintsDirty(Oni.ConstraintType.BendTwist); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Percentage of deformation that gets absorbed into the rest shape per second, once deformation goes above the <see cref="plasticYield"/> threshold.
|
||||
/// </summary>
|
||||
public BonePropertyCurve plasticCreep
|
||||
{
|
||||
get { return _plasticCreep; }
|
||||
set { _plasticCreep = value; SetConstraintsDirty(Oni.ConstraintType.BendTwist); }
|
||||
}
|
||||
|
||||
|
||||
public override ObiActorBlueprint sourceBlueprint
|
||||
{
|
||||
get { return m_BoneBlueprint; }
|
||||
}
|
||||
|
||||
public ObiBoneBlueprint boneBlueprint
|
||||
{
|
||||
get { return m_BoneBlueprint; }
|
||||
set
|
||||
{
|
||||
if (m_BoneBlueprint != value)
|
||||
{
|
||||
RemoveFromSolver();
|
||||
ClearState();
|
||||
m_BoneBlueprint = value;
|
||||
AddToSolver();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
m_BoneBlueprint = ScriptableObject.CreateInstance<ObiBoneBlueprint>();
|
||||
UpdateBlueprint();
|
||||
base.Awake();
|
||||
}
|
||||
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
if (m_BoneBlueprint != null)
|
||||
DestroyImmediate(m_BoneBlueprint);
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
UpdateFilter();
|
||||
UpdateRadius();
|
||||
UpdateMasses();
|
||||
SetupRuntimeConstraints();
|
||||
}
|
||||
|
||||
public void UpdateBlueprint()
|
||||
{
|
||||
if (m_BoneBlueprint != null)
|
||||
{
|
||||
m_BoneBlueprint.root = transform;
|
||||
m_BoneBlueprint.ignored = ignored;
|
||||
m_BoneBlueprint.mass = mass;
|
||||
m_BoneBlueprint.rotationalMass = rotationalMass;
|
||||
m_BoneBlueprint.radius = radius;
|
||||
m_BoneBlueprint.GenerateImmediate();
|
||||
}
|
||||
}
|
||||
|
||||
public override void LoadBlueprint(ObiSolver solver)
|
||||
{
|
||||
base.LoadBlueprint(solver);
|
||||
SetupRuntimeConstraints();
|
||||
ResetToCurrentShape();
|
||||
}
|
||||
|
||||
public override void UnloadBlueprint(ObiSolver solver)
|
||||
{
|
||||
ResetParticles();
|
||||
CopyParticleDataToTransforms();
|
||||
base.UnloadBlueprint(solver);
|
||||
}
|
||||
|
||||
private void SetupRuntimeConstraints()
|
||||
{
|
||||
SetConstraintsDirty(Oni.ConstraintType.Skin);
|
||||
SetConstraintsDirty(Oni.ConstraintType.StretchShear);
|
||||
SetConstraintsDirty(Oni.ConstraintType.BendTwist);
|
||||
SetSelfCollisions(selfCollisions);
|
||||
SetSimplicesDirty();
|
||||
UpdateFilter();
|
||||
UpdateCollisionMaterials();
|
||||
}
|
||||
|
||||
private void FixRoot()
|
||||
{
|
||||
if (isLoaded)
|
||||
{
|
||||
int rootIndex = solverIndices[0];
|
||||
|
||||
var actor2Solver = actorLocalToSolverMatrix;
|
||||
var actor2SolverR = actor2Solver.rotation;
|
||||
|
||||
solver.invMasses[rootIndex] = 0;
|
||||
solver.invRotationalMasses[rootIndex] = 0;
|
||||
solver.velocities[rootIndex] = Vector4.zero;
|
||||
solver.angularVelocities[rootIndex] = Vector4.zero;
|
||||
|
||||
// take particle rest position in actor space (which is always zero), converts to solver space:
|
||||
solver.renderablePositions[rootIndex] = solver.positions[rootIndex] = actor2Solver.MultiplyPoint3x4(Vector3.zero);
|
||||
|
||||
// take particle rest orientation in actor space, and convert to solver space:
|
||||
solver.renderableOrientations[rootIndex] = solver.orientations[rootIndex] = actor2SolverR * boneBlueprint.orientations[0];
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateFilter()
|
||||
{
|
||||
for (int i = 0; i < particleCount; i++)
|
||||
{
|
||||
boneBlueprint.filters[i] = filter;
|
||||
if (isLoaded)
|
||||
solver.filters[solverIndices[i]] = filter;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateRadius()
|
||||
{
|
||||
for (int i = 0; i < particleCount; ++i)
|
||||
{
|
||||
var normalizedCoord = boneBlueprint.normalizedLengths[i];
|
||||
var radii = Vector3.one * radius.Evaluate(normalizedCoord);
|
||||
boneBlueprint.principalRadii[i] = radii;
|
||||
|
||||
if (isLoaded)
|
||||
solver.principalRadii[solverIndices[i]] = radii;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateMasses()
|
||||
{
|
||||
for (int i = 0; i < particleCount; ++i)
|
||||
{
|
||||
var normalizedCoord = boneBlueprint.normalizedLengths[i];
|
||||
var invMass = ObiUtils.MassToInvMass(mass.Evaluate(normalizedCoord));
|
||||
var invRotMass = ObiUtils.MassToInvMass(rotationalMass.Evaluate(normalizedCoord));
|
||||
|
||||
boneBlueprint.invMasses[i] = invMass;
|
||||
boneBlueprint.invRotationalMasses[i] = invRotMass;
|
||||
|
||||
if (isLoaded)
|
||||
{
|
||||
solver.invMasses[solverIndices[i]] = invMass;
|
||||
solver.invRotationalMasses[solverIndices[i]] = invRotMass;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 GetSkinRadiiBackstop(ObiSkinConstraintsBatch batch, int constraintIndex)
|
||||
{
|
||||
float normalizedCoord = boneBlueprint.normalizedLengths[batch.particleIndices[constraintIndex]];
|
||||
return new Vector3(skinRadius.Evaluate(normalizedCoord),0,0);
|
||||
}
|
||||
|
||||
public float GetSkinCompliance(ObiSkinConstraintsBatch batch, int constraintIndex)
|
||||
{
|
||||
float normalizedCoord = boneBlueprint.normalizedLengths[batch.particleIndices[constraintIndex]];
|
||||
return skinCompliance.Evaluate(normalizedCoord);
|
||||
}
|
||||
|
||||
|
||||
public Vector3 GetBendTwistCompliance(ObiBendTwistConstraintsBatch batch, int constraintIndex)
|
||||
{
|
||||
float normalizedCoord = boneBlueprint.normalizedLengths[batch.particleIndices[constraintIndex * 2]];
|
||||
return new Vector3(bend1Compliance.Evaluate(normalizedCoord),
|
||||
bend2Compliance.Evaluate(normalizedCoord),
|
||||
torsionCompliance.Evaluate(normalizedCoord));
|
||||
}
|
||||
|
||||
public Vector2 GetBendTwistPlasticity(ObiBendTwistConstraintsBatch batch, int constraintIndex)
|
||||
{
|
||||
float normalizedCoord = boneBlueprint.normalizedLengths[batch.particleIndices[constraintIndex * 2]];
|
||||
return new Vector2(plasticYield.Evaluate(normalizedCoord),
|
||||
plasticCreep.Evaluate(normalizedCoord));
|
||||
}
|
||||
|
||||
public Vector3 GetStretchShearCompliance(ObiStretchShearConstraintsBatch batch, int constraintIndex)
|
||||
{
|
||||
float normalizedCoord = boneBlueprint.normalizedLengths[batch.particleIndices[constraintIndex * 2]];
|
||||
return new Vector3(shear1Compliance.Evaluate(normalizedCoord),
|
||||
shear2Compliance.Evaluate(normalizedCoord),
|
||||
stretchCompliance.Evaluate(normalizedCoord));
|
||||
}
|
||||
|
||||
public override void BeginStep(float stepTime)
|
||||
{
|
||||
base.BeginStep(stepTime);
|
||||
|
||||
if (fixRoot)
|
||||
FixRoot();
|
||||
|
||||
UpdateRestShape();
|
||||
}
|
||||
|
||||
public override void PrepareFrame()
|
||||
{
|
||||
ResetReferenceOrientations();
|
||||
base.PrepareFrame();
|
||||
}
|
||||
|
||||
public override void Interpolate()
|
||||
{
|
||||
if (Application.isPlaying && isActiveAndEnabled)
|
||||
CopyParticleDataToTransforms();
|
||||
|
||||
base.Interpolate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets particle orientations/positions to match the current pose of the bone hierarchy, and sets all their velocities to zero.
|
||||
/// </summary>
|
||||
public void ResetToCurrentShape()
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
|
||||
var world2Solver = solver.transform.worldToLocalMatrix;
|
||||
|
||||
for (int i = 0; i < particleCount; ++i)
|
||||
{
|
||||
var trfm = boneBlueprint.transforms[i];
|
||||
int solverIndex = solverIndices[i];
|
||||
|
||||
solver.velocities[solverIndex] = Vector4.zero;
|
||||
solver.angularVelocities[solverIndex] = Vector4.zero;
|
||||
|
||||
solver.renderablePositions[solverIndex] = solver.positions[solverIndex] = world2Solver.MultiplyPoint3x4(trfm.position);
|
||||
|
||||
var boneDeltaAWS = trfm.rotation * Quaternion.Inverse(boneBlueprint.restOrientations[i]);
|
||||
solver.renderableOrientations[solverIndex] = solver.orientations[solverIndex] = world2Solver.rotation * boneDeltaAWS * boneBlueprint.root2WorldR * boneBlueprint.orientations[i];
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetReferenceOrientations()
|
||||
{
|
||||
if (boneBlueprint != null)
|
||||
for (int i = 1; i < boneBlueprint.restTransformOrientations.Count; ++i)
|
||||
boneBlueprint.transforms[i].localRotation = boneBlueprint.restTransformOrientations[i];
|
||||
}
|
||||
|
||||
private void UpdateRestShape()
|
||||
{
|
||||
// use current bone transforms as rest state for the simulation:
|
||||
var bc = GetConstraintsByType(Oni.ConstraintType.BendTwist) as ObiConstraints<ObiBendTwistConstraintsBatch>;
|
||||
var sbc = solver.GetConstraintsByType(Oni.ConstraintType.BendTwist) as ObiConstraints<ObiBendTwistConstraintsBatch>;
|
||||
|
||||
if (bendTwistConstraintsEnabled && bc != null && sbc != null)
|
||||
for (int j = 0; j < bc.GetBatchCount(); ++j)
|
||||
{
|
||||
var batch = bc.GetBatch(j) as ObiBendTwistConstraintsBatch;
|
||||
var solverBatch = sbc.batches[j] as ObiBendTwistConstraintsBatch;
|
||||
int offset = solverBatchOffsets[(int)solverBatch.constraintType][j];
|
||||
|
||||
if (solverBatch.restDarbouxVectors.isCreated)
|
||||
{
|
||||
for (int i = 0; i < batch.activeConstraintCount; i++)
|
||||
{
|
||||
int indexA = batch.particleIndices[i * 2];
|
||||
int indexB = batch.particleIndices[i * 2 + 1];
|
||||
|
||||
// calculate bone rotation delta in world space:
|
||||
var boneDeltaAWS = boneBlueprint.transforms[indexA].rotation * Quaternion.Inverse(boneBlueprint.restOrientations[indexA]);
|
||||
var boneDeltaBWS = boneBlueprint.transforms[indexB].rotation * Quaternion.Inverse(boneBlueprint.restOrientations[indexB]);
|
||||
|
||||
// apply delta to rest particle orientation:
|
||||
var orientationA = boneDeltaAWS * boneBlueprint.root2WorldR * boneBlueprint.orientations[indexA];
|
||||
var orientationB = boneDeltaBWS * boneBlueprint.root2WorldR * boneBlueprint.orientations[indexB];
|
||||
|
||||
solverBatch.restDarbouxVectors[offset + i] = ObiUtils.RestDarboux(orientationA, orientationB);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var sc = GetConstraintsByType(Oni.ConstraintType.Skin) as ObiConstraints<ObiSkinConstraintsBatch>;
|
||||
var ssc = solver.GetConstraintsByType(Oni.ConstraintType.Skin) as ObiConstraints<ObiSkinConstraintsBatch>;
|
||||
|
||||
if (skinConstraintsEnabled && sc != null && ssc != null)
|
||||
for (int j = 0; j < sc.GetBatchCount(); ++j)
|
||||
{
|
||||
var batch = sc.GetBatch(j) as ObiSkinConstraintsBatch;
|
||||
var solverBatch = ssc.batches[j] as ObiSkinConstraintsBatch;
|
||||
int offset = solverBatchOffsets[(int)solverBatch.constraintType][j];
|
||||
|
||||
if (solverBatch.skinPoints.isCreated)
|
||||
{
|
||||
for (int i = 0; i < batch.activeConstraintCount; i++)
|
||||
{
|
||||
int index = batch.particleIndices[i];
|
||||
solverBatch.skinPoints[offset + i] = solver.transform.worldToLocalMatrix.MultiplyPoint3x4(boneBlueprint.transforms[index].position);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CopyParticleDataToTransforms()
|
||||
{
|
||||
|
||||
if (boneBlueprint != null)
|
||||
{
|
||||
// copy current particle transforms to bones:
|
||||
for (int i = 1; i < particleCount; ++i)
|
||||
{
|
||||
var trfm = boneBlueprint.transforms[i];
|
||||
|
||||
if (stretchBones)
|
||||
trfm.position = GetParticlePosition(solverIndices[i]);
|
||||
|
||||
var delta = GetParticleOrientation(solverIndices[i]) * Quaternion.Inverse(boneBlueprint.root2WorldR * boneBlueprint.orientations[i]);
|
||||
trfm.rotation = delta * boneBlueprint.restOrientations[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Obi/Scripts/RopeAndRod/Actors/ObiBone.cs.meta
Normal file
11
Assets/Obi/Scripts/RopeAndRod/Actors/ObiBone.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7be315bc5964b4fbca6cf0a978ff76a1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 0a18e0376cc184a9b96ebb3bf0175cc2, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
253
Assets/Obi/Scripts/RopeAndRod/Actors/ObiRod.cs
Normal file
253
Assets/Obi/Scripts/RopeAndRod/Actors/ObiRod.cs
Normal file
@@ -0,0 +1,253 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
[AddComponentMenu("Physics/Obi/Obi Rod", 881)]
|
||||
[ExecuteInEditMode]
|
||||
[DisallowMultipleComponent]
|
||||
public class ObiRod : ObiRopeBase, IStretchShearConstraintsUser, IBendTwistConstraintsUser, IChainConstraintsUser
|
||||
{
|
||||
[SerializeField] protected ObiRodBlueprint m_RodBlueprint;
|
||||
|
||||
// distance constraints:
|
||||
[SerializeField] protected bool _stretchShearConstraintsEnabled = true;
|
||||
[SerializeField] protected float _stretchCompliance = 0;
|
||||
[SerializeField] protected float _shear1Compliance = 0;
|
||||
[SerializeField] protected float _shear2Compliance = 0;
|
||||
|
||||
// bend constraints:
|
||||
[SerializeField] protected bool _bendTwistConstraintsEnabled = true;
|
||||
[SerializeField] protected float _torsionCompliance = 0;
|
||||
[SerializeField] protected float _bend1Compliance = 0;
|
||||
[SerializeField] protected float _bend2Compliance = 0;
|
||||
[SerializeField] [Range(0, 0.1f)] protected float _plasticYield = 0;
|
||||
[SerializeField] protected float _plasticCreep = 0;
|
||||
|
||||
// chain constraints:
|
||||
[SerializeField] protected bool _chainConstraintsEnabled = true;
|
||||
[SerializeField] [Range(0, 1)] protected float _tightness = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Whether particles in this actor colide with particles using the same phase value.
|
||||
/// </summary>
|
||||
public bool selfCollisions
|
||||
{
|
||||
get { return m_SelfCollisions; }
|
||||
set { if (value != m_SelfCollisions) { m_SelfCollisions = value; SetSelfCollisions(m_SelfCollisions); } }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this actor's stretch/shear constraints are enabled.
|
||||
/// </summary>
|
||||
public bool stretchShearConstraintsEnabled
|
||||
{
|
||||
get { return _stretchShearConstraintsEnabled; }
|
||||
set { if (value != _stretchShearConstraintsEnabled) { _stretchShearConstraintsEnabled = value; SetConstraintsDirty(Oni.ConstraintType.StretchShear); } }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compliance of this actor's stretch/shear constraints, along their length.
|
||||
/// </summary>
|
||||
public float stretchCompliance
|
||||
{
|
||||
get { return _stretchCompliance; }
|
||||
set { _stretchCompliance = value; SetConstraintsDirty(Oni.ConstraintType.StretchShear); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shearing compliance of this actor's stretch/shear constraints, along the first axis orthogonal to their length.
|
||||
/// </summary>
|
||||
public float shear1Compliance
|
||||
{
|
||||
get { return _shear1Compliance; }
|
||||
set { _shear1Compliance = value; SetConstraintsDirty(Oni.ConstraintType.StretchShear); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shearing compliance of this actor's stretch/shear constraints, along the second axis orthogonal to their length.
|
||||
/// </summary>
|
||||
public float shear2Compliance
|
||||
{
|
||||
get { return _shear2Compliance; }
|
||||
set { _shear2Compliance = value; SetConstraintsDirty(Oni.ConstraintType.StretchShear); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this actor's bend/twist constraints are enabled.
|
||||
/// </summary>
|
||||
public bool bendTwistConstraintsEnabled
|
||||
{
|
||||
get { return _bendTwistConstraintsEnabled; }
|
||||
set { if (value != _bendTwistConstraintsEnabled) { _bendTwistConstraintsEnabled = value; SetConstraintsDirty(Oni.ConstraintType.BendTwist); } }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Torsional compliance of this actor's bend/twist constraints along their length.
|
||||
/// </summary>
|
||||
public float torsionCompliance
|
||||
{
|
||||
get { return _torsionCompliance; }
|
||||
set { _torsionCompliance = value; SetConstraintsDirty(Oni.ConstraintType.BendTwist); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bending compliance of this actor's bend/twist constraints along the first axis orthogonal to their length.
|
||||
/// </summary>
|
||||
public float bend1Compliance
|
||||
{
|
||||
get { return _bend1Compliance; }
|
||||
set { _bend1Compliance = value; SetConstraintsDirty(Oni.ConstraintType.BendTwist); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bending compliance of this actor's bend/twist constraints along the second axis orthogonal to their length.
|
||||
/// </summary>
|
||||
public float bend2Compliance
|
||||
{
|
||||
get { return _bend2Compliance; }
|
||||
set { _bend2Compliance = value; SetConstraintsDirty(Oni.ConstraintType.BendTwist); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Threshold for plastic behavior.
|
||||
/// </summary>
|
||||
/// Once bending goes above this value, a percentage of the deformation (determined by <see cref="plasticCreep"/>) will be permanently absorbed into the rod's rest shape.
|
||||
public float plasticYield
|
||||
{
|
||||
get { return _plasticYield; }
|
||||
set { _plasticYield = value; SetConstraintsDirty(Oni.ConstraintType.BendTwist); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Percentage of deformation that gets absorbed into the rest shape per second, once deformation goes above the <see cref="plasticYield"/> threshold.
|
||||
/// </summary>
|
||||
public float plasticCreep
|
||||
{
|
||||
get { return _plasticCreep; }
|
||||
set { _plasticCreep = value; SetConstraintsDirty(Oni.ConstraintType.BendTwist); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this actor's chain constraints are enabled.
|
||||
/// </summary>
|
||||
public bool chainConstraintsEnabled
|
||||
{
|
||||
get { return _chainConstraintsEnabled; }
|
||||
set { if (value != _chainConstraintsEnabled) { _chainConstraintsEnabled = value; SetConstraintsDirty(Oni.ConstraintType.BendTwist); } }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tightness of this actor's chain constraints.
|
||||
/// </summary>
|
||||
/// Controls how much chain constraints are allowed to compress.
|
||||
public float tightness
|
||||
{
|
||||
get { return _tightness; }
|
||||
set { _tightness = value; SetConstraintsDirty(Oni.ConstraintType.Chain); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Average distance between consecutive particle centers in this rod.
|
||||
/// </summary>
|
||||
public float interParticleDistance
|
||||
{
|
||||
get { return m_RodBlueprint.interParticleDistance; }
|
||||
}
|
||||
|
||||
public override ObiActorBlueprint sourceBlueprint
|
||||
{
|
||||
get { return m_RodBlueprint; }
|
||||
}
|
||||
|
||||
public ObiRodBlueprint rodBlueprint
|
||||
{
|
||||
get { return m_RodBlueprint; }
|
||||
set
|
||||
{
|
||||
if (m_RodBlueprint != value)
|
||||
{
|
||||
RemoveFromSolver();
|
||||
ClearState();
|
||||
m_RodBlueprint = value;
|
||||
AddToSolver();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
SetConstraintsDirty(Oni.ConstraintType.BendTwist);
|
||||
SetupRuntimeConstraints();
|
||||
}
|
||||
|
||||
public override void LoadBlueprint(ObiSolver solver)
|
||||
{
|
||||
base.LoadBlueprint(solver);
|
||||
RebuildElementsFromConstraints();
|
||||
SetupRuntimeConstraints();
|
||||
}
|
||||
|
||||
private void SetupRuntimeConstraints()
|
||||
{
|
||||
SetConstraintsDirty(Oni.ConstraintType.StretchShear);
|
||||
//SetConstraintsDirty(Oni.ConstraintType.BendTwist);
|
||||
SetConstraintsDirty(Oni.ConstraintType.Chain);
|
||||
SetSelfCollisions(selfCollisions);
|
||||
RecalculateRestLength();
|
||||
SetSimplicesDirty();
|
||||
UpdateCollisionMaterials();
|
||||
}
|
||||
|
||||
public Vector3 GetBendTwistCompliance(ObiBendTwistConstraintsBatch batch, int constraintIndex)
|
||||
{
|
||||
return new Vector3(bend1Compliance, bend2Compliance, torsionCompliance);
|
||||
}
|
||||
|
||||
public Vector2 GetBendTwistPlasticity(ObiBendTwistConstraintsBatch batch, int constraintIndex)
|
||||
{
|
||||
return new Vector2(plasticYield, plasticCreep);
|
||||
}
|
||||
|
||||
public Vector3 GetStretchShearCompliance(ObiStretchShearConstraintsBatch batch, int constraintIndex)
|
||||
{
|
||||
return new Vector3(shear1Compliance, shear2Compliance, stretchCompliance);
|
||||
}
|
||||
|
||||
protected override void RebuildElementsFromConstraintsInternal()
|
||||
{
|
||||
var dc = GetConstraintsByType(Oni.ConstraintType.StretchShear) as ObiConstraints<ObiStretchShearConstraintsBatch>;
|
||||
if (dc == null || dc.GetBatchCount() < 2)
|
||||
return;
|
||||
|
||||
int constraintCount = dc.batches[0].activeConstraintCount + dc.batches[1].activeConstraintCount;
|
||||
|
||||
elements = new List<ObiStructuralElement>(constraintCount);
|
||||
|
||||
for (int i = 0; i < constraintCount; ++i)
|
||||
{
|
||||
var batch = dc.batches[i % 2] as ObiStretchShearConstraintsBatch;
|
||||
int constraintIndex = i / 2;
|
||||
|
||||
var e = new ObiStructuralElement();
|
||||
e.particle1 = solverIndices[batch.particleIndices[constraintIndex * 2]];
|
||||
e.particle2 = solverIndices[batch.particleIndices[constraintIndex * 2 + 1]];
|
||||
e.restLength = batch.restLengths[constraintIndex];
|
||||
elements.Add(e);
|
||||
}
|
||||
|
||||
if (dc.batches.Count > 2)
|
||||
{
|
||||
var batch = dc.batches[2];
|
||||
var e = new ObiStructuralElement();
|
||||
e.particle1 = solverIndices[batch.particleIndices[0]];
|
||||
e.particle2 = solverIndices[batch.particleIndices[1]];
|
||||
e.restLength = batch.restLengths[0];
|
||||
elements.Add(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
11
Assets/Obi/Scripts/RopeAndRod/Actors/ObiRod.cs.meta
Normal file
11
Assets/Obi/Scripts/RopeAndRod/Actors/ObiRod.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f2bc32c4faf04f039a86a6e3cea12a1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7125c3eebd3cc4f86aac51cca3ee1592, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
446
Assets/Obi/Scripts/RopeAndRod/Actors/ObiRope.cs
Normal file
446
Assets/Obi/Scripts/RopeAndRod/Actors/ObiRope.cs
Normal file
@@ -0,0 +1,446 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
[AddComponentMenu("Physics/Obi/Obi Rope", 880)]
|
||||
[ExecuteInEditMode]
|
||||
[DisallowMultipleComponent]
|
||||
public class ObiRope : ObiRopeBase, IDistanceConstraintsUser, IBendConstraintsUser
|
||||
{
|
||||
[SerializeField] protected ObiRopeBlueprint m_RopeBlueprint;
|
||||
private ObiRopeBlueprint m_RopeBlueprintInstance;
|
||||
|
||||
// rope has a list of structural elements.
|
||||
// each structural element is equivalent to 1 distance constraint and 2 bend constraints (with previous, and following element).
|
||||
// a structural element has force and rest length.
|
||||
// a function re-generates constraints from structural elements when needed, placing them in the appropiate batches.
|
||||
|
||||
public bool tearingEnabled = false;
|
||||
public float tearResistanceMultiplier = 1000; /**< Factor that controls how much a structural cloth spring can stretch before breaking.*/
|
||||
public int tearRate = 1;
|
||||
|
||||
// distance constraints:
|
||||
[SerializeField] protected bool _distanceConstraintsEnabled = true;
|
||||
[SerializeField] protected float _stretchingScale = 1;
|
||||
[SerializeField] protected float _stretchCompliance = 0;
|
||||
[SerializeField] [Range(0, 1)] protected float _maxCompression = 0;
|
||||
|
||||
// bend constraints:
|
||||
[SerializeField] protected bool _bendConstraintsEnabled = true;
|
||||
[SerializeField] protected float _bendCompliance = 0;
|
||||
[SerializeField] [Range(0, 0.5f)] protected float _maxBending = 0.025f;
|
||||
[SerializeField] [Range(0, 0.1f)] protected float _plasticYield = 0;
|
||||
[SerializeField] protected float _plasticCreep = 0;
|
||||
|
||||
List<ObiStructuralElement> tornElements = new List<ObiStructuralElement>();
|
||||
|
||||
/// <summary>
|
||||
/// Whether particles in this actor colide with particles using the same phase value.
|
||||
/// </summary>
|
||||
public bool selfCollisions
|
||||
{
|
||||
get { return m_SelfCollisions; }
|
||||
set { if (value != m_SelfCollisions) { m_SelfCollisions = value; SetSelfCollisions(selfCollisions); } }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this actor's distance constraints are enabled.
|
||||
/// </summary>
|
||||
public bool distanceConstraintsEnabled
|
||||
{
|
||||
get { return _distanceConstraintsEnabled; }
|
||||
set { if (value != _distanceConstraintsEnabled) { _distanceConstraintsEnabled = value; SetConstraintsDirty(Oni.ConstraintType.Distance); } }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scale value for this actor's distance constraints rest length.
|
||||
/// </summary>
|
||||
/// The default is 1. For instamce, a value of 2 will make the distance constraints twice as long, 0.5 will reduce their length in half.
|
||||
public float stretchingScale
|
||||
{
|
||||
get { return _stretchingScale; }
|
||||
set { _stretchingScale = value; SetConstraintsDirty(Oni.ConstraintType.Distance); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compliance of this actor's stretch constraints.
|
||||
/// </summary>
|
||||
public float stretchCompliance
|
||||
{
|
||||
get { return _stretchCompliance; }
|
||||
set { _stretchCompliance = value; SetConstraintsDirty(Oni.ConstraintType.Distance); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maximum compression this actor's distance constraints can undergo.
|
||||
/// </summary>
|
||||
/// This is expressed as a percentage of the scaled rest length.
|
||||
public float maxCompression
|
||||
{
|
||||
get { return _maxCompression; }
|
||||
set { _maxCompression = value; SetConstraintsDirty(Oni.ConstraintType.Distance); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this actor's bend constraints are enabled.
|
||||
/// </summary>
|
||||
public bool bendConstraintsEnabled
|
||||
{
|
||||
get { return _bendConstraintsEnabled; }
|
||||
set { if (value != _bendConstraintsEnabled) { _bendConstraintsEnabled = value; SetConstraintsDirty(Oni.ConstraintType.Bending); } }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compliance of this actor's bend constraints.
|
||||
/// </summary>
|
||||
public float bendCompliance
|
||||
{
|
||||
get { return _bendCompliance; }
|
||||
set { _bendCompliance = value; SetConstraintsDirty(Oni.ConstraintType.Bending); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Max bending value that constraints can undergo before resisting bending.
|
||||
/// </summary>
|
||||
public float maxBending
|
||||
{
|
||||
get { return _maxBending; }
|
||||
set { _maxBending = value; SetConstraintsDirty(Oni.ConstraintType.Bending); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Threshold for plastic behavior.
|
||||
/// </summary>
|
||||
/// Once bending goes above this value, a percentage of the deformation (determined by <see cref="plasticCreep"/>) will be permanently absorbed into the rope's rest shape.
|
||||
public float plasticYield
|
||||
{
|
||||
get { return _plasticYield; }
|
||||
set { _plasticYield = value; SetConstraintsDirty(Oni.ConstraintType.Bending); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Percentage of deformation that gets absorbed into the rest shape per second, once deformation goes above the <see cref="plasticYield"/> threshold.
|
||||
/// </summary>
|
||||
public float plasticCreep
|
||||
{
|
||||
get { return _plasticCreep; }
|
||||
set { _plasticCreep = value; SetConstraintsDirty(Oni.ConstraintType.Bending); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Average distance between consecutive particle centers in this rope.
|
||||
/// </summary>
|
||||
public float interParticleDistance
|
||||
{
|
||||
get { return m_RopeBlueprint.interParticleDistance; }
|
||||
}
|
||||
|
||||
public override ObiActorBlueprint sourceBlueprint
|
||||
{
|
||||
get { return m_RopeBlueprint; }
|
||||
}
|
||||
|
||||
public ObiRopeBlueprint ropeBlueprint
|
||||
{
|
||||
get { return m_RopeBlueprint; }
|
||||
set
|
||||
{
|
||||
if (m_RopeBlueprint != value)
|
||||
{
|
||||
RemoveFromSolver();
|
||||
ClearState();
|
||||
m_RopeBlueprint = value;
|
||||
AddToSolver();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public delegate void RopeTornCallback(ObiRope rope, ObiRopeTornEventArgs tearInfo);
|
||||
public event RopeTornCallback OnRopeTorn; /**< Called when a constraint is torn.*/
|
||||
|
||||
public class ObiRopeTornEventArgs
|
||||
{
|
||||
public ObiStructuralElement element; /**< info about the element being torn.*/
|
||||
public int particleIndex; /**< index of the particle being torn*/
|
||||
|
||||
public ObiRopeTornEventArgs(ObiStructuralElement element, int particle)
|
||||
{
|
||||
this.element = element;
|
||||
this.particleIndex = particle;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
SetupRuntimeConstraints();
|
||||
}
|
||||
|
||||
public override void LoadBlueprint(ObiSolver solver)
|
||||
{
|
||||
// create a copy of the blueprint for this cloth:
|
||||
if (Application.isPlaying)
|
||||
m_RopeBlueprintInstance = this.blueprint as ObiRopeBlueprint;
|
||||
|
||||
base.LoadBlueprint(solver);
|
||||
RebuildElementsFromConstraints();
|
||||
SetupRuntimeConstraints();
|
||||
}
|
||||
|
||||
public override void UnloadBlueprint(ObiSolver solver)
|
||||
{
|
||||
base.UnloadBlueprint(solver);
|
||||
|
||||
// delete the blueprint instance:
|
||||
if (m_RopeBlueprintInstance != null)
|
||||
DestroyImmediate(m_RopeBlueprintInstance);
|
||||
}
|
||||
|
||||
private void SetupRuntimeConstraints()
|
||||
{
|
||||
SetConstraintsDirty(Oni.ConstraintType.Distance);
|
||||
SetConstraintsDirty(Oni.ConstraintType.Bending);
|
||||
SetSelfCollisions(selfCollisions);
|
||||
RecalculateRestLength();
|
||||
SetSimplicesDirty();
|
||||
UpdateCollisionMaterials();
|
||||
}
|
||||
|
||||
public override void Substep(float substepTime)
|
||||
{
|
||||
base.Substep(substepTime);
|
||||
|
||||
if (isActiveAndEnabled)
|
||||
ApplyTearing(substepTime);
|
||||
}
|
||||
|
||||
protected void ApplyTearing(float substepTime)
|
||||
{
|
||||
|
||||
if (!tearingEnabled)
|
||||
return;
|
||||
|
||||
float sqrTime = substepTime * substepTime;
|
||||
|
||||
tornElements.Clear();
|
||||
|
||||
var dc = GetConstraintsByType(Oni.ConstraintType.Distance) as ObiConstraints<ObiDistanceConstraintsBatch>;
|
||||
var sc = this.solver.GetConstraintsByType(Oni.ConstraintType.Distance) as ObiConstraints<ObiDistanceConstraintsBatch>;
|
||||
|
||||
if (dc != null && sc != null)
|
||||
for (int j = 0; j < dc.GetBatchCount(); ++j)
|
||||
{
|
||||
var batch = dc.GetBatch(j) as ObiDistanceConstraintsBatch;
|
||||
var solverBatch = sc.batches[j] as ObiDistanceConstraintsBatch;
|
||||
|
||||
for (int i = 0; i < batch.activeConstraintCount; i++)
|
||||
{
|
||||
int elementIndex = j + 2 * i;
|
||||
|
||||
// divide lambda by squared delta time to get force in newtons:
|
||||
int offset = solverBatchOffsets[(int)Oni.ConstraintType.Distance][j];
|
||||
float force = solverBatch.lambdas[offset + i] / sqrTime;
|
||||
|
||||
elements[elementIndex].constraintForce = force;
|
||||
|
||||
if (-force > tearResistanceMultiplier)
|
||||
{
|
||||
tornElements.Add(elements[elementIndex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tornElements.Count > 0)
|
||||
{
|
||||
|
||||
// sort edges by force:
|
||||
tornElements.Sort(delegate (ObiStructuralElement x, ObiStructuralElement y)
|
||||
{
|
||||
return x.constraintForce.CompareTo(y.constraintForce);
|
||||
});
|
||||
|
||||
int tornCount = 0;
|
||||
for (int i = 0; i < tornElements.Count; i++)
|
||||
{
|
||||
if (Tear(tornElements[i]))
|
||||
tornCount++;
|
||||
if (tornCount >= tearRate)
|
||||
break;
|
||||
}
|
||||
|
||||
if (tornCount > 0)
|
||||
RebuildConstraintsFromElements();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private int SplitParticle(int splitIndex)
|
||||
{
|
||||
// halve the original particle's mass:
|
||||
m_Solver.invMasses[splitIndex] *= 2;
|
||||
|
||||
CopyParticle(solver.particleToActor[splitIndex].indexInActor, activeParticleCount);
|
||||
ActivateParticle(activeParticleCount);
|
||||
|
||||
return solverIndices[activeParticleCount - 1];
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Tears any given rope element. After calling Tear() one or multiple times, a call to RebuildConstraintsFromElements is needed to
|
||||
/// update the rope particle/constraint representation.
|
||||
/// </summary>
|
||||
public bool Tear(ObiStructuralElement element)
|
||||
{
|
||||
// don't allow splitting if there are no free particles left in the pool.
|
||||
if (activeParticleCount >= m_RopeBlueprint.particleCount)
|
||||
return false;
|
||||
|
||||
// Cannot split fixed particles:
|
||||
if (m_Solver.invMasses[element.particle1] == 0)
|
||||
return false;
|
||||
|
||||
// Or particles that have been already split.
|
||||
int index = elements.IndexOf(element);
|
||||
if (index > 0 && elements[index - 1].particle2 != element.particle1)
|
||||
return false;
|
||||
|
||||
element.particle1 = SplitParticle(element.particle1);
|
||||
|
||||
if (OnRopeTorn != null)
|
||||
OnRopeTorn(this, new ObiRopeTornEventArgs(element, element.particle1));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void RebuildElementsFromConstraintsInternal()
|
||||
{
|
||||
var dc = GetConstraintsByType(Oni.ConstraintType.Distance) as ObiConstraints<ObiDistanceConstraintsBatch>;
|
||||
if (dc == null || dc.GetBatchCount() < 2)
|
||||
return;
|
||||
|
||||
int constraintCount = dc.batches[0].activeConstraintCount + dc.batches[1].activeConstraintCount;
|
||||
|
||||
elements = new List<ObiStructuralElement>(constraintCount);
|
||||
|
||||
for (int i = 0; i < constraintCount; ++i)
|
||||
{
|
||||
var batch = dc.batches[i % 2] as ObiDistanceConstraintsBatch;
|
||||
int constraintIndex = i / 2;
|
||||
|
||||
var e = new ObiStructuralElement();
|
||||
e.particle1 = solverIndices[batch.particleIndices[constraintIndex * 2]];
|
||||
e.particle2 = solverIndices[batch.particleIndices[constraintIndex * 2 + 1]];
|
||||
e.restLength = batch.restLengths[constraintIndex];
|
||||
e.tearResistance = 1;
|
||||
elements.Add(e);
|
||||
}
|
||||
|
||||
// loop-closing element:
|
||||
if (dc.batches.Count > 2)
|
||||
{
|
||||
var batch = dc.batches[2];
|
||||
var e = new ObiStructuralElement();
|
||||
e.particle1 = solverIndices[batch.particleIndices[0]];
|
||||
e.particle2 = solverIndices[batch.particleIndices[1]];
|
||||
e.restLength = batch.restLengths[0];
|
||||
e.tearResistance = 1;
|
||||
elements.Add(e);
|
||||
}
|
||||
}
|
||||
|
||||
public override void RebuildConstraintsFromElements()
|
||||
{
|
||||
// regenerate constraints from elements:
|
||||
var dc = GetConstraintsByType(Oni.ConstraintType.Distance) as ObiConstraints<ObiDistanceConstraintsBatch>;
|
||||
var bc = GetConstraintsByType(Oni.ConstraintType.Bending) as ObiConstraints<ObiBendConstraintsBatch>;
|
||||
|
||||
dc.DeactivateAllConstraints();
|
||||
bc.DeactivateAllConstraints();
|
||||
|
||||
int elementsCount = elements.Count - (ropeBlueprint.path.Closed ? 1 : 0);
|
||||
for (int i = 0; i < elementsCount; ++i)
|
||||
{
|
||||
var db = dc.batches[i % 2] as ObiDistanceConstraintsBatch;
|
||||
int constraint = db.activeConstraintCount;
|
||||
|
||||
db.particleIndices[constraint * 2] = solver.particleToActor[elements[i].particle1].indexInActor;
|
||||
db.particleIndices[constraint * 2 + 1] = solver.particleToActor[elements[i].particle2].indexInActor;
|
||||
db.restLengths[constraint] = elements[i].restLength;
|
||||
db.stiffnesses[constraint] = new Vector2(_stretchCompliance, _maxCompression * db.restLengths[constraint]);
|
||||
db.ActivateConstraint(constraint);
|
||||
|
||||
if (i < elementsCount - 1)
|
||||
{
|
||||
var bb = bc.batches[i % 3] as ObiBendConstraintsBatch;
|
||||
|
||||
// create bend constraint only if there's continuity between elements:
|
||||
if (elements[i].particle2 == elements[i + 1].particle1)
|
||||
{
|
||||
constraint = bb.activeConstraintCount;
|
||||
|
||||
int indexA = elements[i].particle1;
|
||||
int indexB = elements[i + 1].particle2;
|
||||
int indexC = elements[i].particle2;
|
||||
float restBend = ObiUtils.RestBendingConstraint(solver.restPositions[indexA], solver.restPositions[indexB], solver.restPositions[indexC]);
|
||||
|
||||
bb.particleIndices[constraint * 3] = solver.particleToActor[indexA].indexInActor;
|
||||
bb.particleIndices[constraint * 3 + 1] = solver.particleToActor[indexB].indexInActor;
|
||||
bb.particleIndices[constraint * 3 + 2] = solver.particleToActor[indexC].indexInActor;
|
||||
bb.restBends[constraint] = restBend;
|
||||
bb.bendingStiffnesses[constraint] = new Vector2(_maxBending, _bendCompliance);
|
||||
bb.ActivateConstraint(constraint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// loop-closing constraints:
|
||||
if (dc.batches.Count > 2)
|
||||
{
|
||||
var loopClosingBatch = dc.batches[2];
|
||||
var lastElement = elements[elements.Count - 1];
|
||||
loopClosingBatch.particleIndices[0] = solver.particleToActor[lastElement.particle1].indexInActor;
|
||||
loopClosingBatch.particleIndices[1] = solver.particleToActor[lastElement.particle2].indexInActor;
|
||||
loopClosingBatch.restLengths[0] = lastElement.restLength;
|
||||
loopClosingBatch.stiffnesses[0] = new Vector2(_stretchCompliance, _maxCompression * loopClosingBatch.restLengths[0]);
|
||||
loopClosingBatch.ActivateConstraint(0);
|
||||
}
|
||||
|
||||
if (bc.batches.Count > 4 && elements.Count > 2)
|
||||
{
|
||||
var loopClosingBatch = bc.batches[3];
|
||||
var lastElement = elements[elements.Count - 2];
|
||||
|
||||
// for loop constraints, 0 is our best approximation of rest bend:
|
||||
loopClosingBatch.particleIndices[0] = solver.particleToActor[lastElement.particle1].indexInActor;
|
||||
loopClosingBatch.particleIndices[1] = solver.particleToActor[elements[0].particle1].indexInActor;
|
||||
loopClosingBatch.particleIndices[2] = solver.particleToActor[lastElement.particle2].indexInActor;
|
||||
loopClosingBatch.restBends[0] = 0;
|
||||
loopClosingBatch.bendingStiffnesses[0] = new Vector2(_maxBending, _bendCompliance);
|
||||
loopClosingBatch.ActivateConstraint(0);
|
||||
|
||||
loopClosingBatch = bc.batches[4];
|
||||
loopClosingBatch.particleIndices[0] = solver.particleToActor[lastElement.particle2].indexInActor;
|
||||
loopClosingBatch.particleIndices[1] = solver.particleToActor[elements[0].particle2].indexInActor;
|
||||
loopClosingBatch.particleIndices[2] = solver.particleToActor[elements[0].particle1].indexInActor;
|
||||
loopClosingBatch.restBends[0] = 0;
|
||||
loopClosingBatch.bendingStiffnesses[0] = new Vector2(_maxBending, _bendCompliance);
|
||||
loopClosingBatch.ActivateConstraint(0);
|
||||
}
|
||||
|
||||
// edge simplices:
|
||||
sharedBlueprint.edges = new int[elements.Count*2];
|
||||
for (int i = 0; i < elements.Count; ++i)
|
||||
{
|
||||
sharedBlueprint.edges[i * 2] = solver.particleToActor[elements[i].particle1].indexInActor;
|
||||
sharedBlueprint.edges[i * 2 + 1] = solver.particleToActor[elements[i].particle2].indexInActor;
|
||||
}
|
||||
|
||||
SetConstraintsDirty(Oni.ConstraintType.Distance);
|
||||
SetConstraintsDirty(Oni.ConstraintType.Bending);
|
||||
SetSimplicesDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Obi/Scripts/RopeAndRod/Actors/ObiRope.cs.meta
Normal file
11
Assets/Obi/Scripts/RopeAndRod/Actors/ObiRope.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 61104f33a3f344db9b7e0d0cda41a9fb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7a860725829a34375a627c13703f0798, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
109
Assets/Obi/Scripts/RopeAndRod/Actors/ObiRopeBase.cs
Normal file
109
Assets/Obi/Scripts/RopeAndRod/Actors/ObiRopeBase.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
public abstract class ObiRopeBase : ObiActor
|
||||
{
|
||||
|
||||
[SerializeField] protected bool m_SelfCollisions = false;
|
||||
[HideInInspector] [SerializeField] protected float restLength_ = 0;
|
||||
[HideInInspector] public List<ObiStructuralElement> elements = new List<ObiStructuralElement>(); /**< Elements.*/
|
||||
public event ActorCallback OnElementsGenerated;
|
||||
|
||||
public float restLength
|
||||
{
|
||||
get { return restLength_; }
|
||||
}
|
||||
|
||||
public ObiPath path
|
||||
{
|
||||
get {
|
||||
var ropeBlueprint = (sourceBlueprint as ObiRopeBlueprintBase);
|
||||
return ropeBlueprint != null ? ropeBlueprint.path : null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates and returns current rope length, including stretching/compression.
|
||||
/// </summary>
|
||||
public float CalculateLength()
|
||||
{
|
||||
float length = 0;
|
||||
|
||||
if (isLoaded)
|
||||
{
|
||||
// Iterate trough all distance constraints in order:
|
||||
int elementCount = elements.Count;
|
||||
for (int i = 0; i < elementCount; ++i)
|
||||
length += Vector4.Distance(solver.positions[elements[i].particle1], solver.positions[elements[i].particle2]);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recalculates the rope's rest length, that is, its length as specified by the blueprint.
|
||||
/// </summary>
|
||||
public void RecalculateRestLength()
|
||||
{
|
||||
restLength_ = 0;
|
||||
|
||||
// Iterate trough all distance elements and accumulate their rest lengths.
|
||||
int elementCount = elements.Count;
|
||||
for (int i = 0; i < elementCount; ++i)
|
||||
restLength_ += elements[i].restLength;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recalculates all particle rest positions, used when filtering self-collisions.
|
||||
/// </summary>
|
||||
public void RecalculateRestPositions()
|
||||
{
|
||||
float pos = 0;
|
||||
int elementCount = elements.Count;
|
||||
for (int i = 0; i < elementCount; ++i)
|
||||
{
|
||||
solver.restPositions[elements[i].particle1] = new Vector4(pos, 0, 0, 1);
|
||||
pos += elements[i].restLength;
|
||||
solver.restPositions[elements[i].particle2] = new Vector4(pos, 0, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Regenerates all rope elements using constraints. It's the opposite of RebuildConstraintsFromElements(). This is automatically called when loading a blueprint, but should also be called when manually
|
||||
/// altering rope constraints (adding/removing/updating constraints and/or batches).
|
||||
/// </summary>
|
||||
public void RebuildElementsFromConstraints()
|
||||
{
|
||||
RebuildElementsFromConstraintsInternal();
|
||||
if (OnElementsGenerated != null)
|
||||
OnElementsGenerated(this);
|
||||
}
|
||||
|
||||
protected abstract void RebuildElementsFromConstraintsInternal();
|
||||
|
||||
/// <summary>
|
||||
/// Regenerates all rope constraints using rope elements. It's the opposite of RebuildElementsFromConstraints().This should be called anytime the element representation of the rope
|
||||
/// is changed (adding/removing/updating elements). This is usually the case after tearing the rope or changing its length using a cursor.
|
||||
/// </summary>
|
||||
public virtual void RebuildConstraintsFromElements() { }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns a rope element that contains a length-normalized coordinate. It will also return the length-normalized coordinate within the element.
|
||||
/// </summary>
|
||||
public ObiStructuralElement GetElementAt(float mu, out float elementMu)
|
||||
{
|
||||
float edgeMu = elements.Count * Mathf.Clamp(mu, 0, 0.99999f);
|
||||
|
||||
int index = (int)edgeMu;
|
||||
elementMu = edgeMu - index;
|
||||
|
||||
if (elements != null && index < elements.Count)
|
||||
return elements[index];
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
11
Assets/Obi/Scripts/RopeAndRod/Actors/ObiRopeBase.cs.meta
Normal file
11
Assets/Obi/Scripts/RopeAndRod/Actors/ObiRopeBase.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ab4d6f4281d1c4331b6bacd307c61a95
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
269
Assets/Obi/Scripts/RopeAndRod/Actors/ObiRopeCursor.cs
Normal file
269
Assets/Obi/Scripts/RopeAndRod/Actors/ObiRopeCursor.cs
Normal file
@@ -0,0 +1,269 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
[AddComponentMenu("Physics/Obi/Obi Rope Cursor", 883)]
|
||||
[RequireComponent(typeof(ObiRope))]
|
||||
public class ObiRopeCursor : MonoBehaviour
|
||||
{
|
||||
ObiRope rope;
|
||||
|
||||
[Range(0, 1)]
|
||||
[HideInInspector] [SerializeField] private float m_CursorMu;
|
||||
|
||||
[Range(0, 1)]
|
||||
[HideInInspector] [SerializeField] private float m_SourceMu;
|
||||
|
||||
public bool direction = true;
|
||||
|
||||
ObiStructuralElement m_CursorElement = null;
|
||||
private int m_SourceIndex = -1;
|
||||
|
||||
public float cursorMu
|
||||
{
|
||||
set
|
||||
{
|
||||
m_CursorMu = value;
|
||||
UpdateCursor();
|
||||
}
|
||||
get { return m_CursorMu; }
|
||||
}
|
||||
|
||||
public float sourceMu
|
||||
{
|
||||
set
|
||||
{
|
||||
m_SourceMu = value;
|
||||
UpdateSource();
|
||||
}
|
||||
get { return m_SourceMu; }
|
||||
}
|
||||
|
||||
public ObiStructuralElement cursorElement
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_CursorElement == null)
|
||||
UpdateCursor();
|
||||
return m_CursorElement;
|
||||
}
|
||||
}
|
||||
|
||||
public int sourceParticleIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_SourceIndex < 0)
|
||||
UpdateSource();
|
||||
return m_SourceIndex;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
rope = GetComponent<ObiRope>();
|
||||
|
||||
rope.OnElementsGenerated += Actor_OnElementsGenerated;
|
||||
if (rope.elements != null && rope.elements.Count > 0)
|
||||
Actor_OnElementsGenerated(rope);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
rope.OnElementsGenerated -= Actor_OnElementsGenerated;
|
||||
}
|
||||
|
||||
private void Actor_OnElementsGenerated(ObiActor actor)
|
||||
{
|
||||
UpdateCursor();
|
||||
UpdateSource();
|
||||
}
|
||||
|
||||
public void UpdateCursor()
|
||||
{
|
||||
rope = GetComponent<ObiRope>();
|
||||
m_CursorElement = null;
|
||||
if (rope.isLoaded)
|
||||
{
|
||||
float elmMu;
|
||||
m_CursorElement = rope.GetElementAt(cursorMu, out elmMu);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateSource()
|
||||
{
|
||||
rope = GetComponent<ObiRope>();
|
||||
m_SourceIndex = -1;
|
||||
if (rope.isLoaded)
|
||||
{
|
||||
float elmMu;
|
||||
var elm = rope.GetElementAt(sourceMu, out elmMu);
|
||||
if (elm != null && rope.solver != null)
|
||||
{
|
||||
m_SourceIndex = elmMu < 0.5f ? elm.particle1 : elm.particle2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int AddParticleAt(int index)
|
||||
{
|
||||
// Copy data from the particle where we will insert new particles, to the particles we will insert:
|
||||
int targetIndex = rope.activeParticleCount;
|
||||
rope.CopyParticle(rope.solver.particleToActor[m_SourceIndex].indexInActor, targetIndex);
|
||||
|
||||
// Move the new particle to the one at the place where we will insert it:
|
||||
rope.TeleportParticle(targetIndex, rope.solver.positions[rope.solverIndices[index]]);
|
||||
|
||||
// Activate the particle:
|
||||
rope.ActivateParticle(targetIndex);
|
||||
return rope.solverIndices[targetIndex];
|
||||
}
|
||||
|
||||
private void RemoveParticleAt(int index)
|
||||
{
|
||||
rope.DeactivateParticle(index);
|
||||
}
|
||||
|
||||
public void ChangeLength(float newLength)
|
||||
{
|
||||
if (!rope.isLoaded)
|
||||
return;
|
||||
|
||||
var solver = rope.solver;
|
||||
|
||||
// clamp new length to sane limits:
|
||||
newLength = Mathf.Clamp(newLength, 0, (rope.sourceBlueprint.particleCount - 1) * rope.ropeBlueprint.interParticleDistance);
|
||||
|
||||
// calculate the change in rope length:
|
||||
float lengthChange = newLength - rope.restLength;
|
||||
|
||||
// remove:
|
||||
if (lengthChange < 0)
|
||||
{
|
||||
lengthChange = -lengthChange;
|
||||
|
||||
while (lengthChange > m_CursorElement.restLength)
|
||||
{
|
||||
lengthChange -= m_CursorElement.restLength;
|
||||
|
||||
int index = rope.elements.IndexOf(m_CursorElement);
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
// positive direction:
|
||||
if (direction)
|
||||
{
|
||||
RemoveParticleAt(solver.particleToActor[m_CursorElement.particle2].indexInActor);
|
||||
rope.elements.RemoveAt(index);
|
||||
|
||||
if (index < rope.elements.Count)
|
||||
{
|
||||
if (rope.elements[index].particle1 == m_CursorElement.particle2)
|
||||
rope.elements[index].particle1 = m_CursorElement.particle1;
|
||||
|
||||
m_CursorElement = rope.elements[index];
|
||||
}
|
||||
else
|
||||
m_CursorElement = rope.elements[Mathf.Max(0,index - 1)];
|
||||
}
|
||||
else // negative direction:
|
||||
{
|
||||
RemoveParticleAt(solver.particleToActor[m_CursorElement.particle1].indexInActor);
|
||||
rope.elements.RemoveAt(index);
|
||||
|
||||
if (index > 0)
|
||||
{
|
||||
if (rope.elements[index - 1].particle2 == m_CursorElement.particle1)
|
||||
rope.elements[index - 1].particle2 = m_CursorElement.particle2;
|
||||
m_CursorElement = rope.elements[index - 1];
|
||||
}
|
||||
else
|
||||
m_CursorElement = rope.elements[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the remaining length is subtracted from the current constraint:
|
||||
if (lengthChange > 0)
|
||||
m_CursorElement.restLength = Mathf.Max(0, m_CursorElement.restLength - lengthChange);
|
||||
|
||||
}
|
||||
// add
|
||||
else
|
||||
{
|
||||
float lengthDelta = Mathf.Min(lengthChange, Mathf.Max(0, rope.ropeBlueprint.interParticleDistance - m_CursorElement.restLength));
|
||||
|
||||
// extend the current element, if possible:
|
||||
if (lengthDelta > 0)
|
||||
{
|
||||
m_CursorElement.restLength += lengthDelta;
|
||||
lengthChange -= lengthDelta;
|
||||
}
|
||||
|
||||
// once the current element has been extended, see if we must add new elements, if there's enough particles left:
|
||||
while (rope.activeParticleCount < rope.sourceBlueprint.particleCount &&
|
||||
m_CursorElement.restLength + lengthChange > rope.ropeBlueprint.interParticleDistance)
|
||||
{
|
||||
// calculate added length:
|
||||
lengthDelta = Mathf.Min(lengthChange, rope.ropeBlueprint.interParticleDistance);
|
||||
lengthChange -= lengthDelta;
|
||||
|
||||
if (direction)
|
||||
{
|
||||
// add new particle:
|
||||
int newParticleSolverIndex = AddParticleAt(solver.particleToActor[m_CursorElement.particle1].indexInActor);
|
||||
|
||||
// set position of the new particle:
|
||||
solver.positions[newParticleSolverIndex] = solver.positions[m_CursorElement.particle1] +
|
||||
(solver.positions[m_CursorElement.particle2] - solver.positions[m_CursorElement.particle1]) * lengthDelta;
|
||||
|
||||
// insert a new element:
|
||||
ObiStructuralElement newElement = new ObiStructuralElement();
|
||||
newElement.restLength = lengthDelta;
|
||||
newElement.particle1 = m_CursorElement.particle1;
|
||||
newElement.particle2 = newParticleSolverIndex;
|
||||
m_CursorElement.particle1 = newParticleSolverIndex;
|
||||
int index = rope.elements.IndexOf(m_CursorElement);
|
||||
rope.elements.Insert(index, newElement);
|
||||
|
||||
m_CursorElement = newElement;
|
||||
}
|
||||
else
|
||||
{
|
||||
// add new particle:
|
||||
int newParticleSolverIndex = AddParticleAt(solver.particleToActor[m_CursorElement.particle2].indexInActor);
|
||||
|
||||
// set position of the new particle:
|
||||
solver.positions[newParticleSolverIndex] = solver.positions[m_CursorElement.particle2] +
|
||||
(solver.positions[m_CursorElement.particle1] - solver.positions[m_CursorElement.particle2]) * lengthDelta;
|
||||
|
||||
// insert a new element:
|
||||
ObiStructuralElement newElement = new ObiStructuralElement();
|
||||
newElement.restLength = lengthDelta;
|
||||
newElement.particle1 = newParticleSolverIndex;
|
||||
newElement.particle2 = m_CursorElement.particle2;
|
||||
m_CursorElement.particle2 = newParticleSolverIndex;
|
||||
int index = rope.elements.IndexOf(m_CursorElement);
|
||||
rope.elements.Insert(index + 1, newElement);
|
||||
|
||||
m_CursorElement = newElement;
|
||||
}
|
||||
}
|
||||
|
||||
// the remaining length is added to the current constraint:
|
||||
if (lengthChange > 0)
|
||||
m_CursorElement.restLength += lengthChange;
|
||||
|
||||
}
|
||||
|
||||
// recalculate rest positions and length prior to constraints (bend constraints need rest positions):
|
||||
rope.RecalculateRestPositions();
|
||||
rope.RecalculateRestLength();
|
||||
|
||||
// rebuild constraints:
|
||||
rope.RebuildConstraintsFromElements();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Obi/Scripts/RopeAndRod/Actors/ObiRopeCursor.cs.meta
Normal file
11
Assets/Obi/Scripts/RopeAndRod/Actors/ObiRopeCursor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8285ac97113f74d449053378d0e3a56b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 618cf36eb21e34118ac5697fe77e2c3e, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Obi/Scripts/RopeAndRod/Blueprints.meta
Normal file
8
Assets/Obi/Scripts/RopeAndRod/Blueprints.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: acf7eb82de1d147128faa88b9cdb70d3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
309
Assets/Obi/Scripts/RopeAndRod/Blueprints/ObiBoneBlueprint.cs
Normal file
309
Assets/Obi/Scripts/RopeAndRod/Blueprints/ObiBoneBlueprint.cs
Normal file
@@ -0,0 +1,309 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
|
||||
[CreateAssetMenu(fileName = "bone blueprint", menuName = "Obi/Bone Blueprint", order = 142)]
|
||||
public class ObiBoneBlueprint : ObiActorBlueprint
|
||||
{
|
||||
public Transform root;
|
||||
|
||||
public const float DEFAULT_PARTICLE_MASS = 0.1f;
|
||||
public const float DEFAULT_PARTICLE_ROTATIONAL_MASS = 0.1f;
|
||||
public const float DEFAULT_PARTICLE_RADIUS = 0.05f;
|
||||
|
||||
[HideInInspector] public List<Transform> transforms = new List<Transform>();
|
||||
[HideInInspector] public List<Quaternion> restTransformOrientations = new List<Quaternion>();
|
||||
[HideInInspector] public List<int> parentIndices = new List<int>();
|
||||
[HideInInspector] public List<float> normalizedLengths = new List<float>();
|
||||
|
||||
[HideInInspector] [NonSerialized] public List<ObiBone.IgnoredBone> ignored;
|
||||
[HideInInspector] [NonSerialized] public ObiBone.BonePropertyCurve mass;
|
||||
[HideInInspector] [NonSerialized] public ObiBone.BonePropertyCurve rotationalMass;
|
||||
[HideInInspector] [NonSerialized] public ObiBone.BonePropertyCurve radius;
|
||||
|
||||
public Quaternion root2WorldR;
|
||||
|
||||
private GraphColoring colorizer;
|
||||
|
||||
private ObiBone.IgnoredBone GetIgnoredBone(Transform bone)
|
||||
{
|
||||
for (int i = 0; i < ignored.Count; ++i)
|
||||
if (ignored[i].bone == bone)
|
||||
return ignored[i];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override IEnumerator Initialize()
|
||||
{
|
||||
ClearParticleGroups();
|
||||
|
||||
transforms.Clear();
|
||||
restTransformOrientations.Clear();
|
||||
parentIndices.Clear();
|
||||
normalizedLengths.Clear();
|
||||
|
||||
List<Vector3> particlePositions = new List<Vector3>();
|
||||
List<Quaternion> particleOrientations = new List<Quaternion>();
|
||||
|
||||
var world2Root = root.transform.worldToLocalMatrix;
|
||||
var world2RootR = world2Root.rotation;
|
||||
root2WorldR = Quaternion.Inverse(world2RootR);
|
||||
|
||||
// create a queue to traverse the hierarchy in a width-first fashion.
|
||||
Queue<Transform> bones = new Queue<Transform>();
|
||||
|
||||
// insert the root bone:
|
||||
bones.Enqueue(root);
|
||||
parentIndices.Add(-1);
|
||||
normalizedLengths.Add(0);
|
||||
|
||||
// initialize hierarchy length:
|
||||
float maxLength = 0;
|
||||
|
||||
while (bones.Count > 0)
|
||||
{
|
||||
var bone = bones.Dequeue();
|
||||
|
||||
if (bone != null)
|
||||
{
|
||||
var ig = GetIgnoredBone(bone);
|
||||
|
||||
if (ig == null)
|
||||
{
|
||||
transforms.Add(bone);
|
||||
restTransformOrientations.Add(bone.localRotation);
|
||||
particlePositions.Add(world2Root.MultiplyPoint3x4(bone.position));
|
||||
particleOrientations.Add(world2RootR * bone.rotation);
|
||||
}
|
||||
|
||||
if (ig == null || !ig.ignoreChildren)
|
||||
{
|
||||
foreach (Transform child in bone)
|
||||
{
|
||||
ig = GetIgnoredBone(child);
|
||||
|
||||
if (ig == null)
|
||||
{
|
||||
int parentIndex = transforms.Count - 1;
|
||||
parentIndices.Add(parentIndex);
|
||||
|
||||
float distanceToParent = Vector3.Distance(child.position, bone.position);
|
||||
float distanceToRoot = normalizedLengths[parentIndex] + distanceToParent;
|
||||
maxLength = Mathf.Max(maxLength, distanceToRoot);
|
||||
normalizedLengths.Add(distanceToRoot);
|
||||
}
|
||||
|
||||
bones.Enqueue(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// normalize lengths:
|
||||
if (maxLength > 0)
|
||||
{
|
||||
for (int i = 0; i < normalizedLengths.Count; ++i)
|
||||
normalizedLengths[i] /= maxLength;
|
||||
}
|
||||
|
||||
|
||||
// calculate orientations that minimize the Darboux vector:
|
||||
Vector3[] avgChildrenDirection = new Vector3[parentIndices.Count];
|
||||
int[] childCount = new int[parentIndices.Count];
|
||||
|
||||
for (int i = 0; i < parentIndices.Count; ++i)
|
||||
{
|
||||
int parent = parentIndices[i];
|
||||
if (parent >= 0)
|
||||
{
|
||||
var vector = particlePositions[i] - particlePositions[parent];
|
||||
avgChildrenDirection[parent] += vector;
|
||||
childCount[parent]++;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < parentIndices.Count; ++i)
|
||||
{
|
||||
if (childCount[i] > 0)
|
||||
particleOrientations[i] = Quaternion.LookRotation(avgChildrenDirection[i] / childCount[i]);
|
||||
else if (parentIndices[i] >= 0)
|
||||
particleOrientations[i] = particleOrientations[parentIndices[i]];
|
||||
}
|
||||
|
||||
|
||||
m_ActiveParticleCount = particlePositions.Count;
|
||||
|
||||
positions = new Vector3[m_ActiveParticleCount];
|
||||
orientations = new Quaternion[m_ActiveParticleCount];
|
||||
velocities = new Vector3[m_ActiveParticleCount];
|
||||
angularVelocities = new Vector3[m_ActiveParticleCount];
|
||||
invMasses = new float[m_ActiveParticleCount];
|
||||
invRotationalMasses = new float[m_ActiveParticleCount];
|
||||
principalRadii = new Vector3[m_ActiveParticleCount];
|
||||
filters = new int[m_ActiveParticleCount];
|
||||
restPositions = new Vector4[m_ActiveParticleCount];
|
||||
restOrientations = new Quaternion[m_ActiveParticleCount];
|
||||
colors = new Color[m_ActiveParticleCount];
|
||||
|
||||
for (int i = 0; i < m_ActiveParticleCount; i++)
|
||||
{
|
||||
invMasses[i] = ObiUtils.MassToInvMass(mass != null ? mass.Evaluate(normalizedLengths[i]) : DEFAULT_PARTICLE_MASS);
|
||||
invRotationalMasses[i] = ObiUtils.MassToInvMass(rotationalMass != null ? rotationalMass.Evaluate(normalizedLengths[i]) : DEFAULT_PARTICLE_ROTATIONAL_MASS);
|
||||
positions[i] = particlePositions[i];
|
||||
restPositions[i] = positions[i];
|
||||
restPositions[i][3] = 1; // activate rest position.
|
||||
orientations[i] = particleOrientations[i];
|
||||
restOrientations[i] = /*world2RootR */ transforms[i].rotation;
|
||||
principalRadii[i] = Vector3.one * (radius != null ? radius.Evaluate(normalizedLengths[i]) : DEFAULT_PARTICLE_RADIUS);
|
||||
filters[i] = ObiUtils.MakeFilter(ObiUtils.CollideWithEverything, 0);
|
||||
colors[i] = Color.white;
|
||||
|
||||
if (i % 100 == 0)
|
||||
yield return new CoroutineJob.ProgressInfo("ObiRod: generating particles...", i / (float)m_ActiveParticleCount);
|
||||
}
|
||||
|
||||
colorizer = new GraphColoring(m_ActiveParticleCount);
|
||||
|
||||
// Create edge simplices:
|
||||
CreateSimplices();
|
||||
|
||||
// Create stretch constraints:
|
||||
IEnumerator dc = CreateStretchShearConstraints(particlePositions);
|
||||
while (dc.MoveNext()) yield return dc.Current;
|
||||
|
||||
// Create bending constraints:
|
||||
IEnumerator bc = CreateBendTwistConstraints(particlePositions);
|
||||
while (bc.MoveNext()) yield return bc.Current;
|
||||
|
||||
// Create skin constraints:
|
||||
IEnumerator sc = CreateSkinConstraints(particlePositions);
|
||||
while (sc.MoveNext()) yield return sc.Current;
|
||||
|
||||
yield return new CoroutineJob.ProgressInfo("ObiBone: complete", 1);
|
||||
}
|
||||
|
||||
protected void CreateSimplices()
|
||||
{
|
||||
edges = new int[(parentIndices.Count - 1) * 2];
|
||||
for (int i = 0; i < parentIndices.Count-1; ++i)
|
||||
{
|
||||
edges[i * 2] = i + 1;
|
||||
edges[i * 2 + 1] = parentIndices[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual IEnumerator CreateStretchShearConstraints(List<Vector3> particlePositions)
|
||||
{
|
||||
|
||||
colorizer.Clear();
|
||||
|
||||
for (int i = 1; i < particlePositions.Count; ++i)
|
||||
{
|
||||
int parent = parentIndices[i];
|
||||
if (parent >= 0)
|
||||
{
|
||||
colorizer.AddConstraint(new[] { parent, i });
|
||||
}
|
||||
}
|
||||
|
||||
stretchShearConstraintsData = new ObiStretchShearConstraintsData();
|
||||
|
||||
List<int> constraintColors = new List<int>();
|
||||
var colorize = colorizer.Colorize("ObiBone: coloring stretch/shear constraints...", constraintColors);
|
||||
while (colorize.MoveNext())
|
||||
yield return colorize.Current;
|
||||
|
||||
var particleIndices = colorizer.particleIndices;
|
||||
var constraintIndices = colorizer.constraintIndices;
|
||||
|
||||
for (int i = 0; i < constraintColors.Count; ++i)
|
||||
{
|
||||
int color = constraintColors[i];
|
||||
int cIndex = constraintIndices[i];
|
||||
|
||||
// Add a new batch if needed:
|
||||
if (color >= stretchShearConstraintsData.GetBatchCount())
|
||||
stretchShearConstraintsData.AddBatch(new ObiStretchShearConstraintsBatch());
|
||||
|
||||
int index1 = particleIndices[cIndex];
|
||||
int index2 = particleIndices[cIndex + 1];
|
||||
|
||||
var vector = particlePositions[index2] - particlePositions[index1];
|
||||
var rest = Quaternion.LookRotation(Quaternion.Inverse(orientations[index1]) * vector);
|
||||
|
||||
stretchShearConstraintsData.batches[color].AddConstraint(new Vector2Int(index1, index2), index1, vector.magnitude, rest);
|
||||
stretchShearConstraintsData.batches[color].activeConstraintCount++;
|
||||
|
||||
if (i % 500 == 0)
|
||||
yield return new CoroutineJob.ProgressInfo("ObiBone: generating stretch constraints...", i / constraintColors.Count);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual IEnumerator CreateBendTwistConstraints(List<Vector3> particlePositions)
|
||||
{
|
||||
colorizer.Clear();
|
||||
|
||||
for (int i = 1; i < particlePositions.Count; ++i)
|
||||
{
|
||||
int parent = parentIndices[i];
|
||||
if (parent >= 0)
|
||||
{
|
||||
colorizer.AddConstraint(new[] { parent, i });
|
||||
}
|
||||
}
|
||||
|
||||
bendTwistConstraintsData = new ObiBendTwistConstraintsData();
|
||||
|
||||
List<int> constraintColors = new List<int>();
|
||||
var colorize = colorizer.Colorize("ObiBone: colorizing bend/twist constraints...", constraintColors);
|
||||
while (colorize.MoveNext())
|
||||
yield return colorize.Current;
|
||||
|
||||
var particleIndices = colorizer.particleIndices;
|
||||
var constraintIndices = colorizer.constraintIndices;
|
||||
|
||||
for (int i = 0; i < constraintColors.Count; ++i)
|
||||
{
|
||||
int color = constraintColors[i];
|
||||
int cIndex = constraintIndices[i];
|
||||
|
||||
// Add a new batch if needed:
|
||||
if (color >= bendTwistConstraintsData.GetBatchCount())
|
||||
bendTwistConstraintsData.AddBatch(new ObiBendTwistConstraintsBatch());
|
||||
|
||||
int index1 = particleIndices[cIndex];
|
||||
int index2 = particleIndices[cIndex + 1];
|
||||
|
||||
Quaternion darboux = ObiUtils.RestDarboux(orientations[index1], orientations[index2]);
|
||||
bendTwistConstraintsData.batches[color].AddConstraint(new Vector2Int(index1, index2), darboux);
|
||||
bendTwistConstraintsData.batches[color].activeConstraintCount++;
|
||||
|
||||
if (i % 500 == 0)
|
||||
yield return new CoroutineJob.ProgressInfo("ObiBone: generating bend constraints...", i / constraintColors.Count);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual IEnumerator CreateSkinConstraints(List<Vector3> particlePositions)
|
||||
{
|
||||
skinConstraintsData = new ObiSkinConstraintsData();
|
||||
ObiSkinConstraintsBatch skinBatch = new ObiSkinConstraintsBatch();
|
||||
skinConstraintsData.AddBatch(skinBatch);
|
||||
|
||||
for (int i = 0; i < particlePositions.Count; ++i)
|
||||
{
|
||||
skinBatch.AddConstraint(i, particlePositions[i], Vector3.up, 0, 0, 0, 0);
|
||||
skinBatch.activeConstraintCount++;
|
||||
|
||||
if (i % 500 == 0)
|
||||
yield return new CoroutineJob.ProgressInfo("ObiCloth: generating skin constraints...", i / (float)particlePositions.Count);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1df6a43b2a0884e8da322992da465f32
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: d0123218ec6144d0983c099fc7339924, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
281
Assets/Obi/Scripts/RopeAndRod/Blueprints/ObiRodBlueprint.cs
Normal file
281
Assets/Obi/Scripts/RopeAndRod/Blueprints/ObiRodBlueprint.cs
Normal file
@@ -0,0 +1,281 @@
|
||||
using System.Collections;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
|
||||
[CreateAssetMenu(fileName = "rod blueprint", menuName = "Obi/Rod Blueprint", order = 141)]
|
||||
public class ObiRodBlueprint : ObiRopeBlueprintBase
|
||||
{
|
||||
|
||||
public bool keepInitialShape = true;
|
||||
|
||||
public const float DEFAULT_PARTICLE_MASS = 0.1f;
|
||||
public const float DEFAULT_PARTICLE_ROTATIONAL_MASS = 0.01f;
|
||||
|
||||
|
||||
protected override IEnumerator Initialize()
|
||||
{
|
||||
|
||||
if (path.ControlPointCount < 2)
|
||||
{
|
||||
ClearParticleGroups();
|
||||
path.InsertControlPoint(0, Vector3.left, Vector3.left * 0.25f, Vector3.right * 0.25f, Vector3.up, DEFAULT_PARTICLE_MASS, DEFAULT_PARTICLE_ROTATIONAL_MASS, 1, ObiUtils.MakeFilter(ObiUtils.CollideWithEverything, 1), Color.white, "control point");
|
||||
path.InsertControlPoint(1, Vector3.right, Vector3.left * 0.25f, Vector3.right * 0.25f, Vector3.up, DEFAULT_PARTICLE_MASS, DEFAULT_PARTICLE_ROTATIONAL_MASS, 1, ObiUtils.MakeFilter(ObiUtils.CollideWithEverything, 1), Color.white, "control point");
|
||||
}
|
||||
|
||||
path.RecalculateLenght(Matrix4x4.identity, 0.00001f, 7);
|
||||
|
||||
List<Vector3> particlePositions = new List<Vector3>();
|
||||
List<Vector3> particleNormals = new List<Vector3>();
|
||||
List<float> particleThicknesses = new List<float>();
|
||||
List<float> particleInvMasses = new List<float>();
|
||||
List<float> particleInvRotationalMasses = new List<float>();
|
||||
List<int> particleFilters = new List<int>();
|
||||
List<Color> particleColors = new List<Color>();
|
||||
|
||||
// In case the path is open, add a first particle. In closed paths, the last particle is also the first one.
|
||||
if (!path.Closed)
|
||||
{
|
||||
particlePositions.Add(path.points.GetPositionAtMu(path.Closed, 0));
|
||||
particleNormals.Add(path.normals.GetAtMu(path.Closed, 0));
|
||||
particleThicknesses.Add(path.thicknesses.GetAtMu(path.Closed, 0));
|
||||
particleInvMasses.Add(ObiUtils.MassToInvMass(path.masses.GetAtMu(path.Closed, 0)));
|
||||
particleInvRotationalMasses.Add(ObiUtils.MassToInvMass(path.rotationalMasses.GetAtMu(path.Closed, 0)));
|
||||
particleFilters.Add(path.filters.GetAtMu(path.Closed, 0));
|
||||
particleColors.Add(path.colors.GetAtMu(path.Closed, 0));
|
||||
}
|
||||
|
||||
// Create a particle group for the first control point:
|
||||
groups[0].particleIndices.Clear();
|
||||
groups[0].particleIndices.Add(0);
|
||||
|
||||
ReadOnlyCollection<float> lengthTable = path.ArcLengthTable;
|
||||
int spans = path.GetSpanCount();
|
||||
|
||||
for (int i = 0; i < spans; i++)
|
||||
{
|
||||
int firstArcLengthSample = i * (path.ArcLengthSamples + 1);
|
||||
int lastArcLengthSample = (i + 1) * (path.ArcLengthSamples + 1);
|
||||
|
||||
float upToSpanLength = lengthTable[firstArcLengthSample];
|
||||
float spanLength = lengthTable[lastArcLengthSample] - upToSpanLength;
|
||||
|
||||
int particlesInSpan = 1 + Mathf.FloorToInt(spanLength / thickness * resolution);
|
||||
float distance = spanLength / particlesInSpan;
|
||||
|
||||
for (int j = 0; j < particlesInSpan; ++j)
|
||||
{
|
||||
float mu = path.GetMuAtLenght(upToSpanLength + distance * (j + 1));
|
||||
particlePositions.Add(path.points.GetPositionAtMu(path.Closed, mu));
|
||||
particleNormals.Add(path.normals.GetAtMu(path.Closed, mu));
|
||||
particleThicknesses.Add(path.thicknesses.GetAtMu(path.Closed, mu));
|
||||
particleInvMasses.Add(ObiUtils.MassToInvMass(path.masses.GetAtMu(path.Closed, mu)));
|
||||
particleInvRotationalMasses.Add(ObiUtils.MassToInvMass(path.rotationalMasses.GetAtMu(path.Closed, mu)));
|
||||
particleFilters.Add(path.filters.GetAtMu(path.Closed, mu));
|
||||
particleColors.Add(path.colors.GetAtMu(path.Closed, mu));
|
||||
}
|
||||
|
||||
// Create a particle group for each control point:
|
||||
if (!(path.Closed && i == spans - 1))
|
||||
{
|
||||
groups[i + 1].particleIndices.Clear();
|
||||
groups[i + 1].particleIndices.Add(particlePositions.Count - 1);
|
||||
}
|
||||
|
||||
if (i % 100 == 0)
|
||||
yield return new CoroutineJob.ProgressInfo("ObiRope: generating particles...", i / (float)spans);
|
||||
}
|
||||
|
||||
m_ActiveParticleCount = particlePositions.Count;
|
||||
totalParticles = m_ActiveParticleCount;
|
||||
|
||||
int numSegments = m_ActiveParticleCount - (path.Closed ? 0 : 1);
|
||||
if (numSegments > 0)
|
||||
m_InterParticleDistance = path.Length / (float)numSegments;
|
||||
else
|
||||
m_InterParticleDistance = 0;
|
||||
|
||||
positions = new Vector3[totalParticles];
|
||||
orientations = new Quaternion[totalParticles];
|
||||
velocities = new Vector3[totalParticles];
|
||||
angularVelocities = new Vector3[totalParticles];
|
||||
invMasses = new float[totalParticles];
|
||||
invRotationalMasses = new float[totalParticles];
|
||||
principalRadii = new Vector3[totalParticles];
|
||||
filters = new int[totalParticles];
|
||||
restPositions = new Vector4[totalParticles];
|
||||
restOrientations = new Quaternion[totalParticles];
|
||||
colors = new Color[totalParticles];
|
||||
restLengths = new float[totalParticles];
|
||||
|
||||
for (int i = 0; i < m_ActiveParticleCount; i++)
|
||||
{
|
||||
invMasses[i] = particleInvMasses[i];
|
||||
invRotationalMasses[i] = particleInvRotationalMasses[i];
|
||||
positions[i] = particlePositions[i];
|
||||
restPositions[i] = positions[i];
|
||||
restPositions[i][3] = 1; // activate rest position.
|
||||
principalRadii[i] = Vector3.one * particleThicknesses[i] * thickness;
|
||||
filters[i] = particleFilters[i];
|
||||
colors[i] = particleColors[i];
|
||||
|
||||
if (i % 100 == 0)
|
||||
yield return new CoroutineJob.ProgressInfo("ObiRod: generating particles...", i / (float)m_ActiveParticleCount);
|
||||
}
|
||||
|
||||
// Create edge simplices:
|
||||
CreateSimplices(numSegments);
|
||||
|
||||
// Create distance constraints for the total number of particles, but only activate for the used ones.
|
||||
IEnumerator dc = CreateStretchShearConstraints(particleNormals);
|
||||
|
||||
while (dc.MoveNext())
|
||||
yield return dc.Current;
|
||||
|
||||
// Create bending constraints:
|
||||
IEnumerator bc = CreateBendTwistConstraints();
|
||||
|
||||
while (bc.MoveNext())
|
||||
yield return bc.Current;
|
||||
|
||||
// Create chain constraints:
|
||||
IEnumerator cc = CreateChainConstraints();
|
||||
|
||||
while (cc.MoveNext())
|
||||
yield return cc.Current;
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected virtual IEnumerator CreateStretchShearConstraints(List<Vector3> particleNormals)
|
||||
{
|
||||
stretchShearConstraintsData = new ObiStretchShearConstraintsData();
|
||||
|
||||
stretchShearConstraintsData.AddBatch(new ObiStretchShearConstraintsBatch());
|
||||
stretchShearConstraintsData.AddBatch(new ObiStretchShearConstraintsBatch());
|
||||
|
||||
// rotation minimizing frame:
|
||||
ObiPathFrame frame = new ObiPathFrame();
|
||||
frame.Reset();
|
||||
|
||||
for (int i = 0; i < totalParticles - 1; i++)
|
||||
{
|
||||
var batch = stretchShearConstraintsData.batches[i % 2] as ObiStretchShearConstraintsBatch;
|
||||
|
||||
Vector2Int indices = new Vector2Int(i, i + 1);
|
||||
Vector3 d = positions[indices.y] - positions[indices.x];
|
||||
restLengths[i] = d.magnitude;
|
||||
|
||||
frame.Transport(positions[indices.x], d.normalized, 0);
|
||||
|
||||
orientations[i] = Quaternion.LookRotation(frame.tangent, particleNormals[indices.x]);
|
||||
restOrientations[i] = orientations[i];
|
||||
|
||||
// Also set the orientation of the next particle. If it is not the last one, we will overwrite it.
|
||||
// This makes sure that open rods provide an orientation for their last particle (or rather, a phantom segment past the last particle).
|
||||
|
||||
orientations[indices.y] = orientations[i];
|
||||
restOrientations[indices.y] = orientations[i];
|
||||
|
||||
batch.AddConstraint(indices, indices.x, restLengths[i], Quaternion.identity);
|
||||
batch.activeConstraintCount++;
|
||||
|
||||
if (i % 500 == 0)
|
||||
yield return new CoroutineJob.ProgressInfo("ObiRod: generating structural constraints...", i / (float)(totalParticles - 1));
|
||||
|
||||
}
|
||||
|
||||
// if the path is closed, add the last, loop closing constraint to a new batch to avoid sharing particles.
|
||||
if (path.Closed)
|
||||
{
|
||||
var loopClosingBatch = new ObiStretchShearConstraintsBatch();
|
||||
stretchShearConstraintsData.AddBatch(loopClosingBatch);
|
||||
|
||||
Vector2Int indices = new Vector2Int(m_ActiveParticleCount - 1, 0);
|
||||
Vector3 d = positions[indices.y] - positions[indices.x];
|
||||
restLengths[m_ActiveParticleCount - 2] = d.magnitude;
|
||||
|
||||
frame.Transport(positions[indices.x], d.normalized, 0);
|
||||
|
||||
orientations[m_ActiveParticleCount - 1] = Quaternion.LookRotation(frame.tangent, particleNormals[indices.x]);
|
||||
restOrientations[m_ActiveParticleCount - 1] = orientations[m_ActiveParticleCount - 1];
|
||||
|
||||
loopClosingBatch.AddConstraint(indices, indices.x, restLengths[m_ActiveParticleCount - 2], Quaternion.identity);
|
||||
loopClosingBatch.activeConstraintCount++;
|
||||
}
|
||||
|
||||
// Recalculate rest length:
|
||||
m_RestLength = 0;
|
||||
foreach (float length in restLengths)
|
||||
m_RestLength += length;
|
||||
}
|
||||
|
||||
protected virtual IEnumerator CreateBendTwistConstraints()
|
||||
{
|
||||
bendTwistConstraintsData = new ObiBendTwistConstraintsData();
|
||||
|
||||
// Add two batches:
|
||||
bendTwistConstraintsData.AddBatch(new ObiBendTwistConstraintsBatch());
|
||||
bendTwistConstraintsData.AddBatch(new ObiBendTwistConstraintsBatch());
|
||||
|
||||
// the last bend constraint couples the last segment and a phantom segment past the last particle.
|
||||
for (int i = 0; i < totalParticles - 1; i++)
|
||||
{
|
||||
|
||||
var batch = bendTwistConstraintsData.batches[i % 2] as ObiBendTwistConstraintsBatch;
|
||||
|
||||
Vector2Int indices = new Vector2Int(i, i + 1);
|
||||
|
||||
Quaternion darboux = keepInitialShape ? ObiUtils.RestDarboux(orientations[indices.x], orientations[indices.y]) : Quaternion.identity;
|
||||
batch.AddConstraint(indices, darboux);
|
||||
batch.activeConstraintCount++;
|
||||
|
||||
if (i % 500 == 0)
|
||||
yield return new CoroutineJob.ProgressInfo("ObiRod: generating structural constraints...", i / (float)(totalParticles - 1));
|
||||
|
||||
}
|
||||
|
||||
// if the path is closed, add the last, loop closing constraints to a new batch to avoid sharing particles.
|
||||
if (path.Closed)
|
||||
{
|
||||
var loopClosingBatch = new ObiBendTwistConstraintsBatch();
|
||||
bendTwistConstraintsData.AddBatch(loopClosingBatch);
|
||||
|
||||
Vector2Int indices = new Vector2Int(m_ActiveParticleCount - 1, 0);
|
||||
Quaternion darboux = keepInitialShape ? ObiUtils.RestDarboux(orientations[indices.x], orientations[indices.y]) : Quaternion.identity;
|
||||
loopClosingBatch.AddConstraint(indices, darboux);
|
||||
loopClosingBatch.activeConstraintCount++;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual IEnumerator CreateChainConstraints()
|
||||
{
|
||||
chainConstraintsData = new ObiChainConstraintsData();
|
||||
|
||||
// Add a single batch:
|
||||
var batch = new ObiChainConstraintsBatch();
|
||||
chainConstraintsData.AddBatch(batch);
|
||||
|
||||
int[] indices = new int[m_ActiveParticleCount + (path.Closed ? 1 : 0)];
|
||||
|
||||
for (int i = 0; i < m_ActiveParticleCount; ++i)
|
||||
indices[i] = i;
|
||||
|
||||
// Add the first particle as the last index of the chain, if closed.
|
||||
if (path.Closed)
|
||||
indices[m_ActiveParticleCount] = 0;
|
||||
|
||||
// TODO: variable distance between particles:
|
||||
batch.AddConstraint(indices, m_InterParticleDistance, 1, 1);
|
||||
batch.activeConstraintCount++;
|
||||
|
||||
yield return 0;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e028b099aa5d14e399bc3d87df5b7737
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: d0123218ec6144d0983c099fc7339924, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
228
Assets/Obi/Scripts/RopeAndRod/Blueprints/ObiRopeBlueprint.cs
Normal file
228
Assets/Obi/Scripts/RopeAndRod/Blueprints/ObiRopeBlueprint.cs
Normal file
@@ -0,0 +1,228 @@
|
||||
using System.Collections;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
[CreateAssetMenu(fileName = "rope blueprint", menuName = "Obi/Rope Blueprint", order = 140)]
|
||||
public class ObiRopeBlueprint : ObiRopeBlueprintBase
|
||||
{
|
||||
|
||||
public int pooledParticles = 100;
|
||||
|
||||
public const float DEFAULT_PARTICLE_MASS = 0.1f;
|
||||
|
||||
protected override IEnumerator Initialize()
|
||||
{
|
||||
if (path.ControlPointCount < 2)
|
||||
{
|
||||
ClearParticleGroups();
|
||||
path.InsertControlPoint(0, Vector3.left, Vector3.left * 0.25f, Vector3.right * 0.25f, Vector3.up, DEFAULT_PARTICLE_MASS, 1, 1, ObiUtils.MakeFilter(ObiUtils.CollideWithEverything,1), Color.white, "control point");
|
||||
path.InsertControlPoint(1, Vector3.right, Vector3.left * 0.25f, Vector3.right * 0.25f, Vector3.up, DEFAULT_PARTICLE_MASS, 1, 1, ObiUtils.MakeFilter(ObiUtils.CollideWithEverything, 1), Color.white, "control point");
|
||||
}
|
||||
|
||||
path.RecalculateLenght(Matrix4x4.identity, 0.00001f, 7);
|
||||
|
||||
List<Vector3> particlePositions = new List<Vector3>();
|
||||
List<float> particleThicknesses = new List<float>();
|
||||
List<float> particleInvMasses = new List<float>();
|
||||
List<int> particleFilters = new List<int>();
|
||||
List<Color> particleColors = new List<Color>();
|
||||
|
||||
// In case the path is open, add a first particle. In closed paths, the last particle is also the first one.
|
||||
if (!path.Closed)
|
||||
{
|
||||
particlePositions.Add(path.points.GetPositionAtMu(path.Closed, 0));
|
||||
particleThicknesses.Add(path.thicknesses.GetAtMu(path.Closed, 0));
|
||||
particleInvMasses.Add(ObiUtils.MassToInvMass(path.masses.GetAtMu(path.Closed, 0)));
|
||||
particleFilters.Add(path.filters.GetAtMu(path.Closed, 0));
|
||||
particleColors.Add(path.colors.GetAtMu(path.Closed, 0));
|
||||
}
|
||||
|
||||
// Create a particle group for the first control point:
|
||||
groups[0].particleIndices.Clear();
|
||||
groups[0].particleIndices.Add(0);
|
||||
|
||||
ReadOnlyCollection<float> lengthTable = path.ArcLengthTable;
|
||||
int spans = path.GetSpanCount();
|
||||
|
||||
for (int i = 0; i < spans; i++)
|
||||
{
|
||||
int firstArcLengthSample = i * (path.ArcLengthSamples + 1);
|
||||
int lastArcLengthSample = (i + 1) * (path.ArcLengthSamples + 1);
|
||||
|
||||
float upToSpanLength = lengthTable[firstArcLengthSample];
|
||||
float spanLength = lengthTable[lastArcLengthSample] - upToSpanLength;
|
||||
|
||||
int particlesInSpan = 1 + Mathf.FloorToInt(spanLength / thickness * resolution);
|
||||
float distance = spanLength / particlesInSpan;
|
||||
|
||||
for (int j = 0; j < particlesInSpan; ++j)
|
||||
{
|
||||
float mu = path.GetMuAtLenght(upToSpanLength + distance * (j + 1));
|
||||
particlePositions.Add(path.points.GetPositionAtMu(path.Closed, mu));
|
||||
particleThicknesses.Add(path.thicknesses.GetAtMu(path.Closed, mu));
|
||||
particleInvMasses.Add(ObiUtils.MassToInvMass(path.masses.GetAtMu(path.Closed, mu)));
|
||||
particleFilters.Add(path.filters.GetAtMu(path.Closed, mu));
|
||||
particleColors.Add(path.colors.GetAtMu(path.Closed, mu));
|
||||
}
|
||||
|
||||
// Create a particle group for each control point:
|
||||
if (!(path.Closed && i == spans - 1))
|
||||
{
|
||||
groups[i + 1].particleIndices.Clear();
|
||||
groups[i + 1].particleIndices.Add(particlePositions.Count - 1);
|
||||
}
|
||||
|
||||
if (i % 100 == 0)
|
||||
yield return new CoroutineJob.ProgressInfo("ObiRope: generating particles...", i / (float)spans);
|
||||
}
|
||||
|
||||
m_ActiveParticleCount = particlePositions.Count;
|
||||
totalParticles = m_ActiveParticleCount + pooledParticles;
|
||||
|
||||
int numSegments = m_ActiveParticleCount - (path.Closed ? 0 : 1);
|
||||
if (numSegments > 0)
|
||||
m_InterParticleDistance = path.Length / (float)numSegments;
|
||||
else
|
||||
m_InterParticleDistance = 0;
|
||||
|
||||
positions = new Vector3[totalParticles];
|
||||
restPositions = new Vector4[totalParticles];
|
||||
velocities = new Vector3[totalParticles];
|
||||
invMasses = new float[totalParticles];
|
||||
principalRadii = new Vector3[totalParticles];
|
||||
filters = new int[totalParticles];
|
||||
colors = new Color[totalParticles];
|
||||
restLengths = new float[totalParticles];
|
||||
|
||||
for (int i = 0; i < m_ActiveParticleCount; i++)
|
||||
{
|
||||
invMasses[i] = particleInvMasses[i];
|
||||
positions[i] = particlePositions[i];
|
||||
restPositions[i] = positions[i];
|
||||
restPositions[i][3] = 1; // activate rest position.
|
||||
principalRadii[i] = Vector3.one * particleThicknesses[i] * thickness;
|
||||
filters[i] = particleFilters[i];
|
||||
colors[i] = particleColors[i];
|
||||
|
||||
if (i % 100 == 0)
|
||||
yield return new CoroutineJob.ProgressInfo("ObiRope: generating particles...", i / (float)m_ActiveParticleCount);
|
||||
}
|
||||
|
||||
// Create edge simplices:
|
||||
CreateSimplices(numSegments);
|
||||
|
||||
//Create distance constraints for the total number of particles, but only activate for the used ones.
|
||||
IEnumerator dc = CreateDistanceConstraints();
|
||||
|
||||
while (dc.MoveNext())
|
||||
yield return dc.Current;
|
||||
|
||||
//Create bending constraints:
|
||||
IEnumerator bc = CreateBendingConstraints();
|
||||
|
||||
while (bc.MoveNext())
|
||||
yield return bc.Current;
|
||||
|
||||
// Recalculate rest length:
|
||||
m_RestLength = 0;
|
||||
foreach (float length in restLengths)
|
||||
m_RestLength += length;
|
||||
|
||||
}
|
||||
|
||||
protected virtual IEnumerator CreateDistanceConstraints()
|
||||
{
|
||||
distanceConstraintsData = new ObiDistanceConstraintsData();
|
||||
|
||||
// Add two batches: for even and odd constraints:
|
||||
distanceConstraintsData.AddBatch(new ObiDistanceConstraintsBatch());
|
||||
distanceConstraintsData.AddBatch(new ObiDistanceConstraintsBatch());
|
||||
|
||||
for (int i = 0; i < totalParticles - 1; i++)
|
||||
{
|
||||
var batch = distanceConstraintsData.batches[i % 2] as ObiDistanceConstraintsBatch;
|
||||
|
||||
if (i < m_ActiveParticleCount - 1)
|
||||
{
|
||||
Vector2Int indices = new Vector2Int(i, i + 1);
|
||||
restLengths[i] = Vector3.Distance(positions[indices.x], positions[indices.y]);
|
||||
batch.AddConstraint(indices, restLengths[i]);
|
||||
batch.activeConstraintCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
restLengths[i] = m_InterParticleDistance;
|
||||
batch.AddConstraint(Vector2Int.zero, 0);
|
||||
}
|
||||
|
||||
if (i % 500 == 0)
|
||||
yield return new CoroutineJob.ProgressInfo("ObiRope: generating structural constraints...", i / (float)(totalParticles - 1));
|
||||
|
||||
}
|
||||
|
||||
// if the path is closed, add the last, loop closing constraint to a new batch to avoid sharing particles.
|
||||
if (path.Closed)
|
||||
{
|
||||
var loopClosingBatch = new ObiDistanceConstraintsBatch();
|
||||
distanceConstraintsData.AddBatch(loopClosingBatch);
|
||||
|
||||
Vector2Int indices = new Vector2Int(m_ActiveParticleCount - 1, 0);
|
||||
restLengths[m_ActiveParticleCount - 2] = Vector3.Distance(positions[indices.x], positions[indices.y]);
|
||||
loopClosingBatch.AddConstraint(indices, restLengths[m_ActiveParticleCount - 2]);
|
||||
loopClosingBatch.activeConstraintCount++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected virtual IEnumerator CreateBendingConstraints()
|
||||
{
|
||||
bendConstraintsData = new ObiBendConstraintsData();
|
||||
|
||||
// Add three batches:
|
||||
bendConstraintsData.AddBatch(new ObiBendConstraintsBatch());
|
||||
bendConstraintsData.AddBatch(new ObiBendConstraintsBatch());
|
||||
bendConstraintsData.AddBatch(new ObiBendConstraintsBatch());
|
||||
|
||||
for (int i = 0; i < totalParticles - 2; i++)
|
||||
{
|
||||
var batch = bendConstraintsData.batches[i % 3] as ObiBendConstraintsBatch;
|
||||
|
||||
Vector3Int indices = new Vector3Int(i, i + 2, i + 1);
|
||||
float restBend = ObiUtils.RestBendingConstraint(restPositions[indices[0]], restPositions[indices[1]], restPositions[indices[2]]);
|
||||
batch.AddConstraint(indices, restBend);
|
||||
|
||||
if (i < m_ActiveParticleCount - 2)
|
||||
batch.activeConstraintCount++;
|
||||
|
||||
if (i % 500 == 0)
|
||||
yield return new CoroutineJob.ProgressInfo("ObiRope: generating structural constraints...", i / (float)(totalParticles - 2));
|
||||
|
||||
}
|
||||
|
||||
// if the path is closed, add the last, loop closing constraints to a new batch to avoid sharing particles.
|
||||
if (path.Closed)
|
||||
{
|
||||
var loopClosingBatch = new ObiBendConstraintsBatch();
|
||||
bendConstraintsData.AddBatch(loopClosingBatch);
|
||||
|
||||
Vector3Int indices = new Vector3Int(m_ActiveParticleCount - 2, 0, m_ActiveParticleCount - 1);
|
||||
loopClosingBatch.AddConstraint(indices, 0);
|
||||
loopClosingBatch.activeConstraintCount++;
|
||||
|
||||
var loopClosingBatch2 = new ObiBendConstraintsBatch();
|
||||
bendConstraintsData.AddBatch(loopClosingBatch2);
|
||||
|
||||
indices = new Vector3Int(m_ActiveParticleCount - 1, 1, 0);
|
||||
loopClosingBatch2.AddConstraint(indices, 0);
|
||||
loopClosingBatch2.activeConstraintCount++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f7e67b5626124d0db9886e6cd2aacff
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: d0123218ec6144d0983c099fc7339924, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,77 @@
|
||||
using System.Collections;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
public abstract class ObiRopeBlueprintBase : ObiActorBlueprint
|
||||
{
|
||||
[HideInInspector] [SerializeField] public ObiPath path = new ObiPath();
|
||||
public float thickness = 0.1f;
|
||||
|
||||
[Range(0, 1)]
|
||||
public float resolution = 1;
|
||||
|
||||
[HideInInspector] [SerializeField] protected float m_InterParticleDistance;
|
||||
[HideInInspector] [SerializeField] protected int totalParticles;
|
||||
[HideInInspector] [SerializeField] protected float m_RestLength;
|
||||
|
||||
[HideInInspector] public float[] restLengths;
|
||||
|
||||
public float interParticleDistance
|
||||
{
|
||||
get { return m_InterParticleDistance; }
|
||||
}
|
||||
|
||||
public float restLength
|
||||
{
|
||||
get { return m_RestLength; }
|
||||
}
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
path.OnPathChanged.AddListener(GenerateImmediate);
|
||||
path.OnControlPointAdded.AddListener(ControlPointAdded);
|
||||
path.OnControlPointRemoved.AddListener(ControlPointRemoved);
|
||||
path.OnControlPointRenamed.AddListener(ControlPointRenamed);
|
||||
}
|
||||
|
||||
public void OnDisable()
|
||||
{
|
||||
path.OnPathChanged.RemoveAllListeners();
|
||||
path.OnControlPointAdded.RemoveAllListeners();
|
||||
path.OnControlPointRemoved.RemoveAllListeners();
|
||||
path.OnControlPointRenamed.RemoveAllListeners();
|
||||
}
|
||||
|
||||
protected void ControlPointAdded(int index)
|
||||
{
|
||||
var group = InsertNewParticleGroup(path.GetName(index), index);
|
||||
}
|
||||
|
||||
protected void ControlPointRenamed(int index)
|
||||
{
|
||||
SetParticleGroupName(index, path.GetName(index));
|
||||
}
|
||||
|
||||
protected void ControlPointRemoved(int index)
|
||||
{
|
||||
RemoveParticleGroupAt(index);
|
||||
}
|
||||
|
||||
protected void CreateSimplices(int numSegments)
|
||||
{
|
||||
edges = new int[numSegments * 2];
|
||||
for (int i = 0; i < numSegments; ++i)
|
||||
{
|
||||
edges[i * 2] = i % totalParticles;
|
||||
edges[i * 2 + 1] = (i + 1) % totalParticles;
|
||||
}
|
||||
}
|
||||
|
||||
protected override IEnumerator Initialize() { yield return null; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4abe69b6abd2c4b0e8a621b8fba92dc9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Obi/Scripts/RopeAndRod/DataStructures.meta
Normal file
8
Assets/Obi/Scripts/RopeAndRod/DataStructures.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fa3e31464f45342fc90509e4fcb00976
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
[CreateAssetMenu(fileName = "rope section", menuName = "Obi/Rope Section", order = 142)]
|
||||
public class ObiRopeSection : ScriptableObject
|
||||
{
|
||||
[HideInInspector] public List<Vector2> vertices;
|
||||
public int snapX = 0;
|
||||
public int snapY = 0;
|
||||
|
||||
public int Segments{
|
||||
get{return vertices.Count-1;}
|
||||
}
|
||||
|
||||
public void OnEnable(){
|
||||
|
||||
if (vertices == null){
|
||||
vertices = new List<Vector2>();
|
||||
CirclePreset(8);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void CirclePreset(int segments){
|
||||
|
||||
vertices.Clear();
|
||||
|
||||
for (int j = 0; j <= segments; ++j){
|
||||
float angle = 2 * Mathf.PI / segments * j;
|
||||
vertices.Add(Mathf.Cos(angle)*Vector2.right + Mathf.Sin(angle)*Vector2.up);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Snaps a float value to the nearest multiple of snapInterval.
|
||||
*/
|
||||
public static int SnapTo(float val, int snapInterval, int threshold){
|
||||
int intVal = (int) val;
|
||||
if (snapInterval <= 0)
|
||||
return intVal;
|
||||
int under = Mathf.FloorToInt(val / snapInterval) * snapInterval;
|
||||
int over = under + snapInterval;
|
||||
if (intVal - under < threshold) return under;
|
||||
if (over - intVal < threshold) return over;
|
||||
return intVal;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee7737c43f5734f87be9e49d1bbfba78
|
||||
labels:
|
||||
- ObiRope
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: fdb742a900c8d453ea5ce5027e80ad00, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,16 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
// Abstracts rope topolgy as a list of elements.
|
||||
[System.Serializable]
|
||||
public class ObiStructuralElement
|
||||
{
|
||||
public int particle1;
|
||||
public int particle2;
|
||||
public float restLength;
|
||||
public float constraintForce;
|
||||
public float tearResistance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2aecff76dda4241c5a0c15349e2419d3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Obi/Scripts/RopeAndRod/DataStructures/Path.meta
Normal file
8
Assets/Obi/Scripts/RopeAndRod/DataStructures/Path.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 75e833f2b944640a5934c15fbec8ac00
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5172d3d605ca94f2db0ad73eaecbc4d4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,12 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
[Serializable]
|
||||
public class ObiColorDataChannel : ObiPathDataChannelIdentity<Color>
|
||||
{
|
||||
public ObiColorDataChannel() : base(new ObiColorInterpolator3D()) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4078c779a63154dddac14f5a1e65a692
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
[Serializable]
|
||||
public class ObiPhaseDataChannel : ObiPathDataChannelIdentity<int>
|
||||
{
|
||||
public ObiPhaseDataChannel() : base(new ObiConstantInterpolator()) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9cfdcf019ed7249799993815bfc3c0bd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,12 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
[Serializable]
|
||||
public class ObiMassDataChannel : ObiPathDataChannelIdentity<float>
|
||||
{
|
||||
public ObiMassDataChannel() : base(new ObiCatmullRomInterpolator()) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e55025008fa8f4bccae82dbaded0f019
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,12 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
[Serializable]
|
||||
public class ObiNormalDataChannel : ObiPathDataChannelIdentity<Vector3>
|
||||
{
|
||||
public ObiNormalDataChannel() : base(new ObiCatmullRomInterpolator3D()) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4721087e73d0d422ab48ad62a2fd3d3b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,88 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
public interface IObiPathDataChannel
|
||||
{
|
||||
int Count { get; }
|
||||
bool Dirty { get; }
|
||||
void Clean();
|
||||
void RemoveAt(int index);
|
||||
}
|
||||
|
||||
public abstract class ObiPathDataChannel<T,U> : IObiPathDataChannel
|
||||
{
|
||||
protected ObiInterpolator<U> interpolator;
|
||||
protected bool dirty = false;
|
||||
public List<T> data = new List<T>();
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { return data.Count; }
|
||||
}
|
||||
|
||||
public bool Dirty
|
||||
{
|
||||
get { return dirty; }
|
||||
}
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
public ObiPathDataChannel(ObiInterpolator<U> interpolator)
|
||||
{
|
||||
this.interpolator = interpolator;
|
||||
}
|
||||
|
||||
public T this[int i]
|
||||
{
|
||||
get { return data[i]; }
|
||||
set { data[i] = value; dirty = true; }
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
data.RemoveAt(index);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
public U Evaluate(U v0, U v1, U v2, U v3, float mu)
|
||||
{
|
||||
return interpolator.Evaluate(v0, v1, v2, v3, mu);
|
||||
}
|
||||
public U EvaluateFirstDerivative(U v0, U v1, U v2, U v3, float mu)
|
||||
{
|
||||
return interpolator.EvaluateFirstDerivative(v0, v1, v2, v3, mu);
|
||||
}
|
||||
public U EvaluateSecondDerivative(U v0, U v1, U v2, U v3, float mu)
|
||||
{
|
||||
return interpolator.EvaluateSecondDerivative(v0, v1, v2, v3, mu);
|
||||
}
|
||||
|
||||
public int GetSpanCount(bool closed)
|
||||
{
|
||||
int cps = Count;
|
||||
if (cps < 2)
|
||||
return 0;
|
||||
|
||||
return closed ? cps : cps - 1;
|
||||
}
|
||||
|
||||
public int GetSpanControlPointAtMu(bool closed, float mu, out float spanMu)
|
||||
{
|
||||
|
||||
int spanCount = GetSpanCount(closed);
|
||||
spanMu = mu * spanCount;
|
||||
int i = (mu >= 1f) ? (spanCount - 1) : (int)spanMu;
|
||||
spanMu -= i;
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 43ab47ea5c62b45248edce6a9d099626
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,94 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
public abstract class ObiPathDataChannelIdentity<T> : ObiPathDataChannel<T,T>
|
||||
{
|
||||
public ObiPathDataChannelIdentity(ObiInterpolator<T> interpolator) : base(interpolator)
|
||||
{
|
||||
}
|
||||
|
||||
public T GetFirstDerivative(int index)
|
||||
{
|
||||
int nextCP = (index + 1) % Count;
|
||||
|
||||
return EvaluateFirstDerivative(this[index],
|
||||
this[index],
|
||||
this[nextCP],
|
||||
this[nextCP], 0);
|
||||
}
|
||||
|
||||
public T GetSecondDerivative(int index)
|
||||
{
|
||||
int nextCP = (index + 1) % Count;
|
||||
|
||||
return EvaluateSecondDerivative(this[index],
|
||||
this[index],
|
||||
this[nextCP],
|
||||
this[nextCP], 0);
|
||||
}
|
||||
|
||||
public T GetAtMu(bool closed, float mu)
|
||||
{
|
||||
int cps = Count;
|
||||
if (cps >= 2)
|
||||
{
|
||||
float p;
|
||||
int i = GetSpanControlPointAtMu(closed, mu, out p);
|
||||
int nextCP = (i + 1) % cps;
|
||||
|
||||
return Evaluate(this[i],
|
||||
this[i],
|
||||
this[nextCP],
|
||||
this[nextCP], p);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Cannot get property in path because it has less than 2 control points.");
|
||||
}
|
||||
}
|
||||
|
||||
public T GetFirstDerivativeAtMu(bool closed, float mu)
|
||||
{
|
||||
int cps = Count;
|
||||
if (cps >= 2)
|
||||
{
|
||||
float p;
|
||||
int i = GetSpanControlPointAtMu(closed, mu, out p);
|
||||
int nextCP = (i + 1) % cps;
|
||||
|
||||
return EvaluateFirstDerivative(this[i],
|
||||
this[i],
|
||||
this[nextCP],
|
||||
this[nextCP], p);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Cannot get derivative in path because it has less than 2 control points.");
|
||||
}
|
||||
}
|
||||
|
||||
public T GetSecondDerivativeAtMu(bool closed, float mu)
|
||||
{
|
||||
int cps = Count;
|
||||
if (cps >= 2)
|
||||
{
|
||||
float p;
|
||||
int i = GetSpanControlPointAtMu(closed, mu, out p);
|
||||
int nextCP = (i + 1) % cps;
|
||||
|
||||
return EvaluateSecondDerivative(this[i],
|
||||
this[i],
|
||||
this[nextCP],
|
||||
this[nextCP], p);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Cannot get second derivative in path because it has less than 2 control points.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 61b5c82c04eb14f1999e94baa1f073bc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,125 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
[Serializable]
|
||||
public class ObiPointsDataChannel : ObiPathDataChannel<ObiWingedPoint, Vector3>
|
||||
{
|
||||
|
||||
public ObiPointsDataChannel() : base(new ObiCatmullRomInterpolator3D()) { }
|
||||
|
||||
public Vector3 GetTangent(int index)
|
||||
{
|
||||
int nextCP = (index + 1) % Count;
|
||||
|
||||
var wp1 = this[index];
|
||||
var wp2 = this[nextCP];
|
||||
|
||||
return EvaluateFirstDerivative(wp1.position,
|
||||
wp1.outTangentEndpoint,
|
||||
wp2.inTangentEndpoint,
|
||||
wp2.position, 0);
|
||||
}
|
||||
|
||||
public Vector3 GetAcceleration(int index)
|
||||
{
|
||||
int nextCP = (index + 1) % Count;
|
||||
|
||||
var wp1 = this[index];
|
||||
var wp2 = this[nextCP];
|
||||
|
||||
return EvaluateSecondDerivative(wp1.position,
|
||||
wp1.outTangentEndpoint,
|
||||
wp2.inTangentEndpoint,
|
||||
wp2.position, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns spline position at time mu, with 0<=mu<=1 where 0 is the start of the spline
|
||||
* and 1 is the end.
|
||||
*/
|
||||
public Vector3 GetPositionAtMu(bool closed,float mu)
|
||||
{
|
||||
int cps = Count;
|
||||
if (cps >= 2)
|
||||
{
|
||||
|
||||
float p;
|
||||
int i = GetSpanControlPointAtMu(closed, mu, out p);
|
||||
int nextCP = (i + 1) % cps;
|
||||
|
||||
var wp1 = this[i];
|
||||
var wp2 = this[nextCP];
|
||||
|
||||
return Evaluate(wp1.position,
|
||||
wp1.outTangentEndpoint,
|
||||
wp2.inTangentEndpoint,
|
||||
wp2.position, p);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Cannot get position in path because it has zero control points.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns normal tangent vector at time mu, with 0<=mu<=1 where 0 is the start of the spline
|
||||
* and 1 is the end.
|
||||
*/
|
||||
public Vector3 GetTangentAtMu(bool closed, float mu)
|
||||
{
|
||||
|
||||
int cps = Count;
|
||||
if (cps >= 2)
|
||||
{
|
||||
float p;
|
||||
int i = GetSpanControlPointAtMu(closed, mu, out p);
|
||||
int nextCP = (i + 1) % cps;
|
||||
|
||||
var wp1 = this[i];
|
||||
var wp2 = this[nextCP];
|
||||
|
||||
return EvaluateFirstDerivative(wp1.position,
|
||||
wp1.outTangentEndpoint,
|
||||
wp2.inTangentEndpoint,
|
||||
wp2.position, p);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Cannot get derivative in path because it has less than 2 control points.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns acceleration at time mu, with 0<=mu<=1 where 0 is the start of the spline
|
||||
* and 1 is the end.
|
||||
*/
|
||||
public Vector3 GetAccelerationAtMu(bool closed, float mu)
|
||||
{
|
||||
|
||||
int cps = Count;
|
||||
if (cps >= 2)
|
||||
{
|
||||
float p;
|
||||
int i = GetSpanControlPointAtMu(closed, mu, out p);
|
||||
int nextCP = (i + 1) % cps;
|
||||
|
||||
var wp1 = this[i];
|
||||
var wp2 = this[nextCP];
|
||||
|
||||
return EvaluateSecondDerivative(wp1.position,
|
||||
wp1.outTangentEndpoint,
|
||||
wp2.inTangentEndpoint,
|
||||
wp2.position, p);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Cannot get second derivative in path because it has less than 2 control points.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d710ff448ae4e4a6f9351a678af9a8ac
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,12 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
[Serializable]
|
||||
public class ObiRotationalMassDataChannel : ObiPathDataChannelIdentity<float>
|
||||
{
|
||||
public ObiRotationalMassDataChannel() : base(new ObiCatmullRomInterpolator()) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 28c38822e5a79441a8177689f83644e8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,12 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
[Serializable]
|
||||
public class ObiThicknessDataChannel : ObiPathDataChannelIdentity<float>
|
||||
{
|
||||
public ObiThicknessDataChannel() : base(new ObiCatmullRomInterpolator()) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b414fbc6ce2db42299dc01619ed4fc97
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e85cc8f3b96cf4b4c9be1e6af0a0a99b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,49 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
public class ObiCatmullRomInterpolator : ObiInterpolator<float>
|
||||
{
|
||||
/**
|
||||
* 1D bezier spline interpolation
|
||||
*/
|
||||
public float Evaluate(float y0, float y1, float y2, float y3, float mu)
|
||||
{
|
||||
|
||||
float imu = 1 - mu;
|
||||
return imu * imu * imu * y0 +
|
||||
3f * imu * imu * mu * y1 +
|
||||
3f * imu * mu * mu * y2 +
|
||||
mu * mu * mu * y3;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 1D catmull rom spline second derivative
|
||||
*/
|
||||
public float EvaluateFirstDerivative(float y0, float y1, float y2, float y3, float mu)
|
||||
{
|
||||
|
||||
float imu = 1 - mu;
|
||||
return 3f * imu * imu * (y1 - y0) +
|
||||
6f * imu * mu * (y2 - y1) +
|
||||
3f * mu * mu * (y3 - y2);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 1D catmull rom spline second derivative
|
||||
*/
|
||||
public float EvaluateSecondDerivative(float y0, float y1, float y2, float y3, float mu)
|
||||
{
|
||||
|
||||
float imu = 1 - mu;
|
||||
return 3f * imu * imu * (y1 - y0) +
|
||||
6f * imu * mu * (y2 - y1) +
|
||||
3f * mu * mu * (y3 - y2);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 93918aac247384d8289d151d77dc4dc2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,46 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
public class ObiCatmullRomInterpolator3D : ObiInterpolator<Vector3>
|
||||
{
|
||||
private ObiCatmullRomInterpolator interpolator = new ObiCatmullRomInterpolator();
|
||||
|
||||
/**
|
||||
* 3D spline interpolation
|
||||
*/
|
||||
public Vector3 Evaluate(Vector3 y0, Vector3 y1, Vector3 y2, Vector3 y3, float mu)
|
||||
{
|
||||
|
||||
return new Vector3(interpolator.Evaluate(y0.x, y1.x, y2.x, y3.x, mu),
|
||||
interpolator.Evaluate(y0.y, y1.y, y2.y, y3.y, mu),
|
||||
interpolator.Evaluate(y0.z, y1.z, y2.z, y3.z, mu));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 3D spline first derivative
|
||||
*/
|
||||
public Vector3 EvaluateFirstDerivative(Vector3 y0, Vector3 y1, Vector3 y2, Vector3 y3, float mu)
|
||||
{
|
||||
|
||||
return new Vector3(interpolator.EvaluateFirstDerivative(y0.x, y1.x, y2.x, y3.x, mu),
|
||||
interpolator.EvaluateFirstDerivative(y0.y, y1.y, y2.y, y3.y, mu),
|
||||
interpolator.EvaluateFirstDerivative(y0.z, y1.z, y2.z, y3.z, mu));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 3D spline second derivative
|
||||
*/
|
||||
public Vector3 EvaluateSecondDerivative(Vector3 y0, Vector3 y1, Vector3 y2, Vector3 y3, float mu)
|
||||
{
|
||||
|
||||
return new Vector3(interpolator.EvaluateSecondDerivative(y0.x, y1.x, y2.x, y3.x, mu),
|
||||
interpolator.EvaluateSecondDerivative(y0.y, y1.y, y2.y, y3.y, mu),
|
||||
interpolator.EvaluateSecondDerivative(y0.z, y1.z, y2.z, y3.z, mu));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 845dbd641b61b490bb94d745b6879e97
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,49 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
public class ObiColorInterpolator3D : ObiInterpolator<Color>
|
||||
{
|
||||
private ObiCatmullRomInterpolator interpolator = new ObiCatmullRomInterpolator();
|
||||
|
||||
/**
|
||||
* 3D spline interpolation
|
||||
*/
|
||||
public Color Evaluate(Color y0, Color y1, Color y2, Color y3, float mu)
|
||||
{
|
||||
|
||||
return new Color(interpolator.Evaluate(y0.r, y1.r, y2.r, y3.r, mu),
|
||||
interpolator.Evaluate(y0.g, y1.g, y2.g, y3.g, mu),
|
||||
interpolator.Evaluate(y0.b, y1.b, y2.b, y3.b, mu),
|
||||
interpolator.Evaluate(y0.a, y1.a, y2.a, y3.a, mu));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 3D spline first derivative
|
||||
*/
|
||||
public Color EvaluateFirstDerivative(Color y0, Color y1, Color y2, Color y3, float mu)
|
||||
{
|
||||
|
||||
return new Color(interpolator.EvaluateFirstDerivative(y0.r, y1.r, y2.r, y3.r, mu),
|
||||
interpolator.EvaluateFirstDerivative(y0.g, y1.g, y2.g, y3.g, mu),
|
||||
interpolator.EvaluateFirstDerivative(y0.b, y1.b, y2.b, y3.b, mu),
|
||||
interpolator.EvaluateFirstDerivative(y0.a, y1.a, y2.a, y3.a, mu));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 3D spline second derivative
|
||||
*/
|
||||
public Color EvaluateSecondDerivative(Color y0, Color y1, Color y2, Color y3, float mu)
|
||||
{
|
||||
|
||||
return new Color(interpolator.EvaluateSecondDerivative(y0.r, y1.r, y2.r, y3.r, mu),
|
||||
interpolator.EvaluateSecondDerivative(y0.g, y1.g, y2.g, y3.g, mu),
|
||||
interpolator.EvaluateSecondDerivative(y0.b, y1.b, y2.b, y3.b, mu),
|
||||
interpolator.EvaluateSecondDerivative(y0.a, y1.a, y2.a, y3.a, mu));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c2bda61c040346db8efd82f64c2422e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,33 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
public class ObiConstantInterpolator : ObiInterpolator<int>
|
||||
{
|
||||
/**
|
||||
* constant interpolator
|
||||
*/
|
||||
public int Evaluate(int y0, int y1, int y2, int y3, float mu)
|
||||
{
|
||||
return mu < 0.5f ? y1 : y2;
|
||||
}
|
||||
|
||||
/**
|
||||
* derivative of constant value:
|
||||
*/
|
||||
public int EvaluateFirstDerivative(int y0, int y1, int y2, int y3, float mu)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* second derivative of constant value:
|
||||
*/
|
||||
public int EvaluateSecondDerivative(int y0, int y1, int y2, int y3, float mu)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 33f10aef9ed544b14a762a9c6e8bf411
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,13 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
|
||||
public interface ObiInterpolator<T>
|
||||
{
|
||||
T Evaluate(T v0, T v1, T v2, T v3, float mu);
|
||||
T EvaluateFirstDerivative(T v0, T v1, T v2, T v3, float mu);
|
||||
T EvaluateSecondDerivative(T v0, T v1, T v2, T v3, float mu);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: af25f2a4d723548bfafa551b6da56d25
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
408
Assets/Obi/Scripts/RopeAndRod/DataStructures/Path/ObiPath.cs
Normal file
408
Assets/Obi/Scripts/RopeAndRod/DataStructures/Path/ObiPath.cs
Normal file
@@ -0,0 +1,408 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
[System.Serializable]
|
||||
public class PathControlPointEvent : UnityEvent<int>
|
||||
{
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ObiPath
|
||||
{
|
||||
[HideInInspector] [SerializeField] List<string> m_Names = new List<string>();
|
||||
[HideInInspector] [SerializeField] public ObiPointsDataChannel m_Points = new ObiPointsDataChannel();
|
||||
[HideInInspector] [SerializeField] ObiNormalDataChannel m_Normals = new ObiNormalDataChannel();
|
||||
[HideInInspector] [SerializeField] ObiColorDataChannel m_Colors = new ObiColorDataChannel();
|
||||
[HideInInspector] [SerializeField] ObiThicknessDataChannel m_Thickness = new ObiThicknessDataChannel();
|
||||
[HideInInspector] [SerializeField] ObiMassDataChannel m_Masses = new ObiMassDataChannel();
|
||||
[HideInInspector] [SerializeField] ObiRotationalMassDataChannel m_RotationalMasses = new ObiRotationalMassDataChannel();
|
||||
|
||||
[FormerlySerializedAs("m_Phases")]
|
||||
[HideInInspector] [SerializeField] ObiPhaseDataChannel m_Filters = new ObiPhaseDataChannel();
|
||||
|
||||
[HideInInspector] [SerializeField] private bool m_Closed = false;
|
||||
|
||||
protected bool dirty = false;
|
||||
protected const int arcLenghtSamples = 20;
|
||||
[HideInInspector] [SerializeField] protected List<float> m_ArcLengthTable = new List<float>();
|
||||
[HideInInspector] [SerializeField] protected float m_TotalSplineLenght = 0.0f;
|
||||
|
||||
public UnityEvent OnPathChanged = new UnityEvent();
|
||||
public PathControlPointEvent OnControlPointAdded = new PathControlPointEvent();
|
||||
public PathControlPointEvent OnControlPointRemoved = new PathControlPointEvent();
|
||||
public PathControlPointEvent OnControlPointRenamed = new PathControlPointEvent();
|
||||
|
||||
private IEnumerable<IObiPathDataChannel> GetDataChannels()
|
||||
{
|
||||
yield return m_Points;
|
||||
yield return m_Normals;
|
||||
yield return m_Colors;
|
||||
yield return m_Thickness;
|
||||
yield return m_Masses;
|
||||
yield return m_RotationalMasses;
|
||||
yield return m_Filters;
|
||||
}
|
||||
|
||||
public ObiPointsDataChannel points { get { return m_Points; }}
|
||||
public ObiNormalDataChannel normals { get { return m_Normals; } }
|
||||
public ObiColorDataChannel colors { get { return m_Colors; } }
|
||||
public ObiThicknessDataChannel thicknesses { get { return m_Thickness; } }
|
||||
public ObiMassDataChannel masses { get { return m_Masses; } }
|
||||
public ObiRotationalMassDataChannel rotationalMasses { get { return m_RotationalMasses; } }
|
||||
public ObiPhaseDataChannel filters { get { return m_Filters; } }
|
||||
|
||||
public ReadOnlyCollection<float> ArcLengthTable
|
||||
{
|
||||
get { return m_ArcLengthTable.AsReadOnly(); }
|
||||
}
|
||||
|
||||
public float Length
|
||||
{
|
||||
get { return m_TotalSplineLenght; }
|
||||
}
|
||||
|
||||
public int ArcLengthSamples
|
||||
{
|
||||
get { return arcLenghtSamples; }
|
||||
}
|
||||
|
||||
public int ControlPointCount
|
||||
{
|
||||
get { return m_Points.Count;}
|
||||
}
|
||||
|
||||
public bool Closed
|
||||
{
|
||||
get { return m_Closed; }
|
||||
set
|
||||
{
|
||||
if (value != m_Closed)
|
||||
{
|
||||
m_Closed = value;
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int GetSpanCount()
|
||||
{
|
||||
return m_Points.GetSpanCount(m_Closed);
|
||||
}
|
||||
|
||||
public int GetSpanControlPointForMu(float mu, out float spanMu)
|
||||
{
|
||||
return m_Points.GetSpanControlPointAtMu(m_Closed, mu, out spanMu);
|
||||
}
|
||||
|
||||
public int GetClosestControlPointIndex(float mu)
|
||||
{
|
||||
float spanMu;
|
||||
int cp = GetSpanControlPointForMu(mu, out spanMu);
|
||||
|
||||
if (spanMu > 0.5f)
|
||||
return (cp + 1) % ControlPointCount;
|
||||
else
|
||||
return cp % ControlPointCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the curve parameter (mu) at a certain length of the curve, using linear interpolation
|
||||
* of the values cached in arcLengthTable.
|
||||
*/
|
||||
public float GetMuAtLenght(float length)
|
||||
{
|
||||
if (length <= 0) return 0;
|
||||
if (length >= m_TotalSplineLenght) return 1;
|
||||
|
||||
int i;
|
||||
for (i = 1; i < m_ArcLengthTable.Count; ++i)
|
||||
{
|
||||
if (length < m_ArcLengthTable[i]) break;
|
||||
}
|
||||
|
||||
float prevMu = (i - 1) / (float)(m_ArcLengthTable.Count - 1);
|
||||
float nextMu = i / (float)(m_ArcLengthTable.Count - 1);
|
||||
|
||||
float s = (length - m_ArcLengthTable[i - 1]) / (m_ArcLengthTable[i] - m_ArcLengthTable[i - 1]);
|
||||
|
||||
return prevMu + (nextMu - prevMu) * s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculates spline arc lenght in world space using Gauss-Lobatto adaptive integration.
|
||||
* @param acc minimum accuray desired (eg 0.00001f)
|
||||
* @param maxevals maximum number of spline evaluations we want to allow per segment.
|
||||
*/
|
||||
public float RecalculateLenght(Matrix4x4 referenceFrame, float acc, int maxevals)
|
||||
{
|
||||
if (referenceFrame == null)
|
||||
{
|
||||
m_TotalSplineLenght = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
m_TotalSplineLenght = 0.0f;
|
||||
m_ArcLengthTable.Clear();
|
||||
m_ArcLengthTable.Add(0);
|
||||
|
||||
float step = 1 / (float)(arcLenghtSamples + 1);
|
||||
int controlPoints = ControlPointCount;
|
||||
|
||||
if (controlPoints >= 2)
|
||||
{
|
||||
|
||||
int spans = GetSpanCount();
|
||||
|
||||
for (int cp = 0; cp < spans; ++cp)
|
||||
{
|
||||
int nextCP = (cp + 1) % controlPoints;
|
||||
var wp1 = m_Points[cp];
|
||||
var wp2 = m_Points[nextCP];
|
||||
|
||||
Vector3 _p = referenceFrame.MultiplyPoint3x4(wp1.position);
|
||||
Vector3 p = referenceFrame.MultiplyPoint3x4(wp1.outTangentEndpoint);
|
||||
Vector3 p_ = referenceFrame.MultiplyPoint3x4(wp2.inTangentEndpoint);
|
||||
Vector3 p__ = referenceFrame.MultiplyPoint3x4(wp2.position);
|
||||
|
||||
for (int i = 0; i <= Mathf.Max(1, arcLenghtSamples); ++i)
|
||||
{
|
||||
|
||||
float a = i * step;
|
||||
float b = (i + 1) * step;
|
||||
|
||||
float segmentLength = GaussLobattoIntegrationStep(_p, p, p_, p__, a, b,
|
||||
m_Points.EvaluateFirstDerivative(_p, p, p_, p__, a).magnitude,
|
||||
m_Points.EvaluateFirstDerivative(_p, p, p_, p__, b).magnitude, 0, maxevals, acc);
|
||||
|
||||
m_TotalSplineLenght += segmentLength;
|
||||
|
||||
m_ArcLengthTable.Add(m_TotalSplineLenght);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("A path needs at least 2 control points to be defined.");
|
||||
}
|
||||
|
||||
return m_TotalSplineLenght;
|
||||
}
|
||||
|
||||
/**
|
||||
* One step of the adaptive integration method using Gauss-Lobatto quadrature.
|
||||
* Takes advantage of the fact that the arc lenght of a vector function is equal to the
|
||||
* integral of the magnitude of first derivative.
|
||||
*/
|
||||
private float GaussLobattoIntegrationStep(Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4,
|
||||
float a, float b,
|
||||
float fa, float fb, int nevals, int maxevals, float acc)
|
||||
{
|
||||
|
||||
if (nevals >= maxevals) return 0;
|
||||
|
||||
// Constants used in the algorithm
|
||||
float alpha = Mathf.Sqrt(2.0f / 3.0f);
|
||||
float beta = 1.0f / Mathf.Sqrt(5.0f);
|
||||
|
||||
// Here the abcissa points and function values for both the 4-point
|
||||
// and the 7-point rule are calculated (the points at the end of
|
||||
// interval come from the function call, i.e., fa and fb. Also note
|
||||
// the 7-point rule re-uses all the points of the 4-point rule.)
|
||||
float h = (b - a) / 2;
|
||||
float m = (a + b) / 2;
|
||||
|
||||
float mll = m - alpha * h;
|
||||
float ml = m - beta * h;
|
||||
float mr = m + beta * h;
|
||||
float mrr = m + alpha * h;
|
||||
nevals += 5;
|
||||
|
||||
float fmll = m_Points.EvaluateFirstDerivative(p1, p2, p3, p4, mll).magnitude;
|
||||
float fml = m_Points.EvaluateFirstDerivative(p1, p2, p3, p4, ml).magnitude;
|
||||
float fm = m_Points.EvaluateFirstDerivative(p1, p2, p3, p4, m).magnitude;
|
||||
float fmr = m_Points.EvaluateFirstDerivative(p1, p2, p3, p4, mr).magnitude;
|
||||
float fmrr = m_Points.EvaluateFirstDerivative(p1, p2, p3, p4, mrr).magnitude;
|
||||
|
||||
// Both the 4-point and 7-point rule integrals are evaluted
|
||||
float integral4 = (h / 6) * (fa + fb + 5 * (fml + fmr));
|
||||
float integral7 = (h / 1470) * (77 * (fa + fb) + 432 * (fmll + fmrr) + 625 * (fml + fmr) + 672 * fm);
|
||||
|
||||
// The difference betwen the 4-point and 7-point integrals is the
|
||||
// estimate of the accuracy
|
||||
|
||||
if ((integral4 - integral7) < acc || mll <= a || b <= mrr)
|
||||
{
|
||||
if (!(m > a && b > m))
|
||||
{
|
||||
Debug.LogError("Spline integration reached an interval with no more machine numbers");
|
||||
}
|
||||
return integral7;
|
||||
}
|
||||
else
|
||||
{
|
||||
return GaussLobattoIntegrationStep(p1, p2, p3, p4, a, mll, fa, fmll, nevals, maxevals, acc)
|
||||
+ GaussLobattoIntegrationStep(p1, p2, p3, p4, mll, ml, fmll, fml, nevals, maxevals, acc)
|
||||
+ GaussLobattoIntegrationStep(p1, p2, p3, p4, ml, m, fml, fm, nevals, maxevals, acc)
|
||||
+ GaussLobattoIntegrationStep(p1, p2, p3, p4, m, mr, fm, fmr, nevals, maxevals, acc)
|
||||
+ GaussLobattoIntegrationStep(p1, p2, p3, p4, mr, mrr, fmr, fmrr, nevals, maxevals, acc)
|
||||
+ GaussLobattoIntegrationStep(p1, p2, p3, p4, mrr, b, fmrr, fb, nevals, maxevals, acc);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void SetName(int index, string name)
|
||||
{
|
||||
m_Names[index] = name;
|
||||
if (OnControlPointRenamed != null)
|
||||
OnControlPointRenamed.Invoke(index);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
public string GetName(int index)
|
||||
{
|
||||
return m_Names[index];
|
||||
}
|
||||
|
||||
public void AddControlPoint(Vector3 position, Vector3 inTangentVector, Vector3 outTangentVector, Vector3 normal, float mass, float rotationalMass, float thickness, int filter, Color color, string name)
|
||||
{
|
||||
InsertControlPoint(ControlPointCount, position, inTangentVector, outTangentVector, normal, mass, rotationalMass, thickness, filter, color, name);
|
||||
}
|
||||
|
||||
public void InsertControlPoint(int index, Vector3 position, Vector3 inTangentVector, Vector3 outTangentVector, Vector3 normal, float mass, float rotationalMass, float thickness, int filter, Color color, string name)
|
||||
{
|
||||
m_Points.data.Insert(index, new ObiWingedPoint(inTangentVector,position,outTangentVector));
|
||||
m_Colors.data.Insert(index, color);
|
||||
m_Normals.data.Insert(index, normal);
|
||||
m_Thickness.data.Insert(index, thickness);
|
||||
m_Masses.data.Insert(index, mass);
|
||||
m_RotationalMasses.data.Insert(index, rotationalMass);
|
||||
m_Filters.data.Insert(index, filter);
|
||||
m_Names.Insert(index,name);
|
||||
|
||||
if (OnControlPointAdded != null)
|
||||
OnControlPointAdded.Invoke(index);
|
||||
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
public int InsertControlPoint(float mu)
|
||||
{
|
||||
|
||||
int controlPoints = ControlPointCount;
|
||||
if (controlPoints >= 2)
|
||||
{
|
||||
|
||||
if (!System.Single.IsNaN(mu))
|
||||
{
|
||||
|
||||
float p;
|
||||
int i = GetSpanControlPointForMu(mu, out p);
|
||||
|
||||
int next = (i + 1) % controlPoints;
|
||||
|
||||
var wp1 = m_Points[i];
|
||||
var wp2 = m_Points[next];
|
||||
|
||||
Vector3 P0_1 = (1 - p) * wp1.position + p * wp1.outTangentEndpoint;
|
||||
Vector3 P1_2 = (1 - p) * wp1.outTangentEndpoint + p * wp2.inTangentEndpoint;
|
||||
Vector3 P2_3 = (1 - p) * wp2.inTangentEndpoint + p * wp2.position;
|
||||
|
||||
Vector3 P01_12 = (1 - p) * P0_1 + p * P1_2;
|
||||
Vector3 P12_23 = (1 - p) * P1_2 + p * P2_3;
|
||||
|
||||
Vector3 P0112_1223 = (1 - p) * P01_12 + p * P12_23;
|
||||
|
||||
wp1.SetOutTangentEndpoint(P0_1);
|
||||
wp2.SetInTangentEndpoint(P2_3);
|
||||
|
||||
m_Points[i] = wp1;
|
||||
m_Points[next] = wp2;
|
||||
|
||||
Color color = m_Colors.Evaluate(m_Colors[i],
|
||||
m_Colors[i],
|
||||
m_Colors[next],
|
||||
m_Colors[next], p);
|
||||
|
||||
Vector3 normal = m_Normals.Evaluate(m_Normals[i],
|
||||
m_Normals[i],
|
||||
m_Normals[next],
|
||||
m_Normals[next], p);
|
||||
|
||||
float thickness = m_Thickness.Evaluate(m_Thickness[i],
|
||||
m_Thickness[i],
|
||||
m_Thickness[next],
|
||||
m_Thickness[next], p);
|
||||
|
||||
float mass = m_Masses.Evaluate(m_Masses[i],
|
||||
m_Masses[i],
|
||||
m_Masses[next],
|
||||
m_Masses[next], p);
|
||||
|
||||
float rotationalMass = m_RotationalMasses.Evaluate(m_RotationalMasses[i],
|
||||
m_RotationalMasses[i],
|
||||
m_RotationalMasses[next],
|
||||
m_RotationalMasses[next], p);
|
||||
|
||||
int filter = m_Filters.Evaluate(m_Filters[i],
|
||||
m_Filters[i],
|
||||
m_Filters[next],
|
||||
m_Filters[next], p);
|
||||
|
||||
InsertControlPoint(i + 1, P0112_1223, P01_12 - P0112_1223, P12_23 - P0112_1223, normal, mass,rotationalMass, thickness, filter, color, GetName(i));
|
||||
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
for (int i = ControlPointCount-1; i >= 0; --i)
|
||||
RemoveControlPoint(i);
|
||||
|
||||
m_TotalSplineLenght = 0.0f;
|
||||
m_ArcLengthTable.Clear();
|
||||
m_ArcLengthTable.Add(0);
|
||||
}
|
||||
|
||||
public void RemoveControlPoint(int index)
|
||||
{
|
||||
foreach (var channel in GetDataChannels())
|
||||
channel.RemoveAt(index);
|
||||
|
||||
m_Names.RemoveAt(index);
|
||||
|
||||
if (OnControlPointRemoved != null)
|
||||
OnControlPointRemoved.Invoke(index);
|
||||
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
public void FlushEvents()
|
||||
{
|
||||
bool isDirty = dirty;
|
||||
foreach (var channel in GetDataChannels())
|
||||
{
|
||||
isDirty |= channel.Dirty;
|
||||
channel.Clean();
|
||||
}
|
||||
|
||||
if (OnPathChanged != null && isDirty)
|
||||
{
|
||||
dirty = false;
|
||||
OnPathChanged.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee1575a4e45ef4a8ebafa0e5e2e51da5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,168 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
public struct ObiPathFrame
|
||||
{
|
||||
public enum Axis
|
||||
{
|
||||
X = 0,
|
||||
Y = 1,
|
||||
Z = 2
|
||||
}
|
||||
|
||||
public Vector3 position;
|
||||
|
||||
public Vector3 tangent;
|
||||
public Vector3 normal;
|
||||
public Vector3 binormal;
|
||||
|
||||
public Vector4 color;
|
||||
public float thickness;
|
||||
|
||||
public ObiPathFrame(Vector3 position, Vector3 tangent, Vector3 normal, Vector3 binormal, Vector4 color, float thickness){
|
||||
this.position = position;
|
||||
this.normal = normal;
|
||||
this.tangent = tangent;
|
||||
this.binormal = binormal;
|
||||
this.color = color;
|
||||
this.thickness = thickness;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
position = Vector3.zero;
|
||||
tangent = Vector3.forward;
|
||||
normal = Vector3.up;
|
||||
binormal = Vector3.right;
|
||||
color = Color.white;
|
||||
thickness = 0;
|
||||
}
|
||||
|
||||
public static ObiPathFrame operator +(ObiPathFrame c1, ObiPathFrame c2)
|
||||
{
|
||||
return new ObiPathFrame(c1.position + c2.position,c1.tangent + c2.tangent,c1.normal + c2.normal,c1.binormal + c2.binormal,c1.color + c2.color, c1.thickness + c2.thickness);
|
||||
}
|
||||
|
||||
public static ObiPathFrame operator *(float f,ObiPathFrame c)
|
||||
{
|
||||
return new ObiPathFrame(c.position * f, c.tangent * f, c.normal * f, c.binormal * f,c.color * f, c.thickness * f);
|
||||
}
|
||||
|
||||
public static void WeightedSum(float w1, float w2, float w3, ref ObiPathFrame c1, ref ObiPathFrame c2, ref ObiPathFrame c3, ref ObiPathFrame sum)
|
||||
{
|
||||
sum.position.x = c1.position.x * w1 + c2.position.x * w2 + c3.position.x * w3;
|
||||
sum.position.y = c1.position.y * w1 + c2.position.y * w2 + c3.position.y * w3;
|
||||
sum.position.z = c1.position.z * w1 + c2.position.z * w2 + c3.position.z * w3;
|
||||
|
||||
sum.tangent.x = c1.tangent.x * w1 + c2.tangent.x * w2 + c3.tangent.x * w3;
|
||||
sum.tangent.y = c1.tangent.y * w1 + c2.tangent.y * w2 + c3.tangent.y * w3;
|
||||
sum.tangent.z = c1.tangent.z * w1 + c2.tangent.z * w2 + c3.tangent.z * w3;
|
||||
|
||||
sum.normal.x = c1.normal.x * w1 + c2.normal.x * w2 + c3.normal.x * w3;
|
||||
sum.normal.y = c1.normal.y * w1 + c2.normal.y * w2 + c3.normal.y * w3;
|
||||
sum.normal.z = c1.normal.z * w1 + c2.normal.z * w2 + c3.normal.z * w3;
|
||||
|
||||
sum.binormal.x = c1.binormal.x * w1 + c2.binormal.x * w2 + c3.binormal.x * w3;
|
||||
sum.binormal.y = c1.binormal.y * w1 + c2.binormal.y * w2 + c3.binormal.y * w3;
|
||||
sum.binormal.z = c1.binormal.z * w1 + c2.binormal.z * w2 + c3.binormal.z * w3;
|
||||
|
||||
sum.color.x = c1.color.x * w1 + c2.color.x * w2 + c3.color.x * w3;
|
||||
sum.color.y = c1.color.y * w1 + c2.color.y * w2 + c3.color.y * w3;
|
||||
sum.color.z = c1.color.z * w1 + c2.color.z * w2 + c3.color.z * w3;
|
||||
sum.color.w = c1.color.w * w1 + c2.color.w * w2 + c3.color.w * w3;
|
||||
|
||||
sum.thickness = c1.thickness * w1 + c2.thickness * w2 + c3.thickness * w3;
|
||||
}
|
||||
|
||||
public void SetTwist(float twist)
|
||||
{
|
||||
Quaternion twistQ = Quaternion.AngleAxis(twist, tangent);
|
||||
normal = twistQ * normal;
|
||||
binormal = twistQ * binormal;
|
||||
}
|
||||
|
||||
public void SetTwistAndTangent(float twist, Vector3 tangent)
|
||||
{
|
||||
this.tangent = tangent;
|
||||
normal = new Vector3(tangent.y, tangent.x, 0).normalized;
|
||||
binormal = Vector3.Cross(normal, tangent);
|
||||
|
||||
Quaternion twistQ = Quaternion.AngleAxis(twist, tangent);
|
||||
normal = twistQ * normal;
|
||||
binormal = twistQ * binormal;
|
||||
}
|
||||
|
||||
public void Transport(ObiPathFrame frame, float twist)
|
||||
{
|
||||
// Calculate delta rotation:
|
||||
Quaternion rotQ = Quaternion.FromToRotation(tangent, frame.tangent);
|
||||
Quaternion twistQ = Quaternion.AngleAxis(twist, frame.tangent);
|
||||
Quaternion finalQ = twistQ * rotQ;
|
||||
|
||||
// Rotate previous frame axes to obtain the new ones:
|
||||
normal = finalQ * normal;
|
||||
binormal = finalQ * binormal;
|
||||
tangent = frame.tangent;
|
||||
position = frame.position;
|
||||
thickness = frame.thickness;
|
||||
color = frame.color;
|
||||
}
|
||||
|
||||
public void Transport(Vector3 newPosition, Vector3 newTangent, float twist)
|
||||
{
|
||||
// Calculate delta rotation:
|
||||
Quaternion rotQ = Quaternion.FromToRotation(tangent, newTangent);
|
||||
Quaternion twistQ = Quaternion.AngleAxis(twist, newTangent);
|
||||
Quaternion finalQ = twistQ * rotQ;
|
||||
|
||||
// Rotate previous frame axes to obtain the new ones:
|
||||
normal = finalQ * normal;
|
||||
binormal = finalQ * binormal;
|
||||
tangent = newTangent;
|
||||
position = newPosition;
|
||||
|
||||
}
|
||||
|
||||
// Transport, hinting the normal.
|
||||
public void Transport(Vector3 newPosition, Vector3 newTangent, Vector3 newNormal, float twist)
|
||||
{
|
||||
normal = Quaternion.AngleAxis(twist, newTangent) * newNormal;
|
||||
tangent = newTangent;
|
||||
binormal = Vector3.Cross(normal, tangent);
|
||||
position = newPosition;
|
||||
}
|
||||
|
||||
public Matrix4x4 ToMatrix(Axis mainAxis)
|
||||
{
|
||||
Matrix4x4 basis = new Matrix4x4();
|
||||
|
||||
int xo = ((int)mainAxis) % 3 * 4;
|
||||
int yo = ((int)mainAxis + 1) % 3 * 4;
|
||||
int zo = ((int)mainAxis + 2) % 3 * 4;
|
||||
|
||||
basis[xo] = tangent[0];
|
||||
basis[xo + 1] = tangent[1];
|
||||
basis[xo + 2] = tangent[2];
|
||||
|
||||
basis[yo] = binormal[0];
|
||||
basis[yo + 1] = binormal[1];
|
||||
basis[yo + 2] = binormal[2];
|
||||
|
||||
basis[zo] = normal[0];
|
||||
basis[zo + 1] = normal[1];
|
||||
basis[zo + 2] = normal[2];
|
||||
|
||||
return basis;
|
||||
}
|
||||
|
||||
public void DebugDraw(float size)
|
||||
{
|
||||
Debug.DrawRay(position, binormal * size, Color.red);
|
||||
Debug.DrawRay(position, normal * size, Color.green);
|
||||
Debug.DrawRay(position, tangent * size, Color.blue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 237e9ecf813f646fd8502768445f4ab6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,379 @@
|
||||
using UnityEngine;
|
||||
using Unity.Profiling;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
[ExecuteInEditMode]
|
||||
[RequireComponent(typeof(ObiRopeBase))]
|
||||
public class ObiPathSmoother : MonoBehaviour
|
||||
{
|
||||
static ProfilerMarker m_AllocateRawChunksPerfMarker = new ProfilerMarker("AllocateRawChunks");
|
||||
static ProfilerMarker m_GenerateSmoothChunksPerfMarker = new ProfilerMarker("GenerateSmoothChunks");
|
||||
|
||||
private Matrix4x4 w2l;
|
||||
private Quaternion w2lRotation;
|
||||
|
||||
[Range(0, 1)]
|
||||
[Tooltip("Curvature threshold below which the path will be decimated. A value of 0 won't apply any decimation. As you increase the value, decimation will become more aggresive.")]
|
||||
public float decimation = 0;
|
||||
|
||||
[Range(0, 3)]
|
||||
[Tooltip("Smoothing iterations applied to the path. A smoothing value of 0 won't perform any smoothing at all. Note that smoothing is applied after decimation.")]
|
||||
public uint smoothing = 0;
|
||||
|
||||
[Tooltip("Twist in degrees applied to each sucessive path section.")]
|
||||
public float twist = 0;
|
||||
|
||||
public event ObiActor.ActorCallback OnCurveGenerated;
|
||||
|
||||
protected float smoothLength = 0;
|
||||
protected int smoothSections = 0;
|
||||
|
||||
[HideInInspector] public ObiList<ObiList<ObiPathFrame>> rawChunks = new ObiList<ObiList<ObiPathFrame>>();
|
||||
[HideInInspector] public ObiList<ObiList<ObiPathFrame>> smoothChunks = new ObiList<ObiList<ObiPathFrame>>();
|
||||
private Stack<Vector2Int> stack = new Stack<Vector2Int>();
|
||||
private BitArray decimateBitArray = new BitArray(0);
|
||||
|
||||
public float SmoothLength
|
||||
{
|
||||
get { return smoothLength; }
|
||||
}
|
||||
|
||||
public float SmoothSections
|
||||
{
|
||||
get { return smoothSections; }
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
GetComponent<ObiRopeBase>().OnInterpolate += Actor_OnInterpolate;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
GetComponent<ObiRopeBase>().OnInterpolate -= Actor_OnInterpolate;
|
||||
}
|
||||
|
||||
void Actor_OnInterpolate(ObiActor actor)
|
||||
{
|
||||
GenerateSmoothChunks(((ObiRopeBase)actor), smoothing);
|
||||
|
||||
if (OnCurveGenerated != null)
|
||||
OnCurveGenerated(actor);
|
||||
}
|
||||
|
||||
private void AllocateChunk(int sections)
|
||||
{
|
||||
if (sections > 1)
|
||||
{
|
||||
|
||||
if (rawChunks.Data[rawChunks.Count] == null)
|
||||
{
|
||||
rawChunks.Data[rawChunks.Count] = new ObiList<ObiPathFrame>();
|
||||
smoothChunks.Data[smoothChunks.Count] = new ObiList<ObiPathFrame>();
|
||||
}
|
||||
|
||||
rawChunks.Data[rawChunks.Count].SetCount(sections);
|
||||
|
||||
rawChunks.SetCount(rawChunks.Count + 1);
|
||||
smoothChunks.SetCount(smoothChunks.Count + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private float CalculateChunkLength(ObiList<ObiPathFrame> chunk)
|
||||
{
|
||||
float length = 0;
|
||||
for (int i = 1; i < chunk.Count; ++i)
|
||||
length += Vector3.Distance(chunk[i].position, chunk[i - 1].position);
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates raw curve chunks from the rope description.
|
||||
*/
|
||||
private void AllocateRawChunks(ObiRopeBase actor)
|
||||
{
|
||||
using (m_AllocateRawChunksPerfMarker.Auto())
|
||||
{
|
||||
rawChunks.Clear();
|
||||
|
||||
if (actor.path == null)
|
||||
return;
|
||||
|
||||
// Count particles for each chunk.
|
||||
int particles = 0;
|
||||
for (int i = 0; i < actor.elements.Count; ++i)
|
||||
{
|
||||
particles++;
|
||||
// At discontinuities, start a new chunk.
|
||||
if (i < actor.elements.Count - 1 && actor.elements[i].particle2 != actor.elements[i + 1].particle1)
|
||||
{
|
||||
AllocateChunk(++particles);
|
||||
particles = 0;
|
||||
}
|
||||
}
|
||||
AllocateChunk(++particles);
|
||||
}
|
||||
}
|
||||
|
||||
private void PathFrameFromParticle(ObiRopeBase actor, ref ObiPathFrame frame, int particleIndex, bool interpolateOrientation = true)
|
||||
{
|
||||
// Update current frame values from particles:
|
||||
frame.position = w2l.MultiplyPoint3x4(actor.GetParticlePosition(particleIndex));
|
||||
frame.thickness = actor.GetParticleMaxRadius(particleIndex);
|
||||
frame.color = actor.GetParticleColor(particleIndex);
|
||||
|
||||
// Use particle orientation if possible:
|
||||
if (actor.usesOrientedParticles)
|
||||
{
|
||||
Quaternion current = actor.GetParticleOrientation(particleIndex);
|
||||
Quaternion previous = actor.GetParticleOrientation(Mathf.Max(0, particleIndex - 1));
|
||||
Quaternion average = w2lRotation * (interpolateOrientation ? Quaternion.SlerpUnclamped(current, previous, 0.5f) : current);
|
||||
frame.normal = average * Vector3.up;
|
||||
frame.binormal = average * Vector3.right;
|
||||
frame.tangent = average * Vector3.forward;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates smooth curve chunks.
|
||||
*/
|
||||
public void GenerateSmoothChunks(ObiRopeBase actor, uint smoothingLevels)
|
||||
{
|
||||
using (m_GenerateSmoothChunksPerfMarker.Auto())
|
||||
{
|
||||
smoothChunks.Clear();
|
||||
smoothSections = 0;
|
||||
smoothLength = 0;
|
||||
|
||||
if (!Application.isPlaying)
|
||||
actor.RebuildElementsFromConstraints();
|
||||
|
||||
AllocateRawChunks(actor);
|
||||
|
||||
w2l = actor.transform.worldToLocalMatrix;
|
||||
w2lRotation = w2l.rotation;
|
||||
|
||||
// keep track of the first element of each chunk
|
||||
int chunkStart = 0;
|
||||
|
||||
ObiPathFrame frame_0 = new ObiPathFrame(); // "next" frame
|
||||
ObiPathFrame frame_1 = new ObiPathFrame(); // current frame
|
||||
ObiPathFrame frame_2 = new ObiPathFrame(); // previous frame
|
||||
|
||||
// generate curve for each rope chunk:
|
||||
for (int i = 0; i < rawChunks.Count; ++i)
|
||||
{
|
||||
int elementCount = rawChunks[i].Count - 1;
|
||||
|
||||
// Initialize frames:
|
||||
frame_0.Reset();
|
||||
frame_1.Reset();
|
||||
frame_2.Reset();
|
||||
|
||||
PathFrameFromParticle(actor, ref frame_1, actor.elements[chunkStart].particle1, false);
|
||||
|
||||
frame_2 = frame_1;
|
||||
|
||||
for (int m = 1; m <= rawChunks[i].Count; ++m)
|
||||
{
|
||||
|
||||
int index;
|
||||
if (m >= elementCount)
|
||||
// second particle of last element in the chunk.
|
||||
index = actor.elements[chunkStart + elementCount - 1].particle2;
|
||||
else
|
||||
//first particle of current element.
|
||||
index = actor.elements[chunkStart + m].particle1;
|
||||
|
||||
// generate curve frame from particle:
|
||||
PathFrameFromParticle(actor, ref frame_0, index);
|
||||
|
||||
if (actor.usesOrientedParticles)
|
||||
{
|
||||
// copy frame directly.
|
||||
frame_2 = frame_1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// perform parallel transport, using forward / backward average to calculate tangent.
|
||||
frame_1.tangent = ((frame_1.position - frame_2.position) + (frame_0.position - frame_1.position)).normalized;
|
||||
frame_2.Transport(frame_1, twist);
|
||||
}
|
||||
|
||||
// in case we wrapped around the rope, average first and last frames:
|
||||
if (chunkStart + m > actor.activeParticleCount)
|
||||
{
|
||||
frame_2 = rawChunks[0][0] = 0.5f * frame_2 + 0.5f * rawChunks[0][0];
|
||||
}
|
||||
|
||||
frame_1 = frame_0;
|
||||
|
||||
rawChunks[i][m - 1] = frame_2;
|
||||
}
|
||||
|
||||
// increment chunkStart by the amount of elements in this chunk:
|
||||
chunkStart += elementCount;
|
||||
|
||||
// adaptive curvature-based decimation:
|
||||
if (Decimate(rawChunks[i], smoothChunks[i], decimation))
|
||||
{
|
||||
// if any decimation took place, swap raw and smooth chunks:
|
||||
var aux = rawChunks[i];
|
||||
rawChunks[i] = smoothChunks[i];
|
||||
smoothChunks[i] = aux;
|
||||
}
|
||||
|
||||
// get smooth curve points:
|
||||
Chaikin(rawChunks[i], smoothChunks[i], smoothingLevels);
|
||||
|
||||
// count total curve sections and total curve length:
|
||||
smoothSections += smoothChunks[i].Count;
|
||||
smoothLength += CalculateChunkLength(smoothChunks[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ObiPathFrame GetSectionAt(float mu)
|
||||
{
|
||||
float edgeMu = smoothSections * Mathf.Clamp(mu,0,0.9999f);
|
||||
int index = (int)edgeMu;
|
||||
float sectionMu = edgeMu - index;
|
||||
|
||||
int counter = 0;
|
||||
int chunkIndex = -1;
|
||||
int indexInChunk = -1;
|
||||
for (int i = 0; i < smoothChunks.Count; ++i)
|
||||
{
|
||||
if (counter + smoothChunks[i].Count > index)
|
||||
{
|
||||
chunkIndex = i;
|
||||
indexInChunk = index - counter;
|
||||
break;
|
||||
}
|
||||
counter += smoothChunks[i].Count;
|
||||
}
|
||||
|
||||
ObiList<ObiPathFrame> chunk = smoothChunks[chunkIndex];
|
||||
ObiPathFrame s1 = chunk[indexInChunk];
|
||||
ObiPathFrame s2 = chunk[Mathf.Min(indexInChunk + 1, chunk.Count - 1)];
|
||||
|
||||
return (1 - sectionMu) * s1 + sectionMu * s2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterative version of the Ramer-Douglas-Peucker path decimation algorithm.
|
||||
*/
|
||||
private bool Decimate(ObiList<ObiPathFrame> input, ObiList<ObiPathFrame> output, float threshold)
|
||||
{
|
||||
// no decimation, no work to do, just return:
|
||||
if (threshold < 0.00001f || input.Count < 3)
|
||||
return false;
|
||||
|
||||
float scaledThreshold = threshold * threshold * 0.01f;
|
||||
|
||||
stack.Push(new Vector2Int(0, input.Count - 1));
|
||||
|
||||
decimateBitArray.Length = Mathf.Max(decimateBitArray.Length, input.Count);
|
||||
decimateBitArray.SetAll(true);
|
||||
|
||||
while (stack.Count > 0)
|
||||
{
|
||||
var range = stack.Pop();
|
||||
|
||||
float dmax = 0;
|
||||
int index = range.x;
|
||||
float mu;
|
||||
|
||||
for (int i = index + 1; i < range.y; ++i)
|
||||
{
|
||||
if (decimateBitArray[i])
|
||||
{
|
||||
float d = Vector3.SqrMagnitude(ObiUtils.ProjectPointLine(input[i].position, input[range.x].position, input[range.y].position, out mu) - input[i].position);
|
||||
|
||||
if (d > dmax)
|
||||
{
|
||||
index = i;
|
||||
dmax = d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dmax > scaledThreshold)
|
||||
{
|
||||
stack.Push(new Vector2Int(range.x, index));
|
||||
stack.Push(new Vector2Int(index, range.y));
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = range.x + 1; i < range.y; ++i)
|
||||
decimateBitArray[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
output.Clear();
|
||||
for (int i = 0; i < input.Count; ++i)
|
||||
if (decimateBitArray[i])
|
||||
output.Add(input[i]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method uses a variant of Chainkin's algorithm to produce a smooth curve from a set of control points. It is specially fast
|
||||
* because it directly calculates subdivision level k, instead of recursively calculating levels 1..k.
|
||||
*/
|
||||
private void Chaikin(ObiList<ObiPathFrame> input, ObiList<ObiPathFrame> output, uint k)
|
||||
{
|
||||
// no subdivision levels, no work to do. just copy the input to the output:
|
||||
if (k == 0 || input.Count < 3)
|
||||
{
|
||||
output.SetCount(input.Count);
|
||||
for (int i = 0; i < input.Count; ++i)
|
||||
output[i] = input[i];
|
||||
return;
|
||||
}
|
||||
|
||||
// calculate amount of new points generated by each inner control point:
|
||||
int pCount = (int)Mathf.Pow(2, k);
|
||||
|
||||
// precalculate some quantities:
|
||||
int n0 = input.Count - 1;
|
||||
float twoRaisedToMinusKPlus1 = Mathf.Pow(2, -(k + 1));
|
||||
float twoRaisedToMinusK = Mathf.Pow(2, -k);
|
||||
float twoRaisedToMinus2K = Mathf.Pow(2, -2 * k);
|
||||
float twoRaisedToMinus2KMinus1 = Mathf.Pow(2, -2 * k - 1);
|
||||
|
||||
// allocate ouput:
|
||||
output.SetCount((n0 - 1) * pCount + 2);
|
||||
|
||||
// calculate initial curve points:
|
||||
output[0] = (0.5f + twoRaisedToMinusKPlus1) * input[0] + (0.5f - twoRaisedToMinusKPlus1) * input[1];
|
||||
output[pCount * n0 - pCount + 1] = (0.5f - twoRaisedToMinusKPlus1) * input[n0 - 1] + (0.5f + twoRaisedToMinusKPlus1) * input[n0];
|
||||
|
||||
// calculate internal points:
|
||||
for (int j = 1; j <= pCount; ++j)
|
||||
{
|
||||
// precalculate coefficients:
|
||||
float F = 0.5f - twoRaisedToMinusKPlus1 - (j - 1) * (twoRaisedToMinusK - j * twoRaisedToMinus2KMinus1);
|
||||
float G = 0.5f + twoRaisedToMinusKPlus1 + (j - 1) * (twoRaisedToMinusK - j * twoRaisedToMinus2K);
|
||||
float H = (j - 1) * j * twoRaisedToMinus2KMinus1;
|
||||
|
||||
for (int i = 1; i < n0; ++i)
|
||||
ObiPathFrame.WeightedSum(F, G, H,
|
||||
ref input.Data[i - 1],
|
||||
ref input.Data[i],
|
||||
ref input.Data[i + 1],
|
||||
ref output.Data[(i - 1) * pCount + j]);
|
||||
}
|
||||
|
||||
// make first and last curve points coincide with original points:
|
||||
output[0] = input[0];
|
||||
output[output.Count - 1] = input[input.Count - 1];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 958c969cfb16745f192d4d7bd28b7178
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,100 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
[Serializable]
|
||||
public struct ObiWingedPoint
|
||||
{
|
||||
public enum TangentMode
|
||||
{
|
||||
Aligned,
|
||||
Mirrored,
|
||||
Free,
|
||||
}
|
||||
|
||||
public TangentMode tangentMode;
|
||||
public Vector3 inTangent;
|
||||
public Vector3 position;
|
||||
public Vector3 outTangent;
|
||||
|
||||
public Vector3 inTangentEndpoint
|
||||
{
|
||||
get { return position + inTangent; }
|
||||
}
|
||||
|
||||
public Vector3 outTangentEndpoint
|
||||
{
|
||||
get { return position + outTangent; }
|
||||
}
|
||||
|
||||
public ObiWingedPoint(Vector3 inTangent, Vector3 point, Vector3 outTangent)
|
||||
{
|
||||
this.tangentMode = TangentMode.Aligned;
|
||||
this.inTangent = inTangent;
|
||||
this.position = point;
|
||||
this.outTangent = outTangent;
|
||||
}
|
||||
|
||||
public void SetInTangentEndpoint(Vector3 value)
|
||||
{
|
||||
Vector3 newTangent = value - position;
|
||||
|
||||
switch (tangentMode)
|
||||
{
|
||||
case TangentMode.Mirrored: outTangent = -newTangent; break;
|
||||
case TangentMode.Aligned: outTangent = -newTangent.normalized * outTangent.magnitude; break;
|
||||
}
|
||||
|
||||
inTangent = newTangent;
|
||||
}
|
||||
|
||||
public void SetOutTangentEndpoint(Vector3 value)
|
||||
{
|
||||
Vector3 newTangent = value - position;
|
||||
|
||||
switch (tangentMode)
|
||||
{
|
||||
case TangentMode.Mirrored: inTangent = -newTangent; break;
|
||||
case TangentMode.Aligned: inTangent = -newTangent.normalized * inTangent.magnitude; break;
|
||||
}
|
||||
|
||||
outTangent = newTangent;
|
||||
}
|
||||
|
||||
public void SetInTangent(Vector3 value)
|
||||
{
|
||||
Vector3 newTangent = value;
|
||||
|
||||
switch (tangentMode)
|
||||
{
|
||||
case TangentMode.Mirrored: outTangent = -newTangent; break;
|
||||
case TangentMode.Aligned: outTangent = -newTangent.normalized * outTangent.magnitude; break;
|
||||
}
|
||||
|
||||
inTangent = newTangent;
|
||||
}
|
||||
|
||||
public void SetOutTangent(Vector3 value)
|
||||
{
|
||||
Vector3 newTangent = value;
|
||||
|
||||
switch (tangentMode)
|
||||
{
|
||||
case TangentMode.Mirrored: inTangent = -newTangent; break;
|
||||
case TangentMode.Aligned: inTangent = -newTangent.normalized * inTangent.magnitude; break;
|
||||
}
|
||||
|
||||
outTangent = newTangent;
|
||||
}
|
||||
|
||||
public void Transform(Vector3 translation, Quaternion rotation, Vector3 scale)
|
||||
{
|
||||
position += translation;
|
||||
inTangent = rotation * Vector3.Scale(inTangent, scale);
|
||||
outTangent = rotation * Vector3.Scale(outTangent, scale);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 76ee869b5f2e5440ea0a1c8a1e161cde
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Obi/Scripts/RopeAndRod/Rendering.meta
Normal file
8
Assets/Obi/Scripts/RopeAndRod/Rendering.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 07dfc7e6a316349cba9d3b434f09ee68
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
171
Assets/Obi/Scripts/RopeAndRod/Rendering/ObiRopeChainRenderer.cs
Normal file
171
Assets/Obi/Scripts/RopeAndRod/Rendering/ObiRopeChainRenderer.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Unity.Profiling;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
[AddComponentMenu("Physics/Obi/Obi Rope Chain Renderer", 885)]
|
||||
[ExecuteInEditMode]
|
||||
public class ObiRopeChainRenderer : MonoBehaviour
|
||||
{
|
||||
static ProfilerMarker m_UpdateChainRopeRendererChunksPerfMarker = new ProfilerMarker("UpdateChainRopeRenderer");
|
||||
|
||||
[HideInInspector] [SerializeField] public List<GameObject> linkInstances = new List<GameObject>();
|
||||
|
||||
[SerializeProperty("RandomizeLinks")]
|
||||
[SerializeField] private bool randomizeLinks = false;
|
||||
|
||||
public Vector3 linkScale = Vector3.one; /**< Scale of chain links.*/
|
||||
public List<GameObject> linkPrefabs = new List<GameObject>();
|
||||
|
||||
[Range(0, 1)]
|
||||
public float twistAnchor = 0; /**< Normalized position of twisting origin along rope.*/
|
||||
|
||||
public float sectionTwist = 0; /**< Amount of twist applied to each section, in degrees.*/
|
||||
|
||||
ObiPathFrame frame = new ObiPathFrame();
|
||||
|
||||
void Awake()
|
||||
{
|
||||
ClearChainLinkInstances();
|
||||
}
|
||||
|
||||
public bool RandomizeLinks
|
||||
{
|
||||
get { return randomizeLinks; }
|
||||
set
|
||||
{
|
||||
if (value != randomizeLinks)
|
||||
{
|
||||
randomizeLinks = value;
|
||||
CreateChainLinkInstances(GetComponent<ObiRopeBase>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
GetComponent<ObiRopeBase>().OnInterpolate += UpdateRenderer;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
GetComponent<ObiRopeBase>().OnInterpolate -= UpdateRenderer;
|
||||
ClearChainLinkInstances();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys all chain link instances. Used when the chain must be re-created from scratch, and when the actor is disabled/destroyed.
|
||||
*/
|
||||
public void ClearChainLinkInstances()
|
||||
{
|
||||
|
||||
if (linkInstances == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < linkInstances.Count; ++i)
|
||||
{
|
||||
if (linkInstances[i] != null)
|
||||
GameObject.DestroyImmediate(linkInstances[i]);
|
||||
}
|
||||
linkInstances.Clear();
|
||||
}
|
||||
|
||||
public void CreateChainLinkInstances(ObiRopeBase rope)
|
||||
{
|
||||
|
||||
ClearChainLinkInstances();
|
||||
|
||||
if (linkPrefabs.Count > 0)
|
||||
{
|
||||
|
||||
for (int i = 0; i < rope.particleCount; ++i)
|
||||
{
|
||||
|
||||
int index = randomizeLinks ? UnityEngine.Random.Range(0, linkPrefabs.Count) : i % linkPrefabs.Count;
|
||||
|
||||
GameObject linkInstance = null;
|
||||
|
||||
if (linkPrefabs[index] != null)
|
||||
{
|
||||
linkInstance = GameObject.Instantiate(linkPrefabs[index]);
|
||||
linkInstance.transform.SetParent(rope.transform, false);
|
||||
linkInstance.hideFlags = HideFlags.HideAndDontSave;
|
||||
linkInstance.SetActive(false);
|
||||
}
|
||||
|
||||
linkInstances.Add(linkInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateRenderer(ObiActor actor)
|
||||
{
|
||||
using (m_UpdateChainRopeRendererChunksPerfMarker.Auto())
|
||||
{
|
||||
var rope = actor as ObiRopeBase;
|
||||
|
||||
// In case there are no link prefabs to instantiate:
|
||||
if (linkPrefabs.Count == 0)
|
||||
return;
|
||||
|
||||
// Regenerate instances if needed:
|
||||
if (linkInstances == null || linkInstances.Count < rope.particleCount)
|
||||
{
|
||||
CreateChainLinkInstances(rope);
|
||||
}
|
||||
|
||||
var blueprint = rope.sourceBlueprint;
|
||||
int elementCount = rope.elements.Count;
|
||||
|
||||
float twist = -sectionTwist * elementCount * twistAnchor;
|
||||
|
||||
//we will define and transport a reference frame along the curve using parallel transport method:
|
||||
frame.Reset();
|
||||
frame.SetTwist(twist);
|
||||
|
||||
int lastParticle = -1;
|
||||
|
||||
for (int i = 0; i < elementCount; ++i)
|
||||
{
|
||||
ObiStructuralElement elm = rope.elements[i];
|
||||
|
||||
Vector3 pos = rope.GetParticlePosition(elm.particle1);
|
||||
Vector3 nextPos = rope.GetParticlePosition(elm.particle2);
|
||||
Vector3 linkVector = nextPos - pos;
|
||||
Vector3 tangent = linkVector.normalized;
|
||||
|
||||
if (rope.sourceBlueprint.usesOrientedParticles)
|
||||
{
|
||||
frame.Transport(nextPos, tangent, rope.GetParticleOrientation(elm.particle1) * Vector3.up, twist);
|
||||
twist += sectionTwist;
|
||||
}
|
||||
else
|
||||
{
|
||||
frame.Transport(nextPos, tangent, sectionTwist);
|
||||
}
|
||||
|
||||
if (linkInstances[i] != null)
|
||||
{
|
||||
linkInstances[i].SetActive(true);
|
||||
Transform linkTransform = linkInstances[i].transform;
|
||||
linkTransform.position = pos + linkVector * 0.5f;
|
||||
linkTransform.localScale = rope.GetParticleMaxRadius(elm.particle1) * 2 * linkScale;
|
||||
linkTransform.rotation = Quaternion.LookRotation(tangent, frame.normal);
|
||||
}
|
||||
|
||||
lastParticle = elm.particle2;
|
||||
|
||||
}
|
||||
|
||||
for (int i = elementCount; i < linkInstances.Count; ++i)
|
||||
{
|
||||
if (linkInstances[i] != null)
|
||||
linkInstances[i].SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 09ac962c0743c400aa230ebf871b6156
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 1289c40ad0e7c4fb3bd738f8a3f3e068, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,176 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Unity.Profiling;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
[AddComponentMenu("Physics/Obi/Obi Rope Extruded Renderer", 883)]
|
||||
[ExecuteInEditMode]
|
||||
[RequireComponent(typeof(MeshRenderer))]
|
||||
[RequireComponent(typeof(MeshFilter))]
|
||||
[RequireComponent(typeof(ObiPathSmoother))]
|
||||
public class ObiRopeExtrudedRenderer : MonoBehaviour
|
||||
{
|
||||
static ProfilerMarker m_UpdateExtrudedRopeRendererChunksPerfMarker = new ProfilerMarker("UpdateExtrudedRopeRenderer");
|
||||
|
||||
private List<Vector3> vertices = new List<Vector3>();
|
||||
private List<Vector3> normals = new List<Vector3>();
|
||||
private List<Vector4> tangents = new List<Vector4>();
|
||||
private List<Vector2> uvs = new List<Vector2>();
|
||||
private List<Color> vertColors = new List<Color>();
|
||||
private List<int> tris = new List<int>();
|
||||
|
||||
ObiPathSmoother smoother; // Each renderer should have its own smoother. The renderer then has a method to get position and orientation at a point.
|
||||
|
||||
[HideInInspector] [NonSerialized] public Mesh extrudedMesh;
|
||||
|
||||
[Range(0, 1)]
|
||||
public float uvAnchor = 0; /**< Normalized position of texture coordinate origin along rope.*/
|
||||
|
||||
public Vector2 uvScale = Vector2.one; /**< Scaling of uvs along rope.*/
|
||||
|
||||
public bool normalizeV = true;
|
||||
|
||||
public ObiRopeSection section = null; /**< Section asset to be extruded along the rope.*/
|
||||
|
||||
public float thicknessScale = 0.8f; /**< Scales section thickness.*/
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
smoother = GetComponent<ObiPathSmoother>();
|
||||
smoother.OnCurveGenerated += UpdateRenderer;
|
||||
CreateMeshIfNeeded();
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
smoother.OnCurveGenerated -= UpdateRenderer;
|
||||
GameObject.DestroyImmediate(extrudedMesh);
|
||||
}
|
||||
|
||||
private void CreateMeshIfNeeded()
|
||||
{
|
||||
if (extrudedMesh == null)
|
||||
{
|
||||
extrudedMesh = new Mesh();
|
||||
extrudedMesh.name = "extrudedMesh";
|
||||
extrudedMesh.MarkDynamic();
|
||||
GetComponent<MeshFilter>().mesh = extrudedMesh;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateRenderer(ObiActor actor)
|
||||
{
|
||||
using (m_UpdateExtrudedRopeRendererChunksPerfMarker.Auto())
|
||||
{
|
||||
if (section == null)
|
||||
return;
|
||||
|
||||
var rope = actor as ObiRopeBase;
|
||||
|
||||
CreateMeshIfNeeded();
|
||||
ClearMeshData();
|
||||
|
||||
int sectionIndex = 0;
|
||||
int sectionSegments = section.Segments;
|
||||
int verticesPerSection = sectionSegments + 1; // the last vertex in each section must be duplicated, due to uv wraparound.
|
||||
float vCoord = -uvScale.y * rope.restLength * uvAnchor; // v texture coordinate.
|
||||
float actualToRestLengthRatio = smoother.SmoothLength / rope.restLength;
|
||||
|
||||
Vector3 vertex = Vector3.zero, normal = Vector3.zero;
|
||||
Vector4 texTangent = Vector4.zero;
|
||||
Vector2 uv = Vector2.zero;
|
||||
|
||||
for (int c = 0; c < smoother.smoothChunks.Count; ++c)
|
||||
{
|
||||
ObiList<ObiPathFrame> curve = smoother.smoothChunks[c];
|
||||
|
||||
for (int i = 0; i < curve.Count; ++i)
|
||||
{
|
||||
// Calculate previous and next curve indices:
|
||||
int prevIndex = Mathf.Max(i - 1, 0);
|
||||
|
||||
// advance v texcoord:
|
||||
vCoord += uvScale.y * (Vector3.Distance(curve.Data[i].position, curve.Data[prevIndex].position) /
|
||||
(normalizeV ? smoother.SmoothLength : actualToRestLengthRatio));
|
||||
|
||||
// calculate section thickness and scale the basis vectors by it:
|
||||
float sectionThickness = curve.Data[i].thickness * thicknessScale;
|
||||
|
||||
// Loop around each segment:
|
||||
int nextSectionIndex = sectionIndex + 1;
|
||||
for (int j = 0; j <= sectionSegments; ++j)
|
||||
{
|
||||
// make just one copy of the section vertex:
|
||||
Vector2 sectionVertex = section.vertices[j];
|
||||
|
||||
// calculate normal using section vertex, curve normal and binormal:
|
||||
normal.x = (sectionVertex.x * curve.Data[i].normal.x + sectionVertex.y * curve.Data[i].binormal.x) * sectionThickness;
|
||||
normal.y = (sectionVertex.x * curve.Data[i].normal.y + sectionVertex.y * curve.Data[i].binormal.y) * sectionThickness;
|
||||
normal.z = (sectionVertex.x * curve.Data[i].normal.z + sectionVertex.y * curve.Data[i].binormal.z) * sectionThickness;
|
||||
|
||||
// offset curve position by normal:
|
||||
vertex.x = curve.Data[i].position.x + normal.x;
|
||||
vertex.y = curve.Data[i].position.y + normal.y;
|
||||
vertex.z = curve.Data[i].position.z + normal.z;
|
||||
|
||||
// cross(normal, curve tangent)
|
||||
texTangent.x = normal.y * curve.Data[i].tangent.z - normal.z * curve.Data[i].tangent.y;
|
||||
texTangent.y = normal.z * curve.Data[i].tangent.x - normal.x * curve.Data[i].tangent.z;
|
||||
texTangent.z = normal.x * curve.Data[i].tangent.y - normal.y * curve.Data[i].tangent.x;
|
||||
texTangent.w = -1;
|
||||
|
||||
uv.x = (j / (float)sectionSegments) * uvScale.x;
|
||||
uv.y = vCoord;
|
||||
|
||||
vertices.Add(vertex);
|
||||
normals.Add(normal);
|
||||
tangents.Add(texTangent);
|
||||
vertColors.Add(curve.Data[i].color);
|
||||
uvs.Add(uv);
|
||||
|
||||
if (j < sectionSegments && i < curve.Count - 1)
|
||||
{
|
||||
tris.Add(sectionIndex * verticesPerSection + j);
|
||||
tris.Add(nextSectionIndex * verticesPerSection + j);
|
||||
tris.Add(sectionIndex * verticesPerSection + (j + 1));
|
||||
|
||||
tris.Add(sectionIndex * verticesPerSection + (j + 1));
|
||||
tris.Add(nextSectionIndex * verticesPerSection + j);
|
||||
tris.Add(nextSectionIndex * verticesPerSection + (j + 1));
|
||||
}
|
||||
}
|
||||
sectionIndex++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
CommitMeshData();
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearMeshData()
|
||||
{
|
||||
extrudedMesh.Clear();
|
||||
vertices.Clear();
|
||||
normals.Clear();
|
||||
tangents.Clear();
|
||||
uvs.Clear();
|
||||
vertColors.Clear();
|
||||
tris.Clear();
|
||||
}
|
||||
|
||||
private void CommitMeshData()
|
||||
{
|
||||
extrudedMesh.SetVertices(vertices);
|
||||
extrudedMesh.SetNormals(normals);
|
||||
extrudedMesh.SetTangents(tangents);
|
||||
extrudedMesh.SetColors(vertColors);
|
||||
extrudedMesh.SetUVs(0, uvs);
|
||||
extrudedMesh.SetTriangles(tris, 0, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c4747da60837c44f9ba4b4a86879bcc8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences:
|
||||
- extrudedMesh: {instanceID: 0}
|
||||
- section: {fileID: 11400000, guid: a0bc36a59515f413e90e10895929c938, type: 2}
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: a552ed0e1c0fd47c38eeff6d60b5b115, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
199
Assets/Obi/Scripts/RopeAndRod/Rendering/ObiRopeLineRenderer.cs
Normal file
199
Assets/Obi/Scripts/RopeAndRod/Rendering/ObiRopeLineRenderer.cs
Normal file
@@ -0,0 +1,199 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using Unity.Profiling;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
[AddComponentMenu("Physics/Obi/Obi Rope Line Renderer", 884)]
|
||||
[ExecuteInEditMode]
|
||||
[RequireComponent(typeof(MeshRenderer))]
|
||||
[RequireComponent(typeof(MeshFilter))]
|
||||
[RequireComponent(typeof(ObiPathSmoother))]
|
||||
public class ObiRopeLineRenderer : MonoBehaviour
|
||||
{
|
||||
static ProfilerMarker m_UpdateLineRopeRendererChunksPerfMarker = new ProfilerMarker("UpdateLineRopeRenderer");
|
||||
|
||||
private List<Vector3> vertices = new List<Vector3>();
|
||||
private List<Vector3> normals = new List<Vector3>();
|
||||
private List<Vector4> tangents = new List<Vector4>();
|
||||
private List<Vector2> uvs = new List<Vector2>();
|
||||
private List<Color> vertColors = new List<Color>();
|
||||
private List<int> tris = new List<int>();
|
||||
|
||||
ObiRopeBase rope;
|
||||
ObiPathSmoother smoother;
|
||||
|
||||
#if (UNITY_2019_1_OR_NEWER)
|
||||
System.Action<ScriptableRenderContext, Camera> renderCallback;
|
||||
#endif
|
||||
|
||||
[HideInInspector] [NonSerialized] public Mesh lineMesh;
|
||||
|
||||
[Range(0, 1)]
|
||||
public float uvAnchor = 0; /**< Normalized position of texture coordinate origin along rope.*/
|
||||
|
||||
public Vector2 uvScale = Vector2.one; /**< Scaling of uvs along rope.*/
|
||||
|
||||
public bool normalizeV = true;
|
||||
|
||||
public float thicknessScale = 0.8f; /**< Scales section thickness.*/
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
|
||||
CreateMeshIfNeeded();
|
||||
|
||||
#if (UNITY_2019_1_OR_NEWER)
|
||||
renderCallback = new System.Action<ScriptableRenderContext, Camera>((cntxt, cam) => { UpdateRenderer(cam); });
|
||||
RenderPipelineManager.beginCameraRendering += renderCallback;
|
||||
#endif
|
||||
Camera.onPreCull += UpdateRenderer;
|
||||
|
||||
rope = GetComponent<ObiRopeBase>();
|
||||
smoother = GetComponent<ObiPathSmoother>();
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
|
||||
#if (UNITY_2019_1_OR_NEWER)
|
||||
RenderPipelineManager.beginCameraRendering -= renderCallback;
|
||||
#endif
|
||||
Camera.onPreCull -= UpdateRenderer;
|
||||
|
||||
GameObject.DestroyImmediate(lineMesh);
|
||||
}
|
||||
|
||||
private void CreateMeshIfNeeded()
|
||||
{
|
||||
if (lineMesh == null)
|
||||
{
|
||||
lineMesh = new Mesh();
|
||||
lineMesh.name = "extrudedMesh";
|
||||
lineMesh.MarkDynamic();
|
||||
GetComponent<MeshFilter>().mesh = lineMesh;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateRenderer(Camera camera)
|
||||
{
|
||||
using (m_UpdateLineRopeRendererChunksPerfMarker.Auto())
|
||||
{
|
||||
|
||||
if (camera == null || !rope.gameObject.activeInHierarchy)
|
||||
return;
|
||||
|
||||
CreateMeshIfNeeded();
|
||||
ClearMeshData();
|
||||
|
||||
float actualToRestLengthRatio = smoother.SmoothLength / rope.restLength;
|
||||
|
||||
float vCoord = -uvScale.y * rope.restLength * uvAnchor; // v texture coordinate.
|
||||
int sectionIndex = 0;
|
||||
|
||||
Vector3 localSpaceCamera = rope.transform.InverseTransformPoint(camera.transform.position);
|
||||
Vector3 vertex = Vector3.zero, normal = Vector3.zero;
|
||||
Vector4 bitangent = Vector4.zero;
|
||||
Vector2 uv = Vector2.zero;
|
||||
|
||||
for (int c = 0; c < smoother.smoothChunks.Count; ++c)
|
||||
{
|
||||
|
||||
ObiList<ObiPathFrame> curve = smoother.smoothChunks[c];
|
||||
|
||||
for (int i = 0; i < curve.Count; ++i)
|
||||
{
|
||||
|
||||
// Calculate previous and next curve indices:
|
||||
int prevIndex = Mathf.Max(i - 1, 0);
|
||||
|
||||
// advance v texcoord:
|
||||
vCoord += uvScale.y * (Vector3.Distance(curve.Data[i].position, curve.Data[prevIndex].position) /
|
||||
(normalizeV ? smoother.SmoothLength : actualToRestLengthRatio));
|
||||
|
||||
// calculate section thickness (either constant, or particle radius based):
|
||||
float sectionThickness = curve.Data[i].thickness * thicknessScale;
|
||||
|
||||
|
||||
normal.x = curve.Data[i].position.x - localSpaceCamera.x;
|
||||
normal.y = curve.Data[i].position.y - localSpaceCamera.y;
|
||||
normal.z = curve.Data[i].position.z - localSpaceCamera.z;
|
||||
normal.Normalize();
|
||||
|
||||
bitangent.x = -(normal.y * curve.Data[i].tangent.z - normal.z * curve.Data[i].tangent.y);
|
||||
bitangent.y = -(normal.z * curve.Data[i].tangent.x - normal.x * curve.Data[i].tangent.z);
|
||||
bitangent.z = -(normal.x * curve.Data[i].tangent.y - normal.y * curve.Data[i].tangent.x);
|
||||
bitangent.w = 0;
|
||||
bitangent.Normalize();
|
||||
|
||||
vertex.x = curve.Data[i].position.x - bitangent.x * sectionThickness;
|
||||
vertex.y = curve.Data[i].position.y - bitangent.y * sectionThickness;
|
||||
vertex.z = curve.Data[i].position.z - bitangent.z * sectionThickness;
|
||||
vertices.Add(vertex);
|
||||
|
||||
vertex.x = curve.Data[i].position.x + bitangent.x * sectionThickness;
|
||||
vertex.y = curve.Data[i].position.y + bitangent.y * sectionThickness;
|
||||
vertex.z = curve.Data[i].position.z + bitangent.z * sectionThickness;
|
||||
vertices.Add(vertex);
|
||||
|
||||
normals.Add(-normal);
|
||||
normals.Add(-normal);
|
||||
|
||||
bitangent.w = 1;
|
||||
tangents.Add(bitangent);
|
||||
tangents.Add(bitangent);
|
||||
|
||||
vertColors.Add(curve.Data[i].color);
|
||||
vertColors.Add(curve.Data[i].color);
|
||||
|
||||
uv.x = 0; uv.y = vCoord;
|
||||
uvs.Add(uv);
|
||||
uv.x = 1;
|
||||
uvs.Add(uv);
|
||||
|
||||
if (i < curve.Count - 1)
|
||||
{
|
||||
tris.Add(sectionIndex * 2);
|
||||
tris.Add((sectionIndex + 1) * 2);
|
||||
tris.Add(sectionIndex * 2 + 1);
|
||||
|
||||
tris.Add(sectionIndex * 2 + 1);
|
||||
tris.Add((sectionIndex + 1) * 2);
|
||||
tris.Add((sectionIndex + 1) * 2 + 1);
|
||||
}
|
||||
|
||||
sectionIndex++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
CommitMeshData();
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearMeshData()
|
||||
{
|
||||
lineMesh.Clear();
|
||||
vertices.Clear();
|
||||
normals.Clear();
|
||||
tangents.Clear();
|
||||
uvs.Clear();
|
||||
vertColors.Clear();
|
||||
tris.Clear();
|
||||
}
|
||||
|
||||
private void CommitMeshData()
|
||||
{
|
||||
lineMesh.SetVertices(vertices);
|
||||
lineMesh.SetNormals(normals);
|
||||
lineMesh.SetTangents(tangents);
|
||||
lineMesh.SetColors(vertColors);
|
||||
lineMesh.SetUVs(0, uvs);
|
||||
lineMesh.SetTriangles(tris, 0, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 18f853588397d4a80a561203ed92fc8a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 905a18c273af443d6bc588b59ebd56f6, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
260
Assets/Obi/Scripts/RopeAndRod/Rendering/ObiRopeMeshRenderer.cs
Normal file
260
Assets/Obi/Scripts/RopeAndRod/Rendering/ObiRopeMeshRenderer.cs
Normal file
@@ -0,0 +1,260 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Unity.Profiling;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
[AddComponentMenu("Physics/Obi/Obi Rope Mesh Renderer", 886)]
|
||||
[ExecuteInEditMode]
|
||||
[RequireComponent(typeof(MeshRenderer))]
|
||||
[RequireComponent(typeof(MeshFilter))]
|
||||
[RequireComponent(typeof(ObiPathSmoother))]
|
||||
public class ObiRopeMeshRenderer : MonoBehaviour
|
||||
{
|
||||
static ProfilerMarker m_UpdateMeshRopeRendererChunksPerfMarker = new ProfilerMarker("UpdateMeshRopeRenderer");
|
||||
|
||||
[SerializeProperty("SourceMesh")]
|
||||
[SerializeField] private Mesh mesh;
|
||||
|
||||
[SerializeProperty("SweepAxis")]
|
||||
[SerializeField] private ObiPathFrame.Axis axis;
|
||||
|
||||
public float volumeScaling = 0;
|
||||
public bool stretchWithRope = true;
|
||||
public bool spanEntireLength = true;
|
||||
|
||||
[SerializeProperty("Instances")]
|
||||
[SerializeField] private int instances = 1;
|
||||
|
||||
[SerializeProperty("InstanceSpacing")]
|
||||
[SerializeField] private float instanceSpacing = 1;
|
||||
|
||||
public float offset = 0;
|
||||
public Vector3 scale = Vector3.one;
|
||||
|
||||
[HideInInspector] [SerializeField] private float meshSizeAlongAxis = 1;
|
||||
|
||||
private Vector3[] inputVertices;
|
||||
private Vector3[] inputNormals;
|
||||
private Vector4[] inputTangents;
|
||||
|
||||
private Vector3[] vertices;
|
||||
private Vector3[] normals;
|
||||
private Vector4[] tangents;
|
||||
|
||||
private int[] orderedVertices = new int[0];
|
||||
|
||||
private ObiPathSmoother smoother;
|
||||
|
||||
public Mesh SourceMesh
|
||||
{
|
||||
set { mesh = value; PreprocessInputMesh(); }
|
||||
get { return mesh; }
|
||||
}
|
||||
|
||||
public ObiPathFrame.Axis SweepAxis
|
||||
{
|
||||
set { axis = value; PreprocessInputMesh(); }
|
||||
get { return axis; }
|
||||
}
|
||||
|
||||
public int Instances
|
||||
{
|
||||
set { instances = value; PreprocessInputMesh(); }
|
||||
get { return instances; }
|
||||
}
|
||||
|
||||
public float InstanceSpacing
|
||||
{
|
||||
set { instanceSpacing = value; PreprocessInputMesh(); }
|
||||
get { return instanceSpacing; }
|
||||
}
|
||||
|
||||
[HideInInspector] [NonSerialized] public Mesh deformedMesh;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
smoother = GetComponent<ObiPathSmoother>();
|
||||
smoother.OnCurveGenerated += UpdateRenderer;
|
||||
PreprocessInputMesh();
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
smoother.OnCurveGenerated -= UpdateRenderer;
|
||||
GameObject.DestroyImmediate(deformedMesh);
|
||||
}
|
||||
|
||||
private void PreprocessInputMesh()
|
||||
{
|
||||
|
||||
if (deformedMesh == null)
|
||||
{
|
||||
deformedMesh = new Mesh();
|
||||
deformedMesh.name = "deformedMesh";
|
||||
deformedMesh.MarkDynamic();
|
||||
GetComponent<MeshFilter>().mesh = deformedMesh;
|
||||
}
|
||||
|
||||
deformedMesh.Clear();
|
||||
|
||||
if (mesh == null)
|
||||
{
|
||||
orderedVertices = new int[0];
|
||||
return;
|
||||
}
|
||||
|
||||
// Clamp instance count to a positive value.
|
||||
instances = Mathf.Max(0, instances);
|
||||
|
||||
// combine all mesh instances into a single mesh:
|
||||
Mesh combinedMesh = new Mesh();
|
||||
CombineInstance[] meshInstances = new CombineInstance[instances];
|
||||
Vector3 pos = Vector3.zero;
|
||||
|
||||
// initial offset for the combined mesh is half the size of its bounding box in the swept axis:
|
||||
pos[(int)axis] = mesh.bounds.extents[(int)axis];
|
||||
|
||||
for (int i = 0; i < instances; ++i)
|
||||
{
|
||||
meshInstances[i].mesh = mesh;
|
||||
meshInstances[i].transform = Matrix4x4.TRS(pos, Quaternion.identity, Vector3.one);
|
||||
pos[(int)axis] = mesh.bounds.extents[(int)axis] + (i + 1) * mesh.bounds.size[(int)axis] * instanceSpacing;
|
||||
}
|
||||
combinedMesh.CombineMeshes(meshInstances, true, true);
|
||||
|
||||
// get combined mesh data:
|
||||
inputVertices = combinedMesh.vertices;
|
||||
inputNormals = combinedMesh.normals;
|
||||
inputTangents = combinedMesh.tangents;
|
||||
|
||||
// sort vertices along curve axis:
|
||||
float[] keys = new float[inputVertices.Length];
|
||||
orderedVertices = new int[inputVertices.Length];
|
||||
|
||||
for (int i = 0; i < keys.Length; ++i)
|
||||
{
|
||||
keys[i] = inputVertices[i][(int)axis];
|
||||
orderedVertices[i] = i;
|
||||
}
|
||||
|
||||
Array.Sort(keys, orderedVertices);
|
||||
|
||||
// Copy the combined mesh data to deform it:
|
||||
deformedMesh.vertices = combinedMesh.vertices;
|
||||
deformedMesh.normals = combinedMesh.normals;
|
||||
deformedMesh.tangents = combinedMesh.tangents;
|
||||
deformedMesh.uv = combinedMesh.uv;
|
||||
deformedMesh.uv2 = combinedMesh.uv2;
|
||||
deformedMesh.uv3 = combinedMesh.uv3;
|
||||
deformedMesh.uv4 = combinedMesh.uv4;
|
||||
deformedMesh.colors = combinedMesh.colors;
|
||||
deformedMesh.triangles = combinedMesh.triangles;
|
||||
|
||||
vertices = deformedMesh.vertices;
|
||||
normals = deformedMesh.normals;
|
||||
tangents = deformedMesh.tangents;
|
||||
|
||||
// Calculate scale along swept axis so that the mesh spans the entire lenght of the rope if required.
|
||||
meshSizeAlongAxis = combinedMesh.bounds.size[(int)axis];
|
||||
|
||||
// destroy combined mesh:
|
||||
GameObject.DestroyImmediate(combinedMesh);
|
||||
|
||||
}
|
||||
|
||||
public void UpdateRenderer(ObiActor actor)
|
||||
{
|
||||
using (m_UpdateMeshRopeRendererChunksPerfMarker.Auto())
|
||||
{
|
||||
|
||||
if (mesh == null)
|
||||
return;
|
||||
|
||||
if (smoother.smoothChunks.Count == 0)
|
||||
return;
|
||||
|
||||
ObiList<ObiPathFrame> curve = smoother.smoothChunks[0];
|
||||
|
||||
if (curve.Count < 2)
|
||||
return;
|
||||
|
||||
var rope = actor as ObiRopeBase;
|
||||
|
||||
float actualToRestLengthRatio = stretchWithRope ? smoother.SmoothLength / rope.restLength : 1;
|
||||
|
||||
// squashing factor, makes mesh thinner when stretched and thicker when compresssed.
|
||||
float squashing = Mathf.Clamp(1 + volumeScaling * (1 / Mathf.Max(actualToRestLengthRatio, 0.01f) - 1), 0.01f, 2);
|
||||
|
||||
// Calculate scale along swept axis so that the mesh spans the entire lenght of the rope if required.
|
||||
Vector3 actualScale = scale;
|
||||
if (spanEntireLength)
|
||||
actualScale[(int)axis] = rope.restLength / meshSizeAlongAxis;
|
||||
|
||||
float previousVertexValue = 0;
|
||||
float meshLength = 0;
|
||||
int index = 0;
|
||||
int nextIndex = 1;
|
||||
int prevIndex = 0;
|
||||
float sectionMagnitude = Vector3.Distance(curve[index].position, curve[nextIndex].position);
|
||||
|
||||
// basis matrix for deforming the mesh:
|
||||
Matrix4x4 basis = curve[0].ToMatrix(axis);
|
||||
|
||||
for (int i = 0; i < orderedVertices.Length; ++i)
|
||||
{
|
||||
|
||||
int vIndex = orderedVertices[i];
|
||||
float vertexValue = inputVertices[vIndex][(int)axis] * actualScale[(int)axis] + offset;
|
||||
|
||||
// Calculate how much we've advanced in the sort axis since the last vertex:
|
||||
meshLength += (vertexValue - previousVertexValue) * actualToRestLengthRatio;
|
||||
previousVertexValue = vertexValue;
|
||||
|
||||
// If we have advanced to the next section of the curve:
|
||||
while (meshLength > sectionMagnitude && sectionMagnitude > Mathf.Epsilon)
|
||||
{
|
||||
|
||||
meshLength -= sectionMagnitude;
|
||||
index = Mathf.Min(index + 1, curve.Count - 1);
|
||||
|
||||
// Calculate previous and next curve indices:
|
||||
nextIndex = Mathf.Min(index + 1, curve.Count - 1);
|
||||
prevIndex = Mathf.Max(index - 1, 0);
|
||||
|
||||
// Calculate current tangent as the vector between previous and next curve points:
|
||||
sectionMagnitude = Vector3.Distance(curve[index].position, curve[nextIndex].position);
|
||||
|
||||
// Update basis matrix:
|
||||
basis = curve[index].ToMatrix(axis);
|
||||
|
||||
}
|
||||
|
||||
float sectionThickness = curve[index].thickness;
|
||||
|
||||
// calculate deformed vertex position:
|
||||
Vector3 offsetFromCurve = Vector3.Scale(inputVertices[vIndex], actualScale * sectionThickness * squashing);
|
||||
offsetFromCurve[(int)axis] = meshLength;
|
||||
|
||||
vertices[vIndex] = curve[index].position + basis.MultiplyVector(offsetFromCurve);
|
||||
normals[vIndex] = basis.MultiplyVector(inputNormals[vIndex]);
|
||||
tangents[vIndex] = basis * inputTangents[vIndex]; // avoids expensive implicit conversion from Vector4 to Vector3.
|
||||
tangents[vIndex].w = inputTangents[vIndex].w;
|
||||
}
|
||||
|
||||
CommitMeshData();
|
||||
}
|
||||
}
|
||||
|
||||
private void CommitMeshData()
|
||||
{
|
||||
deformedMesh.vertices = vertices;
|
||||
deformedMesh.normals = normals;
|
||||
deformedMesh.tangents = tangents;
|
||||
deformedMesh.RecalculateBounds();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4ec7e70e3318044b69e11352b1f2136b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: c5afdc6c291b44a68be8bf66702358a5, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Obi/Scripts/RopeAndRod/Utils.meta
Normal file
8
Assets/Obi/Scripts/RopeAndRod/Utils.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 55cc87411ee7647b4a078becb6a5ed01
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
22
Assets/Obi/Scripts/RopeAndRod/Utils/ObiRopeAttach.cs
Normal file
22
Assets/Obi/Scripts/RopeAndRod/Utils/ObiRopeAttach.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
public class ObiRopeAttach : MonoBehaviour
|
||||
{
|
||||
public ObiPathSmoother smoother;
|
||||
[Range(0,1)]
|
||||
public float m;
|
||||
|
||||
public void LateUpdate()
|
||||
{
|
||||
if (smoother != null)
|
||||
{
|
||||
ObiPathFrame section = smoother.GetSectionAt(m);
|
||||
transform.position = smoother.transform.TransformPoint(section.position);
|
||||
transform.rotation = smoother.transform.rotation * (Quaternion.LookRotation(section.tangent, section.binormal));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
11
Assets/Obi/Scripts/RopeAndRod/Utils/ObiRopeAttach.cs.meta
Normal file
11
Assets/Obi/Scripts/RopeAndRod/Utils/ObiRopeAttach.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 93cd44c04a2944349b4fe311545b7e05
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
106
Assets/Obi/Scripts/RopeAndRod/Utils/ObiRopePrefabPlugger.cs
Normal file
106
Assets/Obi/Scripts/RopeAndRod/Utils/ObiRopePrefabPlugger.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
/**
|
||||
* This component plugs a prefab instance at each cut in the rope. Optionally, it will also place a couple instances at the start/end of an open rope.
|
||||
*/
|
||||
[RequireComponent(typeof(ObiRope))]
|
||||
[RequireComponent(typeof(ObiPathSmoother))]
|
||||
public class ObiRopePrefabPlugger : MonoBehaviour
|
||||
{
|
||||
public GameObject prefab; /**< prefab object being instantiated at the rope cuts.*/
|
||||
public Vector3 instanceScale = Vector3.one;
|
||||
public bool plugTears = true;
|
||||
public bool plugStart = false;
|
||||
public bool plugEnd = false;
|
||||
|
||||
private List<GameObject> instances; /**< instances of the prefab being rendered. */
|
||||
private ObiPathSmoother smoother;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
instances = new List<GameObject>();
|
||||
smoother = GetComponent<ObiPathSmoother>();
|
||||
smoother.OnCurveGenerated += UpdatePlugs;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
smoother.OnCurveGenerated -= UpdatePlugs;
|
||||
ClearPrefabInstances();
|
||||
}
|
||||
|
||||
private GameObject GetOrCreatePrefabInstance(int index)
|
||||
{
|
||||
if (index < instances.Count)
|
||||
return instances[index];
|
||||
|
||||
GameObject tearPrefabInstance = Instantiate(prefab);
|
||||
tearPrefabInstance.hideFlags = HideFlags.HideAndDontSave;
|
||||
instances.Add(tearPrefabInstance);
|
||||
return tearPrefabInstance;
|
||||
}
|
||||
|
||||
public void ClearPrefabInstances()
|
||||
{
|
||||
for (int i = 0; i < instances.Count; ++i)
|
||||
DestroyImmediate(instances[i]);
|
||||
|
||||
instances.Clear();
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void UpdatePlugs(ObiActor actor)
|
||||
{
|
||||
var rope = actor as ObiRopeBase;
|
||||
|
||||
// cache the rope's transform matrix/quaternion:
|
||||
Matrix4x4 l2w = rope.transform.localToWorldMatrix;
|
||||
Quaternion l2wRot = l2w.rotation;
|
||||
|
||||
int instanceIndex = 0;
|
||||
|
||||
// place prefabs at the start/end of each curve:
|
||||
for (int c = 0; c < smoother.smoothChunks.Count; ++c)
|
||||
{
|
||||
ObiList<ObiPathFrame> curve = smoother.smoothChunks[c];
|
||||
|
||||
if ((plugTears && c > 0) ||
|
||||
(plugStart && c == 0))
|
||||
{
|
||||
var instance = GetOrCreatePrefabInstance(instanceIndex++);
|
||||
instance.SetActive(true);
|
||||
|
||||
ObiPathFrame frame = curve[0];
|
||||
instance.transform.position = l2w.MultiplyPoint3x4(frame.position);
|
||||
instance.transform.rotation = l2wRot * (Quaternion.LookRotation(-frame.tangent, frame.binormal));
|
||||
instance.transform.localScale = instanceScale;
|
||||
}
|
||||
|
||||
if ((plugTears && c < smoother.smoothChunks.Count - 1) ||
|
||||
(plugEnd && c == smoother.smoothChunks.Count - 1))
|
||||
{
|
||||
|
||||
var instance = GetOrCreatePrefabInstance(instanceIndex++);
|
||||
instance.SetActive(true);
|
||||
|
||||
ObiPathFrame frame = curve[curve.Count - 1];
|
||||
instance.transform.position = l2w.MultiplyPoint3x4(frame.position);
|
||||
instance.transform.rotation = l2wRot * (Quaternion.LookRotation(frame.tangent, frame.binormal));
|
||||
instance.transform.localScale = instanceScale;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// deactivate remaining instances:
|
||||
for (int i = instanceIndex; i < instances.Count; ++i)
|
||||
instances[i].SetActive(false);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4514513daf5b14cb689daa23e9d8575e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
53
Assets/Obi/Scripts/RopeAndRod/Utils/ObiRopeReel.cs
Normal file
53
Assets/Obi/Scripts/RopeAndRod/Utils/ObiRopeReel.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
[RequireComponent(typeof(ObiRopeCursor))]
|
||||
public class ObiRopeReel : MonoBehaviour
|
||||
{
|
||||
private ObiRopeCursor cursor;
|
||||
private ObiRope rope;
|
||||
|
||||
[Header("Roll out/in thresholds")]
|
||||
public float outThreshold = 0.8f;
|
||||
public float inThreshold = 0.4f;
|
||||
|
||||
[Header("Roll out/in speeds")]
|
||||
public float outSpeed = 0.05f;
|
||||
public float inSpeed = 0.15f;
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
cursor = GetComponent<ObiRopeCursor>();
|
||||
rope = GetComponent<ObiRope>();
|
||||
}
|
||||
|
||||
public void OnValidate()
|
||||
{
|
||||
// Make sure the range thresholds don't cross:
|
||||
outThreshold = Mathf.Max(inThreshold, outThreshold);
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
// get current and rest lengths:
|
||||
float length = rope.CalculateLength();
|
||||
float restLength = rope.restLength;
|
||||
|
||||
// calculate difference between current length and rest length:
|
||||
float diff = Mathf.Max(0, length - restLength);
|
||||
|
||||
// if the rope has been stretched beyond the reel out threshold, increase its rest length:
|
||||
if (diff > outThreshold)
|
||||
restLength += diff * outSpeed;
|
||||
|
||||
// if the rope is not stretched past the reel in threshold, decrease its rest length:
|
||||
if (diff < inThreshold)
|
||||
restLength -= diff * inSpeed;
|
||||
|
||||
// set the new rest length:
|
||||
cursor.ChangeLength(restLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Obi/Scripts/RopeAndRod/Utils/ObiRopeReel.cs.meta
Normal file
11
Assets/Obi/Scripts/RopeAndRod/Utils/ObiRopeReel.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4793f08b8350d44db99d1bc98daf625b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user