添加插件

This commit is contained in:
2025-11-10 00:08:26 +08:00
parent 4059c207c0
commit 76f80db694
2814 changed files with 436400 additions and 178 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: abc784de05f944701a4dff27d147b35f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,705 @@
using UnityEngine;
using System;
using System.Collections.Generic;
namespace Obi
{
[AddComponentMenu("Physics/Obi/Obi Bone", 882)]
[ExecuteInEditMode]
[DisallowMultipleComponent]
[DefaultExecutionOrder(100)] // make sure ObiBone's LateUpdate is updated after ObiSolver's.
public class ObiBone : ObiActor, IStretchShearConstraintsUser, IBendTwistConstraintsUser, ISkinConstraintsUser, IAerodynamicConstraintsUser
{
[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);
// aerodynamics
[SerializeField] protected bool _aerodynamicsEnabled = true;
[SerializeField] protected BonePropertyCurve _drag = new BonePropertyCurve(0.05f, 1);
[SerializeField] protected BonePropertyCurve _lift = new BonePropertyCurve(0.02f, 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); }
}
/// <summary>
/// Whether this actor's aerodynamic constraints are enabled.
/// </summary>
public bool aerodynamicsEnabled
{
get { return _aerodynamicsEnabled; }
set { if (value != _aerodynamicsEnabled) { _aerodynamicsEnabled = value; SetConstraintsDirty(Oni.ConstraintType.Aerodynamics); } }
}
/// <summary>
/// Aerodynamic drag value.
/// </summary>
public BonePropertyCurve drag
{
get { return _drag; }
set { _drag = value; SetConstraintsDirty(Oni.ConstraintType.Aerodynamics); }
}
/// <summary>
/// Aerodynamic lift value.
/// </summary>
public BonePropertyCurve lift
{
get { return _lift; }
set { _lift = value; SetConstraintsDirty(Oni.ConstraintType.Aerodynamics); }
}
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()
{
// TODO: guard against having another ObiBone above it in hierarchy.
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();
}
}
internal override void LoadBlueprint()
{
base.LoadBlueprint();
// synchronously read required data from GPU:
solver.renderablePositions.Readback(false);
solver.renderableOrientations.Readback(false);
solver.orientations.Readback(false);
solver.angularVelocities.Readback(false);
SetupRuntimeConstraints();
ResetToCurrentShape();
}
internal override void UnloadBlueprint()
{
ResetParticles();
CopyParticleDataToTransforms();
base.UnloadBlueprint();
}
public override void RequestReadback()
{
base.RequestReadback();
solver.orientations.Readback();
solver.angularVelocities.Readback();
solver.renderablePositions.Readback();
solver.renderableOrientations.Readback();
}
public override void SimulationEnd(float simulatedTime, float substepTime)
{
base.SimulationEnd(simulatedTime, substepTime);
solver.orientations.WaitForReadback();
solver.angularVelocities.WaitForReadback();
solver.renderablePositions.WaitForReadback();
solver.renderableOrientations.WaitForReadback();
}
private void SetupRuntimeConstraints()
{
SetConstraintsDirty(Oni.ConstraintType.Skin);
SetConstraintsDirty(Oni.ConstraintType.StretchShear);
SetConstraintsDirty(Oni.ConstraintType.BendTwist);
SetConstraintsDirty(Oni.ConstraintType.Aerodynamics);
SetSelfCollisions(selfCollisions);
SetSimplicesDirty();
UpdateFilter();
}
public override void ProvideDeformableEdges(ObiNativeIntList deformableEdges)
{
var boneBprint = sharedBlueprint as ObiBoneBlueprint;
if (boneBprint != null && boneBprint.deformableEdges != null)
{
// Send deformable edge indices to the solver:
for (int i = 0; i < boneBprint.deformableEdges.Length; ++i)
deformableEdges.Add(solverIndices[boneBprint.deformableEdges[i]]);
}
}
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.startPositions[rootIndex] = solver.endPositions[rootIndex] = solver.positions[rootIndex] = actor2Solver.MultiplyPoint3x4(Vector3.zero);
// take particle rest orientation in actor space, and convert to solver space:
solver.startOrientations[rootIndex] = solver.endOrientations[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 boneOverride = boneBlueprint.GetOverride(i, out float normalizedCoord);
var radii = Vector3.one * (boneOverride != null ? boneOverride.radius.Evaluate(normalizedCoord) : 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 boneOverride = boneBlueprint.GetOverride(i, out float normalizedCoord);
var invMass = ObiUtils.MassToInvMass(boneOverride != null ? boneOverride .mass.Evaluate(normalizedCoord) : mass.Evaluate(normalizedCoord));
var invRotMass = ObiUtils.MassToInvMass(boneOverride != null ? boneOverride.rotationalMass.Evaluate(normalizedCoord) : 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)
{
var boneOverride = boneBlueprint.GetOverride(batch.particleIndices[constraintIndex], out float normalizedCoord);
return new Vector3(boneOverride != null ? boneOverride.skinRadius.Evaluate(normalizedCoord) : skinRadius.Evaluate(normalizedCoord), 0, 0);
}
public float GetSkinCompliance(ObiSkinConstraintsBatch batch, int constraintIndex)
{
var boneOverride = boneBlueprint.GetOverride(batch.particleIndices[constraintIndex], out float normalizedCoord);
return boneOverride != null ? boneOverride.skinCompliance.Evaluate(normalizedCoord) : skinCompliance.Evaluate(normalizedCoord);
}
public Vector3 GetBendTwistCompliance(ObiBendTwistConstraintsBatch batch, int constraintIndex)
{
var boneOverride = boneBlueprint.GetOverride(batch.particleIndices[constraintIndex * 2], out float normalizedCoord);
if (boneOverride != null)
return new Vector3(boneOverride.bend1Compliance.Evaluate(normalizedCoord),
boneOverride.bend2Compliance.Evaluate(normalizedCoord),
boneOverride.torsionCompliance.Evaluate(normalizedCoord));
return new Vector3(bend1Compliance.Evaluate(normalizedCoord),
bend2Compliance.Evaluate(normalizedCoord),
torsionCompliance.Evaluate(normalizedCoord));
}
public Vector2 GetBendTwistPlasticity(ObiBendTwistConstraintsBatch batch, int constraintIndex)
{
var boneOverride = boneBlueprint.GetOverride(batch.particleIndices[constraintIndex * 2], out float normalizedCoord);
if (boneOverride != null)
return new Vector2(boneOverride.plasticYield.Evaluate(normalizedCoord),
boneOverride.plasticCreep.Evaluate(normalizedCoord));
return new Vector2(plasticYield.Evaluate(normalizedCoord),
plasticCreep.Evaluate(normalizedCoord));
}
public Vector3 GetStretchShearCompliance(ObiStretchShearConstraintsBatch batch, int constraintIndex)
{
var boneOverride = boneBlueprint.GetOverride(batch.particleIndices[constraintIndex * 2], out float normalizedCoord);
if (boneOverride != null)
return new Vector3(boneOverride.shear1Compliance.Evaluate(normalizedCoord),
boneOverride.shear2Compliance.Evaluate(normalizedCoord),
boneOverride.stretchCompliance.Evaluate(normalizedCoord));
return new Vector3(shear1Compliance.Evaluate(normalizedCoord),
shear2Compliance.Evaluate(normalizedCoord),
stretchCompliance.Evaluate(normalizedCoord));
}
public float GetDrag(ObiAerodynamicConstraintsBatch batch, int constraintIndex)
{
var boneOverride = boneBlueprint.GetOverride(batch.particleIndices[constraintIndex], out float normalizedCoord);
return boneOverride != null ? boneOverride.drag.Evaluate(normalizedCoord) : drag.Evaluate(normalizedCoord);
}
public float GetLift(ObiAerodynamicConstraintsBatch batch, int constraintIndex)
{
var boneOverride = boneBlueprint.GetOverride(batch.particleIndices[constraintIndex], out float normalizedCoord);
return boneOverride != null ? boneOverride.lift.Evaluate(normalizedCoord) : lift.Evaluate(normalizedCoord);
}
public void FixedUpdate()
{
// This resets all bones not affected by animation,
// needs to happen once per frame at the very start before Animators are updated.
ResetReferenceOrientations();
}
public override void SimulationStart(float timeToSimulate, float substepTime)
{
base.SimulationStart(timeToSimulate, substepTime);
if (fixRoot)
FixRoot();
UpdateRestShape();
}
public void LateUpdate()
{
if (Application.isPlaying && isActiveAndEnabled)
CopyParticleDataToTransforms();
}
/// <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.startPositions[solverIndex] = solver.endPositions[solverIndex] = solver.positions[solverIndex] = world2Solver.MultiplyPoint3x4(trfm.position);
var boneDeltaAWS = trfm.rotation * Quaternion.Inverse(boneBlueprint.restOrientations[i]);
solver.startOrientations[solverIndex] = solver.endOrientations[solverIndex] = solver.orientations[solverIndex] = world2Solver.rotation * boneDeltaAWS * boneBlueprint.root2WorldR * boneBlueprint.orientations[i];
}
// Update constraint data in the blueprint, since StartSimulation won't be called until next frame.
var bc = GetConstraintsByType(Oni.ConstraintType.BendTwist) as ObiConstraints<ObiBendTwistConstraintsBatch>;
if (bc != null)
for (int j = 0; j < bc.batchCount; ++j)
{
var batch = bc.GetBatch(j) as ObiBendTwistConstraintsBatch;
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];
batch.restDarbouxVectors[i] = ObiUtils.RestDarboux(orientationA, orientationB);
}
}
var sc = GetConstraintsByType(Oni.ConstraintType.Skin) as ObiConstraints<ObiSkinConstraintsBatch>;
if (sc != null)
for (int j = 0; j < sc.batchCount; ++j)
{
var batch = sc.GetBatch(j) as ObiSkinConstraintsBatch;
for (int i = 0; i < batch.activeConstraintCount; i++)
{
int index = batch.particleIndices[i];
batch.skinPoints[i] = solver.transform.worldToLocalMatrix.MultiplyPoint3x4(boneBlueprint.transforms[index].position);
}
}
}
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)
{
// iterate up to the amount of entries in solverBatchOffsets, insteaf of bc.batchCount. This ensures
// the batches we access have been added to the solver, as solver.UpdateConstraints() could have not been called yet on a newly added actor.
for (int j = 0; j < solverBatchOffsets[(int)Oni.ConstraintType.BendTwist].Count; ++j)
{
var batch = bc.GetBatch(j) as ObiBendTwistConstraintsBatch;
var solverBatch = sbc.batches[j] as ObiBendTwistConstraintsBatch;
int offset = solverBatchOffsets[(int)Oni.ConstraintType.BendTwist][j];
if (solverBatch.restDarbouxVectors.isCreated)
{
if (solverBatch.restDarbouxVectors.computeBuffer == null)
solverBatch.restDarbouxVectors.SafeAsComputeBuffer<Vector4>();
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);
}
solverBatch.restDarbouxVectors.Upload();
}
}
}
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)
{
// iterate up to the amount of entries in solverBatchOffsets, insteaf of sc.batchCount. This ensures
// the batches we access have been added to the solver, as solver.UpdateConstraints() could have not been called yet on a newly added actor.
for (int j = 0; j < solverBatchOffsets[(int)Oni.ConstraintType.Skin].Count; ++j)
{
var batch = sc.GetBatch(j) as ObiSkinConstraintsBatch;
var solverBatch = ssc.batches[j] as ObiSkinConstraintsBatch;
int offset = solverBatchOffsets[(int)Oni.ConstraintType.Skin][j];
if (solverBatch.skinPoints.isCreated)
{
if (solverBatch.skinPoints.computeBuffer == null)
solverBatch.skinPoints.SafeAsComputeBuffer<Vector4>();
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);
}
solverBatch.skinPoints.Upload();
}
}
}
}
private void CopyParticleDataToTransforms()
{
if (isLoaded && 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];
}
}
}
}
}

View 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:

View File

@@ -0,0 +1,192 @@
using UnityEngine;
using System;
using System.Collections.Generic;
namespace Obi
{
[AddComponentMenu("Physics/Obi/Obi Bone Override", 882)]
[ExecuteInEditMode]
[DisallowMultipleComponent]
public class ObiBoneOverride : MonoBehaviour
{
[SerializeField] protected ObiBone.BonePropertyCurve _radius = new ObiBone.BonePropertyCurve(0.1f, 1);
[SerializeField] protected ObiBone.BonePropertyCurve _mass = new ObiBone.BonePropertyCurve(0.1f, 1);
[SerializeField] protected ObiBone.BonePropertyCurve _rotationalMass = new ObiBone.BonePropertyCurve(0.1f, 1);
// skin constraints:
[SerializeField] protected ObiBone.BonePropertyCurve _skinCompliance = new ObiBone.BonePropertyCurve(0.01f, 1);
[SerializeField] protected ObiBone.BonePropertyCurve _skinRadius = new ObiBone.BonePropertyCurve(0.1f, 1);
// distance constraints:
[SerializeField] protected ObiBone.BonePropertyCurve _stretchCompliance = new ObiBone.BonePropertyCurve(0, 1);
[SerializeField] protected ObiBone.BonePropertyCurve _shear1Compliance = new ObiBone.BonePropertyCurve(0, 1);
[SerializeField] protected ObiBone.BonePropertyCurve _shear2Compliance = new ObiBone.BonePropertyCurve(0, 1);
// bend constraints:
[SerializeField] protected ObiBone.BonePropertyCurve _torsionCompliance = new ObiBone.BonePropertyCurve(0, 1);
[SerializeField] protected ObiBone.BonePropertyCurve _bend1Compliance = new ObiBone.BonePropertyCurve(0, 1);
[SerializeField] protected ObiBone.BonePropertyCurve _bend2Compliance = new ObiBone.BonePropertyCurve(0, 1);
[SerializeField] protected ObiBone.BonePropertyCurve _plasticYield = new ObiBone.BonePropertyCurve(0, 1);
[SerializeField] protected ObiBone.BonePropertyCurve _plasticCreep = new ObiBone.BonePropertyCurve(0, 1);
// aerodynamics
[SerializeField] protected ObiBone.BonePropertyCurve _drag = new ObiBone.BonePropertyCurve(0.05f, 1);
[SerializeField] protected ObiBone.BonePropertyCurve _lift = new ObiBone.BonePropertyCurve(0.02f, 1);
/// <summary>
/// Particle radius distribution over this bone hierarchy length.
/// </summary>
public ObiBone.BonePropertyCurve radius
{
get { return _radius; }
set { _radius = value; bone.UpdateRadius(); }
}
/// <summary>
/// Mass distribution over this bone hierarchy length.
/// </summary>
public ObiBone.BonePropertyCurve mass
{
get { return _mass; }
set { _mass = value; bone.UpdateMasses(); }
}
/// <summary>
/// Rotational mass distribution over this bone hierarchy length.
/// </summary>
public ObiBone.BonePropertyCurve rotationalMass
{
get { return _rotationalMass; }
set { _rotationalMass = value; bone.UpdateMasses(); }
}
/// <summary>
/// Compliance of this actor's skin constraints.
/// </summary>
public ObiBone.BonePropertyCurve skinCompliance
{
get { return _skinCompliance; }
set { _skinCompliance = value; bone.SetConstraintsDirty(Oni.ConstraintType.Skin); }
}
/// <summary>
/// Compliance of this actor's skin radius
/// </summary>
public ObiBone.BonePropertyCurve skinRadius
{
get { return _skinRadius; }
set { _skinRadius = value; bone.SetConstraintsDirty(Oni.ConstraintType.Skin); }
}
/// <summary>
/// Compliance of this actor's stretch/shear constraints, along their length.
/// </summary>
public ObiBone.BonePropertyCurve stretchCompliance
{
get { return _stretchCompliance; }
set { _stretchCompliance = value; bone.SetConstraintsDirty(Oni.ConstraintType.StretchShear); }
}
/// <summary>
/// Shearing compliance of this actor's stretch/shear constraints, along the first axis orthogonal to their length.
/// </summary>
public ObiBone.BonePropertyCurve shear1Compliance
{
get { return _shear1Compliance; }
set { _shear1Compliance = value; bone.SetConstraintsDirty(Oni.ConstraintType.StretchShear); }
}
/// <summary>
/// Shearing compliance of this actor's stretch/shear constraints, along the second axis orthogonal to their length.
/// </summary>
public ObiBone.BonePropertyCurve shear2Compliance
{
get { return _shear2Compliance; }
set { _shear2Compliance = value; bone.SetConstraintsDirty(Oni.ConstraintType.StretchShear); }
}
/// <summary>
/// Torsional compliance of this actor's bend/twist constraints along their length.
/// </summary>
public ObiBone.BonePropertyCurve torsionCompliance
{
get { return _torsionCompliance; }
set { _torsionCompliance = value; bone.SetConstraintsDirty(Oni.ConstraintType.BendTwist); }
}
/// <summary>
/// Bending compliance of this actor's bend/twist constraints along the first axis orthogonal to their length.
/// </summary>
public ObiBone.BonePropertyCurve bend1Compliance
{
get { return _bend1Compliance; }
set { _bend1Compliance = value; bone.SetConstraintsDirty(Oni.ConstraintType.BendTwist); }
}
/// <summary>
/// Bending compliance of this actor's bend/twist constraints along the second axis orthogonal to their length.
/// </summary>
public ObiBone.BonePropertyCurve bend2Compliance
{
get { return _bend2Compliance; }
set { _bend2Compliance = value; bone.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 ObiBone.BonePropertyCurve plasticYield
{
get { return _plasticYield; }
set { _plasticYield = value; bone.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 ObiBone.BonePropertyCurve plasticCreep
{
get { return _plasticCreep; }
set { _plasticCreep = value; bone.SetConstraintsDirty(Oni.ConstraintType.BendTwist); }
}
/// <summary>
/// Aerodynamic drag value.
/// </summary>
public ObiBone.BonePropertyCurve drag
{
get { return _drag; }
set { _drag = value; bone.SetConstraintsDirty(Oni.ConstraintType.Aerodynamics); }
}
/// <summary>
/// Aerodynamic lift value.
/// </summary>
public ObiBone.BonePropertyCurve lift
{
get { return _lift; }
set { _lift = value; bone.SetConstraintsDirty(Oni.ConstraintType.Aerodynamics); }
}
private ObiBone bone;
public void Awake()
{
bone = GetComponentInParent<ObiBone>();
}
protected void OnValidate()
{
if (bone != null)
{
bone.UpdateRadius();
bone.UpdateMasses();
bone.SetConstraintsDirty(Oni.ConstraintType.Skin);
bone.SetConstraintsDirty(Oni.ConstraintType.StretchShear);
bone.SetConstraintsDirty(Oni.ConstraintType.BendTwist);
bone.SetConstraintsDirty(Oni.ConstraintType.Aerodynamics);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: eed7ac246d14742e5a77c218f4e491ec
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 0a18e0376cc184a9b96ebb3bf0175cc2, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,266 @@
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();
}
internal override void LoadBlueprint()
{
base.LoadBlueprint();
RebuildElementsFromConstraints();
SetupRuntimeConstraints();
}
public override void RequestReadback()
{
base.RequestReadback();
solver.orientations.Readback();
}
public override void SimulationEnd(float simulatedTime, float substepTime)
{
base.SimulationEnd(simulatedTime, substepTime);
solver.orientations.WaitForReadback();
}
private void SetupRuntimeConstraints()
{
SetConstraintsDirty(Oni.ConstraintType.StretchShear);
SetConstraintsDirty(Oni.ConstraintType.BendTwist);
SetConstraintsDirty(Oni.ConstraintType.Chain);
SetConstraintsDirty(Oni.ConstraintType.Aerodynamics);
SetSelfCollisions(selfCollisions);
SetMassScale(m_MassScale);
RecalculateRestLength();
SetSimplicesDirty();
}
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.batchCount < 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);
}
}
}
}

View 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:

View File

@@ -0,0 +1,468 @@
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();
}
internal override void LoadBlueprint()
{
// create a copy of the blueprint for this cloth:
if (Application.isPlaying)
m_RopeBlueprintInstance = this.blueprint as ObiRopeBlueprint;
base.LoadBlueprint();
RebuildElementsFromConstraints();
SetupRuntimeConstraints();
}
internal override void UnloadBlueprint()
{
base.UnloadBlueprint();
// delete the blueprint instance:
if (m_RopeBlueprintInstance != null)
DestroyImmediate(m_RopeBlueprintInstance);
}
private void SetupRuntimeConstraints()
{
SetConstraintsDirty(Oni.ConstraintType.Distance);
SetConstraintsDirty(Oni.ConstraintType.Bending);
SetConstraintsDirty(Oni.ConstraintType.Aerodynamics);
SetSelfCollisions(selfCollisions);
SetMassScale(m_MassScale);
RecalculateRestLength();
SetSimplicesDirty();
}
// Tearing must be done at the end of each step instead of substep, to give a chance to solver constraints to be rebuilt.
public override void SimulationStart(float timeToSimulate, float substepTime)
{
base.SimulationStart(timeToSimulate, substepTime);
if (isActiveAndEnabled && tearingEnabled)
ApplyTearing(substepTime);
}
protected void ApplyTearing(float substepTime)
{
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)
{
// iterate up to the amount of entries in solverBatchOffsets, insteaf of dc.batchCount. This ensures
// the batches we access have been added to the solver, as solver.UpdateConstraints() could have not been called yet on a newly added actor.
for (int j = 0; j < solverBatchOffsets[(int)Oni.ConstraintType.Distance].Count; ++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();
SetRenderingDirty(Oni.RenderingSystemType.AllRopes);
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);
OnRopeTorn?.Invoke(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.batchCount < 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>;
var ac = GetConstraintsByType(Oni.ConstraintType.Aerodynamics) as ObiConstraints<ObiAerodynamicConstraintsBatch>;
dc.DeactivateAllConstraints();
bc.DeactivateAllConstraints();
ac.DeactivateAllConstraints();
for (int i = 0; i < activeParticleCount; ++i)
{
// aerodynamic constraints:
var ab = ac.batches[0] as ObiAerodynamicConstraintsBatch;
int constraint = ab.activeConstraintCount;
ab.particleIndices[constraint] = i;
ab.aerodynamicCoeffs[constraint * 3] = 2 * solver.principalRadii[solverIndices[i]].x;
ab.ActivateConstraint(constraint);
}
int elementsCount = elements.Count - (ropeBlueprint.path.Closed ? 1 : 0);
for (int i = 0; i < elementsCount; ++i)
{
// distance constraints
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);
// bend constraints
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 = 0;//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 and deformable edges
var rb = sharedBlueprint as ObiRopeBlueprint;
rb.edges = new int[elements.Count * 2];
rb.deformableEdges = new int[elements.Count * 2];
for (int i = 0; i < elements.Count; ++i)
{
rb.deformableEdges[i * 2] = rb.edges[i * 2] = solver.particleToActor[elements[i].particle1].indexInActor;
rb.deformableEdges[i * 2 + 1] = rb.edges[i * 2 + 1] = solver.particleToActor[elements[i].particle2].indexInActor;
}
SetConstraintsDirty(Oni.ConstraintType.Distance);
SetConstraintsDirty(Oni.ConstraintType.Bending);
SetConstraintsDirty(Oni.ConstraintType.Aerodynamics);
solver.dirtyDeformableEdges = true;
SetSimplicesDirty();
}
}
}

View 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:

View File

@@ -0,0 +1,193 @@
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
namespace Obi
{
public abstract class ObiRopeBase : ObiActor, IAerodynamicConstraintsUser
{
[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;
// aerodynamics
[SerializeField] protected bool _aerodynamicsEnabled = true;
[SerializeField] protected float _drag = 0.05f;
[SerializeField] protected float _lift = 0.02f;
/// <summary>
/// Whether this actor's aerodynamic constraints are enabled.
/// </summary>
public bool aerodynamicsEnabled
{
get { return _aerodynamicsEnabled; }
set { if (value != _aerodynamicsEnabled) { _aerodynamicsEnabled = value; SetConstraintsDirty(Oni.ConstraintType.Aerodynamics); } }
}
/// <summary>
/// Aerodynamic drag value.
/// </summary>
public float drag
{
get { return _drag; }
set { _drag = value; SetConstraintsDirty(Oni.ConstraintType.Aerodynamics); }
}
/// <summary>
/// Aerodynamic lift value.
/// </summary>
public float lift
{
get { return _lift; }
set { _lift = value; SetConstraintsDirty(Oni.ConstraintType.Aerodynamics); }
}
public float restLength
{
get { return restLength_; }
}
public ObiPath path
{
get {
var ropeBlueprint = (sourceBlueprint as ObiRopeBlueprintBase);
return ropeBlueprint != null ? ropeBlueprint.path : null;
}
}
public float GetDrag(ObiAerodynamicConstraintsBatch batch, int constraintIndex)
{
return drag;
}
public float GetLift(ObiAerodynamicConstraintsBatch batch, int constraintIndex)
{
return lift;
}
public override void ProvideDeformableEdges(ObiNativeIntList deformableEdges)
{
deformableEdgesOffset = deformableEdges.count / 2;
var ropeBlueprint = sharedBlueprint as ObiRopeBlueprintBase;
if (ropeBlueprint != null && ropeBlueprint.deformableEdges != null)
{
// Send deformable edge indices to the solver:
for (int i = 0; i < ropeBlueprint.deformableEdges.Length; ++i)
deformableEdges.Add(solverIndices[ropeBlueprint.deformableEdges[i]]);
}
}
public override int GetDeformableEdgeCount()
{
var ropeBlueprint = sharedBlueprint as ObiRopeBlueprintBase;
if (ropeBlueprint != null && ropeBlueprint.deformableEdges != null)
return ropeBlueprint.deformableEdges.Length / 2;
return 0;
}
/// <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;
}
/// <summary>
/// Returns index of the edge that contains a length-normalized coordinate. It will also return the length-normalized coordinate within the edge.
/// </summary>
public int GetEdgeAt(float mu, out float elementMu)
{
elementMu = -1;
var ropeBlueprint = sharedBlueprint as ObiRopeBlueprintBase;
if (ropeBlueprint != null && ropeBlueprint.deformableEdges != null)
{
float edgeMu = ropeBlueprint.deformableEdges.Length/2 * Mathf.Clamp(mu, 0, 0.99999f);
int index = (int)edgeMu;
elementMu = edgeMu - index;
if (index < ropeBlueprint.deformableEdges.Length/2)
return index;
}
return -1;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ab4d6f4281d1c4331b6bacd307c61a95
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,288 @@
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;
private float lengthChange = 0;
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;
rope.OnSimulationStart += Rope_OnSimulate;
if (rope.elements != null && rope.elements.Count > 0)
Actor_OnElementsGenerated(rope);
}
private void OnDisable()
{
rope.OnElementsGenerated -= Actor_OnElementsGenerated;
rope.OnSimulationStart -= Rope_OnSimulate;
}
private void Actor_OnElementsGenerated(ObiActor actor)
{
UpdateCursor();
UpdateSource();
}
private void Rope_OnSimulate(ObiActor actor, float simulatedTime, float substepTime)
{
if (!rope.isLoaded || Mathf.Abs(lengthChange) < ObiUtils.epsilon)
return;
var solver = rope.solver;
// remove:
if (lengthChange < 0)
{
lengthChange = -lengthChange;
while (lengthChange > m_CursorElement.restLength)
{
lengthChange -= m_CursorElement.restLength;
// if we subtracted the length of the last element, break out of the loop.
if (rope.elements.Count == 1)
break;
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();
lengthChange = 0;
}
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)
{
int targetIndex = rope.activeParticleCount;
// Copy data from the particle where we will insert new particles, to the particles we will insert:
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();
rope.SetRenderingDirty(Oni.RenderingSystemType.AllRopes);
return rope.solverIndices[targetIndex];
}
private void RemoveParticleAt(int index)
{
rope.DeactivateParticle(index);
rope.SetRenderingDirty(Oni.RenderingSystemType.AllRopes);
}
public float ChangeLength(float lengthChange)
{
// clamp new length to sane limits:
//newLength = Mathf.Clamp(newLength, 0, (rope.sourceBlueprint.particleCount - 1) * rope.ropeBlueprint.interParticleDistance);
// accumulate length change, we'll reset it to zero after it has been applied.
this.lengthChange += lengthChange;
// return new length:
return this.lengthChange + rope.restLength;
}
}
}

View 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:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: acf7eb82de1d147128faa88b9cdb70d3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,372 @@
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] public int[] deformableEdges = null; /**< Indices of deformable edges (2 per edge)*/
[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;
}
public ObiBoneOverride GetOverride(int particleIndex, out float normalizedLength)
{
int overrideIndex = particleIndex;
normalizedLength = normalizedLengths[overrideIndex];
while (overrideIndex >= 0)
{
if (transforms[overrideIndex].TryGetComponent(out ObiBoneOverride over))
{
normalizedLength = 1 - (1 - normalizedLengths[particleIndex]) / Mathf.Max(ObiUtils.epsilon,1 - normalizedLengths[overrideIndex]);
return over;
}
overrideIndex = parentIndices[overrideIndex];
}
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;
var group = ScriptableObject.CreateInstance<ObiParticleGroup>();
group.SetSourceBlueprint(this);
group.name = transforms[i].name;
group.particleIndices.Add(i);
groups.Add(group);
if (i % 100 == 0)
yield return new CoroutineJob.ProgressInfo("ObiRod: generating particles...", i / (float)m_ActiveParticleCount);
}
colorizer = new GraphColoring(m_ActiveParticleCount);
// Deformable edges:
CreateDeformableEdges();
// 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;
// Create aerodynamic constraints:
IEnumerator ac = CreateAerodynamicConstraints();
while (ac.MoveNext()) yield return ac.Current;
yield return new CoroutineJob.ProgressInfo("ObiBone: complete", 1);
}
protected void CreateDeformableEdges()
{
deformableEdges = new int[(parentIndices.Count - 1) * 2];
for (int i = 0; i < parentIndices.Count - 1; ++i)
{
deformableEdges[i * 2] = i + 1;
deformableEdges[i * 2 + 1] = parentIndices[i + 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 CreateAerodynamicConstraints()
{
aerodynamicConstraintsData = new ObiAerodynamicConstraintsData();
var aeroBatch = new ObiAerodynamicConstraintsBatch();
aerodynamicConstraintsData.AddBatch(aeroBatch);
for (int i = 0; i < m_ActiveParticleCount; i++)
{
aeroBatch.AddConstraint(i, 2 * principalRadii[i].x, 1, 1);
if (i % 500 == 0)
yield return new CoroutineJob.ProgressInfo("ObiRope generating aerodynamic constraints...", i / (float)m_ActiveParticleCount);
}
// Set initial amount of active constraints:
for (int i = 0; i < aerodynamicConstraintsData.batches.Count; ++i)
{
aerodynamicConstraintsData.batches[i].activeConstraintCount = m_ActiveParticleCount;
}
}
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.batchCount)
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.batchCount)
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);
}
}
}
}

View File

@@ -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:

View File

@@ -0,0 +1,288 @@
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);
}
// Deformable edges:
CreateDeformableEdges(numSegments);
// 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 aerodynamic constraints:
IEnumerator ac = CreateAerodynamicConstraints();
while (ac.MoveNext())
yield return ac.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 = ObiPathFrame.Identity;
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;
}
}
}

View File

@@ -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:

View File

@@ -0,0 +1,236 @@
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);
}
// Deformable edges:
CreateDeformableEdges(numSegments);
// 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;
// Create aerodynamic constraints:
IEnumerator ac = CreateAerodynamicConstraints();
while (ac.MoveNext())
yield return ac.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 = 0;//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++;
}
}
}
}

View File

@@ -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:

View File

@@ -0,0 +1,109 @@
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 int[] deformableEdges = null; /**< Indices of deformable edges (2 per edge)*/
[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 virtual IEnumerator CreateAerodynamicConstraints()
{
aerodynamicConstraintsData = new ObiAerodynamicConstraintsData();
var aeroBatch = new ObiAerodynamicConstraintsBatch();
aerodynamicConstraintsData.AddBatch(aeroBatch);
for (int i = 0; i < totalParticles; i++)
{
aeroBatch.AddConstraint(i, 2 * principalRadii[i].x, 1, 1);
if (i % 500 == 0)
yield return new CoroutineJob.ProgressInfo("ObiRope generating aerodynamic constraints...", i / (float)totalParticles);
}
// Set initial amount of active constraints:
for (int i = 0; i < aerodynamicConstraintsData.batches.Count; ++i)
{
aerodynamicConstraintsData.batches[i].activeConstraintCount = m_ActiveParticleCount;
}
}
protected void CreateDeformableEdges(int numSegments)
{
deformableEdges = new int[numSegments * 2];
for (int i = 0; i < numSegments; ++i)
{
deformableEdges[i * 2] = i % activeParticleCount;
deformableEdges[i * 2 + 1] = (i + 1) % activeParticleCount;
}
}
protected void CreateSimplices(int numSegments)
{
edges = new int[numSegments * 2];
for (int i = 0; i < numSegments; ++i)
{
edges[i * 2] = i % activeParticleCount;
edges[i * 2 + 1] = (i + 1) % activeParticleCount;
}
}
protected override IEnumerator Initialize() { yield return null; }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4abe69b6abd2c4b0e8a621b8fba92dc9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: fa3e31464f45342fc90509e4fcb00976
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,52 @@
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;
}
}
}

View File

@@ -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:

View File

@@ -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;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2aecff76dda4241c5a0c15349e2419d3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 75e833f2b944640a5934c15fbec8ac00
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5172d3d605ca94f2db0ad73eaecbc4d4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,12 @@
using UnityEngine;
using System;
using System.Collections;
namespace Obi
{
[Serializable]
public class ObiColorDataChannel : ObiPathDataChannelIdentity<Color>
{
public ObiColorDataChannel() : base(new ObiColorInterpolator3D()) { }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4078c779a63154dddac14f5a1e65a692
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,10 @@
using System;
namespace Obi
{
[Serializable]
public class ObiPhaseDataChannel : ObiPathDataChannelIdentity<int>
{
public ObiPhaseDataChannel() : base(new ObiConstantInterpolator()) { }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9cfdcf019ed7249799993815bfc3c0bd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,12 @@
using UnityEngine;
using System;
using System.Collections;
namespace Obi
{
[Serializable]
public class ObiMassDataChannel : ObiPathDataChannelIdentity<float>
{
public ObiMassDataChannel() : base(new ObiCatmullRomInterpolator()) { }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e55025008fa8f4bccae82dbaded0f019
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,12 @@
using UnityEngine;
using System;
using System.Collections;
namespace Obi
{
[Serializable]
public class ObiNormalDataChannel : ObiPathDataChannelIdentity<Vector3>
{
public ObiNormalDataChannel() : base(new ObiCatmullRomInterpolator3D()) { }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4721087e73d0d422ab48ad62a2fd3d3b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 43ab47ea5c62b45248edce6a9d099626
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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.");
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 61b5c82c04eb14f1999e94baa1f073bc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,124 @@
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.");
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d710ff448ae4e4a6f9351a678af9a8ac
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,12 @@
using UnityEngine;
using System;
using System.Collections;
namespace Obi
{
[Serializable]
public class ObiRotationalMassDataChannel : ObiPathDataChannelIdentity<float>
{
public ObiRotationalMassDataChannel() : base(new ObiCatmullRomInterpolator()) { }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 28c38822e5a79441a8177689f83644e8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,12 @@
using UnityEngine;
using System;
using System.Collections;
namespace Obi
{
[Serializable]
public class ObiThicknessDataChannel : ObiPathDataChannelIdentity<float>
{
public ObiThicknessDataChannel() : base(new ObiCatmullRomInterpolator()) { }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b414fbc6ce2db42299dc01619ed4fc97
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e85cc8f3b96cf4b4c9be1e6af0a0a99b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 93918aac247384d8289d151d77dc4dc2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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));
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 845dbd641b61b490bb94d745b6879e97
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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));
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1c2bda61c040346db8efd82f64c2422e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 33f10aef9ed544b14a762a9c6e8bf411
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: af25f2a4d723548bfafa551b6da56d25
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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();
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ee1575a4e45ef4a8ebafa0e5e2e51da5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,170 @@
using System;
using UnityEngine;
namespace Obi
{
public struct ObiPathFrame
{
public enum Axis
{
X = 0,
Y = 1,
Z = 2
}
public static ObiPathFrame Identity => new ObiPathFrame(Vector3.zero, Vector3.forward, Vector3.up, Vector3.right, Color.white, 0);
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, in ObiPathFrame c1, in ObiPathFrame c2, in 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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 237e9ecf813f646fd8502768445f4ab6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,101 @@
using UnityEngine;
using System;
namespace Obi
{
[ExecuteInEditMode]
[RequireComponent(typeof(ObiRopeBase))]
public class ObiPathSmoother : MonoBehaviour, ObiActorRenderer<ObiPathSmoother>
{
[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 ObiActor actor { get; private set; }
[HideInInspector] public int indexInSystem = 0;
public float SmoothLength
{
get
{
if (actor.isLoaded)
{
var system = actor.solver.GetRenderSystem<ObiPathSmoother>() as ObiPathSmootherRenderSystem;
if (system != null)
return system.GetSmoothLength(indexInSystem);
}
return 0;
}
}
public float SmoothSections
{
get {
if (actor.isLoaded)
{
var system = actor.solver.GetRenderSystem<ObiPathSmoother>() as ObiPathSmootherRenderSystem;
if (system != null)
return system.GetSmoothFrameCount(indexInSystem);
}
return 0;
}
}
public void OnEnable()
{
actor = GetComponent<ObiActor>();
((ObiActorRenderer<ObiPathSmoother>)this).EnableRenderer();
}
private void OnDisable()
{
((ObiActorRenderer<ObiPathSmoother>)this).DisableRenderer();
}
private void OnValidate()
{
((ObiActorRenderer<ObiPathSmoother>)this).SetRendererDirty(Oni.RenderingSystemType.AllSmoothedRopes);
}
public ObiPathFrame GetSectionAt(float mu)
{
if (actor.isLoaded)
{
var system = actor.solver.GetRenderSystem<ObiPathSmoother>() as ObiPathSmootherRenderSystem;
if (system != null)
return system.GetFrameAt(indexInSystem, mu);
}
return ObiPathFrame.Identity;
}
RenderSystem<ObiPathSmoother> ObiRenderer<ObiPathSmoother>.CreateRenderSystem(ObiSolver solver)
{
switch (solver.backendType)
{
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
case ObiSolver.BackendType.Burst: return new BurstPathSmootherRenderSystem(solver);
#endif
case ObiSolver.BackendType.Compute:
default:
if (SystemInfo.supportsComputeShaders)
return new ComputePathSmootherRenderSystem(solver);
return null;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 958c969cfb16745f192d4d7bd28b7178
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 8791eecf125744cbeadea65319c29d5a, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 76ee869b5f2e5440ea0a1c8a1e161cde
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 07dfc7e6a316349cba9d3b434f09ee68
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,216 @@
using System.Runtime.InteropServices;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using Unity.Profiling;
namespace Obi
{
[StructLayout(LayoutKind.Sequential)]
public struct ChainRendererData
{
public int modifierOffset;
public float twistAnchor;
public float twist;
public uint usesOrientedParticles;
public Vector4 scale;
public ChainRendererData(int modifierOffset, float twistAnchor, float twist, Vector3 scale, bool usesOrientedParticles)
{
this.modifierOffset = modifierOffset;
this.twistAnchor = twistAnchor;
this.twist = twist;
this.usesOrientedParticles = (uint)(usesOrientedParticles ? 1 : 0);
this.scale = scale;
}
}
public abstract class ObiChainRopeRenderSystem : RenderSystem<ObiRopeChainRenderer>
{
public Oni.RenderingSystemType typeEnum { get => Oni.RenderingSystemType.ChainRope; }
public RendererSet<ObiRopeChainRenderer> renderers { get; } = new RendererSet<ObiRopeChainRenderer>();
// specify vertex count and layout
protected VertexAttributeDescriptor[] layout =
{
new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3),
new VertexAttributeDescriptor(VertexAttribute.Normal, VertexAttributeFormat.Float32, 3),
new VertexAttributeDescriptor(VertexAttribute.Tangent, VertexAttributeFormat.Float32, 4),
new VertexAttributeDescriptor(VertexAttribute.Color, VertexAttributeFormat.Float32, 4),
new VertexAttributeDescriptor(VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 2),
};
static protected ProfilerMarker m_SetupRenderMarker = new ProfilerMarker("SetupChainRopeRendering");
static protected ProfilerMarker m_RenderMarker = new ProfilerMarker("ChainRopeRendering");
protected ObiSolver m_Solver;
protected List<InstancedRenderBatch> batchList = new List<InstancedRenderBatch>();
protected ObiNativeList<ChainRendererData> rendererData;
protected ObiNativeList<ChunkData> chunkData;
protected ObiNativeList<ObiRopeChainRenderer.LinkModifier> modifiers;
protected ObiNativeList<Vector2Int> elements;
protected ObiNativeList<Matrix4x4> instanceTransforms;
protected ObiNativeList<Matrix4x4> invInstanceTransforms;
protected ObiNativeList<Vector4> instanceColors;
public ObiChainRopeRenderSystem(ObiSolver solver)
{
m_Solver = solver;
}
public virtual void Dispose()
{
CleanupBatches();
DestroyLists();
}
private void DestroyLists()
{
if (instanceTransforms != null)
instanceTransforms.Dispose();
if (invInstanceTransforms != null)
invInstanceTransforms.Dispose();
if (instanceColors != null)
instanceColors.Dispose();
if (elements != null)
elements.Dispose();
if (chunkData != null)
chunkData.Dispose();
if (rendererData != null)
rendererData.Dispose();
if (modifiers != null)
modifiers.Dispose();
}
private void CreateListsIfNecessary()
{
DestroyLists();
instanceTransforms = new ObiNativeList<Matrix4x4>();
invInstanceTransforms = new ObiNativeList<Matrix4x4>();
instanceColors = new ObiNativeList<Vector4>();
elements = new ObiNativeList<Vector2Int>();
chunkData = new ObiNativeList<ChunkData>();
rendererData = new ObiNativeList<ChainRendererData>();
modifiers = new ObiNativeList<ObiRopeChainRenderer.LinkModifier>();
}
private void CleanupBatches()
{
for (int i = 0; i < batchList.Count; ++i)
batchList[i].Dispose();
batchList.Clear();
}
private void GenerateBatches()
{
instanceTransforms.Clear();
invInstanceTransforms.Clear();
instanceColors.Clear();
elements.Clear();
rendererData.Clear();
chunkData.Clear();
modifiers.Clear();
// generate batches:
for (int i = 0; i < renderers.Count; ++i)
{
var renderer = renderers[i];
if (renderer.linkMesh != null && renderer.linkMaterial != null)
{
renderer.renderParameters.layer = renderer.gameObject.layer;
batchList.Add(new InstancedRenderBatch(i, renderer.linkMesh, renderer.linkMaterial, renderer.renderParameters));
}
}
// sort batches:
batchList.Sort();
// append elements:
for (int i = 0; i < batchList.Count; ++i)
{
var renderer = renderers[batchList[i].firstRenderer];
var rope = renderer.actor as ObiRopeBase;
modifiers.AddRange(renderer.linkModifiers);
rendererData.Add(new ChainRendererData(modifiers.count, renderer.twistAnchor, renderer.linkTwist, renderer.linkScale, rope.usesOrientedParticles));
batchList[i].firstInstance = elements.count;
batchList[i].instanceCount = rope.elements.Count;
// iterate trough elements, finding discontinuities as we go:
for (int e = 0; e < rope.elements.Count; ++e)
{
elements.Add(new Vector2Int(rope.elements[e].particle1, rope.elements[e].particle2));
// At discontinuities, start a new chunk.
if (e < rope.elements.Count - 1 && rope.elements[e].particle2 != rope.elements[e + 1].particle1)
{
chunkData.Add(new ChunkData(rendererData.count - 1, elements.count));
}
}
chunkData.Add(new ChunkData(rendererData.count - 1, elements.count));
}
instanceTransforms.ResizeUninitialized(elements.count);
invInstanceTransforms.ResizeUninitialized(elements.count);
instanceColors.ResizeUninitialized(elements.count);
}
protected virtual void CloseBatches()
{
// Initialize each batch:
for (int i = 0; i < batchList.Count; ++i)
batchList[i].Initialize();
}
public virtual void Setup()
{
using (m_SetupRenderMarker.Auto())
{
CreateListsIfNecessary();
CleanupBatches();
GenerateBatches();
ObiUtils.MergeBatches(batchList);
CloseBatches();
}
}
public abstract void Render();
public void Step()
{
}
public void BakeMesh(ObiRopeChainRenderer renderer, ref Mesh mesh, bool transformToActorLocalSpace = false)
{
int index = renderers.IndexOf(renderer);
for (int i = 0; i < batchList.Count; ++i)
{
var batch = batchList[i];
if (index >= batch.firstRenderer && index < batch.firstRenderer + batch.rendererCount)
{
batch.BakeMesh(renderers, renderer, chunkData, instanceTransforms,
renderer.actor.actorSolverToLocalMatrix, ref mesh, transformToActorLocalSpace);
return;
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9a4cb8b10f8b049c2ae0a97c50c9b33c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,279 @@
using System.Runtime.InteropServices;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using Unity.Profiling;
namespace Obi
{
[StructLayout(LayoutKind.Sequential)]
public struct BurstExtrudedMeshData
{
public int sectionVertexCount;
public float thicknessScale;
public float uvAnchor;
public uint normalizeV;
public Vector2 uvScale;
public BurstExtrudedMeshData(ObiRopeExtrudedRenderer renderer)
{
sectionVertexCount = renderer.section.vertices.Count;
uvAnchor = renderer.uvAnchor;
thicknessScale = renderer.thicknessScale;
uvScale = renderer.uvScale;
normalizeV = (uint)(renderer.normalizeV ? 1 : 0);
}
}
[StructLayout(LayoutKind.Sequential)]
public struct ProceduralRopeVertex
{
public Vector3 pos;
public Vector3 normal;
public Vector4 tangent;
public Vector4 color;
public Vector2 uv;
}
public abstract class ObiExtrudedRopeRenderSystem : RenderSystem<ObiRopeExtrudedRenderer>
{
public Oni.RenderingSystemType typeEnum { get => Oni.RenderingSystemType.ExtrudedRope; }
public RendererSet<ObiRopeExtrudedRenderer> renderers { get; } = new RendererSet<ObiRopeExtrudedRenderer>();
protected List<ObiRopeExtrudedRenderer> sortedRenderers = new List<ObiRopeExtrudedRenderer>(); /**< temp list used to store renderers sorted by batch.*/
// specify vertex count and layout
protected VertexAttributeDescriptor[] layout =
{
new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3),
new VertexAttributeDescriptor(VertexAttribute.Normal, VertexAttributeFormat.Float32, 3),
new VertexAttributeDescriptor(VertexAttribute.Tangent, VertexAttributeFormat.Float32, 4),
new VertexAttributeDescriptor(VertexAttribute.Color, VertexAttributeFormat.Float32, 4),
new VertexAttributeDescriptor(VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 2),
};
static protected ProfilerMarker m_SetupRenderMarker = new ProfilerMarker("SetupExtrudedRopeRendering");
static protected ProfilerMarker m_RenderMarker = new ProfilerMarker("ExtrudedRopeRendering");
protected ObiSolver m_Solver;
protected SubMeshDescriptor subMeshDescriptor = new SubMeshDescriptor(0, 0);
protected List<ProceduralRenderBatch<ProceduralRopeVertex>> batchList = new List<ProceduralRenderBatch<ProceduralRopeVertex>>();
protected ObiNativeList<BurstExtrudedMeshData> rendererData; /**< for each renderer, data about smoother.*/
protected ObiNativeList<int> pathSmootherIndices; /**< renderer indices, sorted by batch */
protected Dictionary<ObiRopeSection, int> sectionToIndex = new Dictionary<ObiRopeSection, int>();
protected ObiNativeVector2List sectionData;
protected ObiNativeList<int> sectionOffsets; /**< for each section, offset of its first entry in the sectionData array.*/
protected ObiNativeList<int> sectionIndices; /**< for each renderer, index of the section used.*/
protected ObiNativeList<int> vertexOffsets; /**< for each renderer, vertex offset in its batch mesh data.*/
protected ObiNativeList<int> triangleOffsets; /**< for each renderer, triangle offset in its batch mesh data.*/
protected ObiNativeList<int> vertexCounts; /**< for each renderer, vertex count.*/
protected ObiNativeList<int> triangleCounts; /**< for each renderer, triangle count.*/
protected ObiPathSmootherRenderSystem pathSmootherSystem;
public ObiExtrudedRopeRenderSystem(ObiSolver solver)
{
m_Solver = solver;
rendererData = new ObiNativeList<BurstExtrudedMeshData>();
pathSmootherIndices = new ObiNativeList<int>();
sectionData = new ObiNativeVector2List();
sectionOffsets = new ObiNativeList<int>();
sectionIndices = new ObiNativeList<int>();
vertexOffsets = new ObiNativeList<int>();
triangleOffsets = new ObiNativeList<int>();
vertexCounts = new ObiNativeList<int>();
triangleCounts = new ObiNativeList<int>();
}
public void Dispose()
{
for (int i = 0; i < batchList.Count; ++i)
batchList[i].Dispose();
batchList.Clear();
if (rendererData != null)
rendererData.Dispose();
if (pathSmootherIndices != null)
pathSmootherIndices.Dispose();
if (sectionData != null)
sectionData.Dispose();
if (sectionOffsets != null)
sectionOffsets.Dispose();
if (sectionIndices != null)
sectionIndices.Dispose();
if (vertexOffsets != null)
vertexOffsets.Dispose();
if (triangleOffsets != null)
triangleOffsets.Dispose();
if (vertexCounts != null)
vertexCounts.Dispose();
if (triangleCounts != null)
triangleCounts.Dispose();
}
private void Clear()
{
rendererData.Clear();
pathSmootherIndices.Clear();
sectionData.Clear();
sectionToIndex.Clear();
sectionOffsets.Clear();
sectionIndices.Clear();
vertexOffsets.Clear();
triangleOffsets.Clear();
vertexCounts.Clear();
triangleCounts.Clear();
for (int i = 0; i < batchList.Count; ++i)
batchList[i].Dispose();
batchList.Clear();
}
private void CreateBatches()
{
// generate batches:
sortedRenderers.Clear();
for (int i = 0; i < renderers.Count; ++i)
{
if (renderers[i].section != null && renderers[i].TryGetComponent(out ObiPathSmoother smoother) && smoother.enabled)
{
renderers[i].renderParameters.layer = renderers[i].gameObject.layer;
batchList.Add(new ProceduralRenderBatch<ProceduralRopeVertex>(i, renderers[i].material, renderers[i].renderParameters));
sortedRenderers.Add(renderers[i]);
}
}
vertexOffsets.ResizeUninitialized(sortedRenderers.Count);
triangleOffsets.ResizeUninitialized(sortedRenderers.Count);
// sort batches:
batchList.Sort();
// reorder renderers based on sorted batches:
sortedRenderers.Clear();
for (int i = 0; i < batchList.Count; ++i)
{
var batch = batchList[i];
sortedRenderers.Add(renderers[batch.firstRenderer]);
batch.firstRenderer = i;
int pathIndex = sortedRenderers[i].GetComponent<ObiPathSmoother>().indexInSystem;
pathSmootherIndices.Add(pathIndex);
// get or create extruded section:
if (!sectionToIndex.TryGetValue(sortedRenderers[i].section, out int sectionIndex))
{
sectionIndex = sectionOffsets.count;
sectionToIndex[sortedRenderers[i].section] = sectionIndex;
sectionOffsets.Add(sectionData.count);
sectionData.AddRange(sortedRenderers[i].section.vertices);
}
sectionIndices.Add(sectionIndex);
// calculate vertex and triangle counts for each renderer:
int chunkStart = pathSmootherSystem.chunkOffsets[pathIndex];
int chunkAmount = pathSmootherSystem.chunkOffsets[pathIndex + 1] - chunkStart;
for (int k = chunkStart; k < chunkStart + chunkAmount; ++k)
{
int frameCount = pathSmootherSystem.smoothFrameCounts[k];
batch.vertexCount += frameCount * sortedRenderers[i].section.vertices.Count;
batch.triangleCount += (frameCount - 1) * (sortedRenderers[i].section.vertices.Count - 1) * 2;
}
vertexCounts.Add(batch.vertexCount);
triangleCounts.Add(batch.triangleCount);
rendererData.Add(new BurstExtrudedMeshData(sortedRenderers[i]));
}
// add last entry to section offsets:
sectionOffsets.Add(sectionData.count);
}
private void CalculateMeshOffsets()
{
for (int i = 0; i < batchList.Count; ++i)
{
var batch = batchList[i];
int vtxCount = 0;
int triCount = 0;
// Calculate vertex and triangle offsets for each renderer in the batch:
for (int j = 0; j < batch.rendererCount; ++j)
{
int r = batch.firstRenderer + j;
vertexOffsets[r] = vtxCount;
triangleOffsets[r] = triCount;
vtxCount += vertexCounts[r];
triCount += triangleCounts[r];
}
}
}
public virtual void Setup()
{
pathSmootherSystem = m_Solver.GetRenderSystem<ObiPathSmoother>() as ObiPathSmootherRenderSystem;
if (pathSmootherSystem == null)
return;
using (m_SetupRenderMarker.Auto())
{
Clear();
CreateBatches();
ObiUtils.MergeBatches(batchList);
CalculateMeshOffsets();
}
}
public abstract void Render();
public void Step()
{
}
public void BakeMesh(ObiRopeExtrudedRenderer renderer, ref Mesh mesh, bool transformToActorLocalSpace = false)
{
int index = sortedRenderers.IndexOf(renderer);
for (int i = 0; i < batchList.Count; ++i)
{
var batch = batchList[i];
if (index >= batch.firstRenderer && index < batch.firstRenderer + batch.rendererCount)
{
batch.BakeMesh(vertexOffsets[index], vertexCounts[index], triangleOffsets[index], triangleCounts[index],
renderer.actor.actorSolverToLocalMatrix, ref mesh, transformToActorLocalSpace);
return;
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e19ecda6559144921868e71085db8408
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,230 @@
using System.Runtime.InteropServices;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using Unity.Profiling;
namespace Obi
{
[StructLayout(LayoutKind.Sequential)]
public struct BurstLineMeshData
{
public Vector2 uvScale;
public float thicknessScale;
public float uvAnchor;
public uint normalizeV;
public BurstLineMeshData(ObiRopeLineRenderer renderer)
{
uvAnchor = renderer.uvAnchor;
thicknessScale = renderer.thicknessScale;
uvScale = renderer.uvScale;
normalizeV = (uint)(renderer.normalizeV ? 1 : 0);
}
}
public abstract class ObiLineRopeRenderSystem : RenderSystem<ObiRopeLineRenderer>
{
public Oni.RenderingSystemType typeEnum { get => Oni.RenderingSystemType.LineRope; }
public RendererSet<ObiRopeLineRenderer> renderers { get; } = new RendererSet<ObiRopeLineRenderer>();
protected List<ObiRopeLineRenderer> sortedRenderers = new List<ObiRopeLineRenderer>();
// specify vertex count and layout
protected VertexAttributeDescriptor[] layout =
{
new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3),
new VertexAttributeDescriptor(VertexAttribute.Normal, VertexAttributeFormat.Float32, 3),
new VertexAttributeDescriptor(VertexAttribute.Tangent, VertexAttributeFormat.Float32, 4),
new VertexAttributeDescriptor(VertexAttribute.Color, VertexAttributeFormat.Float32, 4),
new VertexAttributeDescriptor(VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 2),
};
static protected ProfilerMarker m_SetupRenderMarker = new ProfilerMarker("SetupExtrudedRopeRendering");
static protected ProfilerMarker m_RenderMarker = new ProfilerMarker("ExtrudedRopeRendering");
protected ObiSolver m_Solver;
protected SubMeshDescriptor subMeshDescriptor = new SubMeshDescriptor(0, 0);
protected List<ProceduralRenderBatch<ProceduralRopeVertex>> batchList = new List<ProceduralRenderBatch<ProceduralRopeVertex>>();
protected ObiNativeList<int> pathSmootherIndices;
protected ObiNativeList<BurstLineMeshData> rendererData; /**< for each renderer, data about smoother.*/
protected ObiNativeList<int> vertexOffsets; /**< for each renderer, vertex offset in its batch mesh data.*/
protected ObiNativeList<int> triangleOffsets; /**< for each renderer, triangle offset in its batch mesh data.*/
protected ObiNativeList<int> vertexCounts; /**< for each renderer, vertex count.*/
protected ObiNativeList<int> triangleCounts; /**< for each renderer, triangle count.*/
protected ObiPathSmootherRenderSystem pathSmootherSystem;
#if (UNITY_2019_1_OR_NEWER)
System.Action<ScriptableRenderContext, Camera> renderCallback;
#endif
public ObiLineRopeRenderSystem(ObiSolver solver)
{
#if (UNITY_2019_1_OR_NEWER)
renderCallback = new System.Action<ScriptableRenderContext, Camera>((cntxt, cam) => { RenderFromCamera(cam); });
RenderPipelineManager.beginCameraRendering += renderCallback;
#endif
Camera.onPreCull += RenderFromCamera;
m_Solver = solver;
pathSmootherIndices = new ObiNativeList<int>();
rendererData = new ObiNativeList<BurstLineMeshData>();
vertexOffsets = new ObiNativeList<int>();
triangleOffsets = new ObiNativeList<int>();
vertexCounts = new ObiNativeList<int>();
triangleCounts = new ObiNativeList<int>();
}
public void Dispose()
{
#if (UNITY_2019_1_OR_NEWER)
RenderPipelineManager.beginCameraRendering -= renderCallback;
#endif
Camera.onPreCull -= RenderFromCamera;
for (int i = 0; i < batchList.Count; ++i)
batchList[i].Dispose();
batchList.Clear();
if (pathSmootherIndices != null)
pathSmootherIndices.Dispose();
if (rendererData != null)
rendererData.Dispose();
if (vertexOffsets != null)
vertexOffsets.Dispose();
if (triangleOffsets != null)
triangleOffsets.Dispose();
if (vertexCounts != null)
vertexCounts.Dispose();
if (triangleCounts != null)
triangleCounts.Dispose();
}
private void Clear()
{
pathSmootherIndices.Clear();
rendererData.Clear();
vertexOffsets.Clear();
vertexCounts.Clear();
triangleCounts.Clear();
for (int i = 0; i < batchList.Count; ++i)
batchList[i].Dispose();
batchList.Clear();
}
private void CreateBatches()
{
// generate batches:
sortedRenderers.Clear();
for (int i = 0; i < renderers.Count; ++i)
{
if (renderers[i].TryGetComponent(out ObiPathSmoother smoother) && smoother.enabled)
{
renderers[i].renderParams.layer = renderers[i].gameObject.layer;
batchList.Add(new ProceduralRenderBatch<ProceduralRopeVertex>(i, renderers[i].material, renderers[i].renderParams));
sortedRenderers.Add(renderers[i]);
}
}
vertexOffsets.ResizeUninitialized(sortedRenderers.Count);
triangleOffsets.ResizeUninitialized(sortedRenderers.Count);
// sort batches:
batchList.Sort();
// reorder renderers based on sorted batches:
sortedRenderers.Clear();
for (int i = 0; i < batchList.Count; ++i)
{
var batch = batchList[i];
sortedRenderers.Add(renderers[batch.firstRenderer]);
batch.firstRenderer = i;
int pathIndex = sortedRenderers[i].GetComponent<ObiPathSmoother>().indexInSystem;
pathSmootherIndices.Add(pathIndex);
// calculate vertex and triangle counts for each renderer:
int chunkStart = pathSmootherSystem.chunkOffsets[pathIndex];
int chunkAmount = pathSmootherSystem.chunkOffsets[pathIndex + 1] - chunkStart;
for (int k = chunkStart; k < chunkStart + chunkAmount; ++k)
{
int frameCount = pathSmootherSystem.smoothFrameCounts[k];
batch.vertexCount += frameCount * 2; // in a triangle strip, there's 2 vertices per frame.
batch.triangleCount += (frameCount - 1) * 2; // and 2 triangles per frame (except for the last one)
}
vertexCounts.Add(batch.vertexCount);
triangleCounts.Add(batch.triangleCount);
rendererData.Add(new BurstLineMeshData(sortedRenderers[i]));
}
}
private void CalculateMeshOffsets()
{
for (int i = 0; i < batchList.Count; ++i)
{
var batch = batchList[i];
int vtxCount = 0;
int triCount = 0;
// Calculate vertex and triangle offsets for each renderer in the batch:
for (int j = 0; j < batch.rendererCount; ++j)
{
int r = batch.firstRenderer + j;
vertexOffsets[r] = vtxCount;
triangleOffsets[r] = triCount;
vtxCount += vertexCounts[r];
triCount += triangleCounts[r];
}
}
}
public virtual void Setup()
{
pathSmootherSystem = m_Solver.GetRenderSystem<ObiPathSmoother>() as ObiPathSmootherRenderSystem;
if (pathSmootherSystem == null)
return;
using (m_SetupRenderMarker.Auto())
{
Clear();
CreateBatches();
ObiUtils.MergeBatches(batchList);
CalculateMeshOffsets();
}
}
public abstract void RenderFromCamera(Camera camera);
public abstract void Render();
public void Step()
{
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: feb78fd6bed7a49979dee4205d74deaa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,296 @@
using System.Runtime.InteropServices;
using UnityEngine;
using Unity.Profiling;
using UnityEngine.Rendering;
using System.Collections.Generic;
using System;
namespace Obi
{
[StructLayout(LayoutKind.Sequential)]
public struct BurstMeshData
{
public uint axis;
public float volumeScaling;
public uint stretchWithRope;
public uint spanEntireLength;
public uint instances;
public float instanceSpacing;
public float offset;
public float meshSizeAlongAxis;
public Vector4 scale;
public BurstMeshData(ObiRopeMeshRenderer renderer)
{
axis = (uint)renderer.axis;
volumeScaling = renderer.volumeScaling;
stretchWithRope = (uint)(renderer.stretchWithRope ? 1 : 0);
spanEntireLength = (uint)(renderer.spanEntireLength ? 1 : 0);
instances = renderer.instances;
instanceSpacing = renderer.instanceSpacing;
offset = renderer.offset;
meshSizeAlongAxis = renderer.sourceMesh != null ? renderer.sourceMesh.bounds.size[(int)renderer.axis] : 0;
scale = renderer.scale;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct RopeMeshVertex
{
public Vector3 pos;
public Vector3 normal;
public Vector4 tangent;
public Vector4 color;
}
public abstract class ObiMeshRopeRenderSystem : RenderSystem<ObiRopeMeshRenderer>
{
public Oni.RenderingSystemType typeEnum { get => Oni.RenderingSystemType.MeshRope; }
public RendererSet<ObiRopeMeshRenderer> renderers { get; } = new RendererSet<ObiRopeMeshRenderer>();
protected List<ObiRopeMeshRenderer> sortedRenderers = new List<ObiRopeMeshRenderer>(); /**< temp list used to store renderers sorted by batch.*/
static protected ProfilerMarker m_SetupRenderMarker = new ProfilerMarker("SetupMeshRopeRendering");
static protected ProfilerMarker m_RenderMarker = new ProfilerMarker("MeshRopeRendering");
// specify vertex count and layout
protected VertexAttributeDescriptor[] layout =
{
new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3,0),
new VertexAttributeDescriptor(VertexAttribute.Normal, VertexAttributeFormat.Float32, 3,0),
new VertexAttributeDescriptor(VertexAttribute.Tangent, VertexAttributeFormat.Float32, 4,0),
new VertexAttributeDescriptor(VertexAttribute.Color, VertexAttributeFormat.Float32, 4,0),
new VertexAttributeDescriptor(VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 2,1),
new VertexAttributeDescriptor(VertexAttribute.TexCoord1, VertexAttributeFormat.Float32, 2,1),
new VertexAttributeDescriptor(VertexAttribute.TexCoord2, VertexAttributeFormat.Float32, 2,1),
new VertexAttributeDescriptor(VertexAttribute.TexCoord3, VertexAttributeFormat.Float32, 2,1),
};
protected ObiSolver m_Solver;
protected List<DynamicRenderBatch<ObiRopeMeshRenderer>> batchList = new List<DynamicRenderBatch<ObiRopeMeshRenderer>>();
protected MeshDataBatch meshData;
protected ObiNativeList<int> meshIndices; // for each renderer, its mesh index.
protected ObiNativeList<int> pathSmootherIndices; /**< for each renderer, index of its path smoother in the path smoother system.*/
protected ObiNativeList<BurstMeshData> rendererData;
protected ObiNativeList<int> sortedIndices; /**< axis-sorted vertex indices. */
protected ObiNativeList<int> sortedOffsets; /**< for each renderer, offset in the sortedIndices array.*/
protected ObiNativeList<int> vertexOffsets; /**< for each renderer, vertex offset in its batch mesh data.*/
protected ObiNativeList<int> vertexCounts; /**< for each renderer, vertex count.*/
protected ObiPathSmootherRenderSystem pathSmootherSystem;
public ObiMeshRopeRenderSystem(ObiSolver solver)
{
m_Solver = solver;
meshData = new MeshDataBatch();
meshIndices = new ObiNativeList<int>();
pathSmootherIndices = new ObiNativeList<int>();
rendererData = new ObiNativeList<BurstMeshData>();
sortedIndices = new ObiNativeList<int>();
sortedOffsets = new ObiNativeList<int>();
vertexOffsets = new ObiNativeList<int>();
vertexCounts = new ObiNativeList<int>();
}
public void Dispose()
{
for (int i = 0; i < batchList.Count; ++i)
batchList[i].Dispose();
batchList.Clear();
meshData.Dispose();
if (pathSmootherIndices != null)
pathSmootherIndices.Dispose();
if (meshIndices != null)
meshIndices.Dispose();
if (sortedIndices != null)
sortedIndices.Dispose();
if (sortedOffsets != null)
sortedOffsets.Dispose();
if (vertexOffsets != null)
vertexOffsets.Dispose();
if (vertexCounts != null)
vertexCounts.Dispose();
if (rendererData != null)
rendererData.Dispose();
}
private void Clear()
{
meshData.Clear();
meshIndices.Clear();
pathSmootherIndices.Clear();
rendererData.Clear();
vertexOffsets.Clear();
vertexCounts.Clear();
sortedIndices.Clear();
sortedOffsets.Clear();
for (int i = 0; i < batchList.Count; ++i)
batchList[i].Dispose();
batchList.Clear();
meshData.InitializeStaticData();
meshData.InitializeTempData();
}
private void CreateBatches()
{
// generate batches:
sortedRenderers.Clear();
for (int i = 0; i < renderers.Count; ++i)
{
if (renderers[i].sourceMesh != null && renderers[i].TryGetComponent(out ObiPathSmoother smoother) && smoother.enabled)
{
int vertexCount = renderers[i].vertexCount * (int)renderers[i].meshInstances;
renderers[i].renderParameters.layer = renderers[i].gameObject.layer;
batchList.Add(new DynamicRenderBatch<ObiRopeMeshRenderer>(i, vertexCount, renderers[i].materials, renderers[i].renderParameters));
sortedRenderers.Add(renderers[i]);
}
}
vertexOffsets.ResizeUninitialized(sortedRenderers.Count);
// sort batches:
batchList.Sort();
// reorder renderers based on sorted batches:
sortedRenderers.Clear();
for (int i = 0; i < batchList.Count; ++i)
{
var batch = batchList[i];
// store amount of vertices in this batch, prior to merging:
vertexCounts.Add(batch.vertexCount);
// write renderers in the order dictated by the sorted batch:
sortedRenderers.Add(renderers[batch.firstRenderer]);
batch.firstRenderer = i;
pathSmootherIndices.Add(sortedRenderers[i].GetComponent<ObiPathSmoother>().indexInSystem);
rendererData.Add(new BurstMeshData(sortedRenderers[i]));
}
}
protected virtual void PopulateBatches()
{
List<Vector3> verts = new List<Vector3>();
// store per-mesh data
for (int i = 0; i < sortedRenderers.Count; ++i)
{
// sort vertices along curve axis:
sortedRenderers[i].GetVertices(verts);
float[] keys = new float[sortedRenderers[i].vertexCount];
var orderedVertices = new int[sortedRenderers[i].vertexCount];
for (int j = 0; j < keys.Length; ++j)
{
keys[j] = verts[j][(int)sortedRenderers[i].axis];
orderedVertices[j] = j;
}
Array.Sort(keys, orderedVertices);
sortedOffsets.Add(sortedIndices.count);
sortedIndices.AddRange(orderedVertices);
// add mesh index
meshIndices.Add(meshData.AddMesh(sortedRenderers[i]));
}
}
private void CalculateMeshOffsets()
{
for (int i = 0; i < batchList.Count; ++i)
{
var batch = batchList[i];
int vtxCount = 0;
// Calculate vertex and triangle offsets for each renderer in the batch:
for (int j = 0; j < batch.rendererCount; ++j)
{
int r = batch.firstRenderer + j;
vertexOffsets[r] = vtxCount;
vtxCount += vertexCounts[r];
}
}
}
protected virtual void CloseBatches()
{
meshData.DisposeOfStaticData();
meshData.DisposeOfTempData();
}
public void Setup()
{
pathSmootherSystem = m_Solver.GetRenderSystem<ObiPathSmoother>() as ObiPathSmootherRenderSystem;
if (pathSmootherSystem == null)
return;
using (m_SetupRenderMarker.Auto())
{
Clear();
CreateBatches();
PopulateBatches();
ObiUtils.MergeBatches(batchList);
CalculateMeshOffsets();
CloseBatches();
}
}
public void Step()
{
}
public virtual void Render()
{
}
public void BakeMesh(ObiRopeMeshRenderer renderer, ref Mesh mesh, bool transformToActorLocalSpace = false)
{
int index = sortedRenderers.IndexOf(renderer);
for (int i = 0; i < batchList.Count; ++i)
{
var batch = batchList[i];
if (index >= batch.firstRenderer && index < batch.firstRenderer + batch.rendererCount)
{
batch.BakeMesh(sortedRenderers, renderer, ref mesh, transformToActorLocalSpace);
return;
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ad311e324f23a480d867e8e9b7f89cfe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,325 @@
using System.Runtime.InteropServices;
using UnityEngine;
using Unity.Profiling;
namespace Obi
{
[StructLayout(LayoutKind.Sequential)]
public struct BurstPathSmootherData
{
public uint smoothing;
public float decimation;
public float twist;
public float restLength;
public float smoothLength;
public uint usesOrientedParticles;
public BurstPathSmootherData(ObiRopeBase rope, ObiPathSmoother smoother)
{
smoothing = smoother.smoothing;
decimation = smoother.decimation;
twist = smoother.twist;
usesOrientedParticles = (uint)(rope.usesOrientedParticles ? 1 : 0);
restLength = rope.restLength;
smoothLength = 0;
}
}
public abstract class ObiPathSmootherRenderSystem : RenderSystem<ObiPathSmoother>
{
public Oni.RenderingSystemType typeEnum { get => Oni.RenderingSystemType.AllSmoothedRopes; }
public RendererSet<ObiPathSmoother> renderers { get; } = new RendererSet<ObiPathSmoother>();
static protected ProfilerMarker m_SetupRenderMarker = new ProfilerMarker("SetupSmoothPathRendering");
static protected ProfilerMarker m_RenderMarker = new ProfilerMarker("SmoothPathRendering");
protected ObiSolver m_Solver;
public ObiNativeList<int> particleIndices;
public ObiNativeList<int> chunkOffsets; /**< for each actor, index of the first chunk */
public ObiNativeList<BurstPathSmootherData> pathData; /**< for each chunk, smoother params/data.*/
public ObiNativeList<ObiPathFrame> rawFrames;
public ObiNativeList<int> rawFrameOffsets; /**< index of the first frame for each chunk.*/
public ObiNativeList<int> decimatedFrameCounts; /**< amount of frames in each chunk, after decimation.*/
public ObiNativeList<ObiPathFrame> smoothFrames;
public ObiNativeList<int> smoothFrameOffsets; /**< index of the first frame for each chunk.*/
public ObiNativeList<int> smoothFrameCounts; /**< amount of smooth frames for each chunk.*/
// path smoothing must be done before all other rope render systems, which are on a higher tier.
public uint tier
{
get { return 0; }
}
public ObiPathSmootherRenderSystem(ObiSolver solver)
{
m_Solver = solver;
pathData = new ObiNativeList<BurstPathSmootherData>();
particleIndices = new ObiNativeList<int>();
chunkOffsets = new ObiNativeList<int>();
rawFrames = new ObiNativeList<ObiPathFrame>();
rawFrameOffsets = new ObiNativeList<int>();
decimatedFrameCounts = new ObiNativeList<int>();
smoothFrames = new ObiNativeList<ObiPathFrame>();
smoothFrameOffsets = new ObiNativeList<int>();
smoothFrameCounts = new ObiNativeList<int>();
}
public void Dispose()
{
if (particleIndices != null)
particleIndices.Dispose();
if (chunkOffsets != null)
chunkOffsets.Dispose();
if (pathData != null)
pathData.Dispose();
if (rawFrames != null)
rawFrames.Dispose();
if (rawFrameOffsets != null)
rawFrameOffsets.Dispose();
if (decimatedFrameCounts != null)
decimatedFrameCounts.Dispose();
if (smoothFrames != null)
smoothFrames.Dispose();
if (smoothFrameOffsets != null)
smoothFrameOffsets.Dispose();
if (smoothFrameCounts != null)
smoothFrameCounts.Dispose();
}
private void Clear()
{
pathData.Clear();
particleIndices.Clear();
chunkOffsets.Clear();
rawFrames.Clear();
rawFrameOffsets.Clear();
decimatedFrameCounts.Clear();
smoothFrames.Clear();
smoothFrameOffsets.Clear();
smoothFrameCounts.Clear();
}
private int GetChaikinCount(int initialPoints, uint recursionLevel)
{
if (recursionLevel <= 0 || initialPoints < 3)
return initialPoints;
// calculate amount of new points generated by each inner control point:
int pCount = (int)Mathf.Pow(2, recursionLevel);
return (initialPoints - 2) * pCount + 2;
}
public virtual void Setup()
{
using (m_SetupRenderMarker.Auto())
{
Clear();
int actorCount = 0;
int chunkCount = 0;
int rawFrameCount = 0;
for (int i = 0; i < renderers.Count; ++i)
{
var renderer = renderers[i];
var rope = renderer.actor as ObiRopeBase;
var data = new BurstPathSmootherData(rope, renderer);
chunkOffsets.Add(chunkCount);
// iterate trough elements, finding discontinuities as we go:
for (int e = 0; e < rope.elements.Count; ++e)
{
rawFrameCount++;
particleIndices.Add(rope.elements[e].particle1);
// At discontinuities, start a new chunk.
if (e < rope.elements.Count - 1 && rope.elements[e].particle2 != rope.elements[e + 1].particle1)
{
rawFrameOffsets.Add(++rawFrameCount);
particleIndices.Add(rope.elements[e].particle2);
pathData.Add(data);
chunkCount++;
}
}
chunkCount++;
rawFrameOffsets.Add(++rawFrameCount);
particleIndices.Add(rope.elements[rope.elements.Count - 1].particle2);
pathData.Add(data);
// store the index in this system, so that other render systems
// in higher tiers can easily access smooth path data:
renderer.indexInSystem = actorCount++;
}
// Add last entry (total amount of chunks):
chunkOffsets.Add(chunkCount);
// resize storage:
rawFrames.ResizeUninitialized(rawFrameCount);
decimatedFrameCounts.ResizeUninitialized(rawFrameOffsets.count);
smoothFrameOffsets.ResizeUninitialized(rawFrameOffsets.count);
smoothFrameCounts.ResizeUninitialized(rawFrameOffsets.count);
// calculate smooth chunk counts:
int smoothFrameCount = 0;
for (int i = 0; i < rawFrameOffsets.count; ++i)
{
int frameCount = rawFrameOffsets[i] - (i > 0 ? rawFrameOffsets[i - 1] : 0);
int smoothCount = GetChaikinCount(frameCount, pathData[i].smoothing);
smoothFrameOffsets[i] = smoothFrameCount;
smoothFrameCounts[i] = smoothCount;
smoothFrameCount += smoothCount;
}
smoothFrames.ResizeUninitialized(smoothFrameCount);
}
}
public int GetChunkCount(int rendererIndex)
{
rendererIndex = Mathf.Clamp(rendererIndex, 0, renderers.Count);
if (rendererIndex >= chunkOffsets.count)
return 0;
return chunkOffsets[rendererIndex + 1] - chunkOffsets[rendererIndex];
}
public int GetSmoothFrameCount(int rendererIndex)
{
rendererIndex = Mathf.Clamp(rendererIndex, 0, renderers.Count);
int frameCount = 0;
if (rendererIndex >= chunkOffsets.count)
return frameCount;
for (int i = chunkOffsets[rendererIndex]; i < chunkOffsets[rendererIndex + 1]; ++i)
frameCount += smoothFrameCounts[i];
return frameCount;
}
public int GetSmoothFrameCount(int rendererIndex, int chunkIndex)
{
rendererIndex = Mathf.Clamp(rendererIndex, 0, renderers.Count);
if (rendererIndex >= chunkOffsets.count)
return 0;
int chunkCount = chunkOffsets[rendererIndex + 1] - chunkOffsets[rendererIndex];
int chunk = chunkOffsets[rendererIndex] + Mathf.Clamp(chunkIndex, 0, chunkCount);
return smoothFrameCounts[chunk];
}
public float GetSmoothLength(int rendererIndex)
{
rendererIndex = Mathf.Clamp(rendererIndex, 0, renderers.Count);
float smoothLength = 0;
if (rendererIndex >= chunkOffsets.count)
return smoothLength;
for (int i = chunkOffsets[rendererIndex]; i < chunkOffsets[rendererIndex + 1]; ++i)
smoothLength += pathData[i].smoothLength;
return smoothLength;
}
public ObiPathFrame GetFrameAt(int rendererIndex, int chunkIndex, int frameIndex)
{
rendererIndex = Mathf.Clamp(rendererIndex, 0, renderers.Count);
if (rendererIndex >= chunkOffsets.count)
return ObiPathFrame.Identity;
int chunkCount = chunkOffsets[rendererIndex + 1] - chunkOffsets[rendererIndex];
int chunk = chunkOffsets[rendererIndex] + Mathf.Clamp(chunkIndex, 0, chunkCount);
return smoothFrames[smoothFrameOffsets[chunk] + Mathf.Clamp(frameIndex, 0, smoothFrameCounts[chunk])];
}
public ObiPathFrame GetFrameAt(int rendererIndex, float mu)
{
rendererIndex = Mathf.Clamp(rendererIndex, 0, renderers.Count);
if (rendererIndex >= chunkOffsets.count)
return ObiPathFrame.Identity;
float length = 0;
for (int i = chunkOffsets[rendererIndex]; i < chunkOffsets[rendererIndex + 1]; ++i)
length += pathData[i].smoothLength;
length *= mu;
// iterate trough all chunks:
float lerp = 0;
int frame = 0;
for (int i = chunkOffsets[rendererIndex]; i < chunkOffsets[rendererIndex + 1]; ++i)
{
int firstFrame = smoothFrameOffsets[i];
int frameCount = smoothFrameCounts[i];
// iterate trough all frames in this chunk, accumulating distance:
for (int j = firstFrame + 1; j < firstFrame + frameCount; ++j)
{
float frameDistance = Vector3.Distance(smoothFrames[j - 1].position,
smoothFrames[j].position);
lerp = length / frameDistance;
length -= frameDistance;
frame = j;
if (length <= 0)
return (1 - lerp) * smoothFrames[j - 1] + lerp * smoothFrames[j];
}
}
// if no chunks/no frames, return default frame.
return (1 - lerp) * smoothFrames[frame - 1] + lerp * smoothFrames[frame];
}
public void Step()
{
}
public virtual void Render()
{
// Update rest lengths, in case they've changed due to cursors:
for (int i = 0; i < renderers.Count; ++i)
{
var rope = renderers[i].actor as ObiRopeBase;
for (int j = chunkOffsets[i]; j < chunkOffsets[i + 1]; ++j)
{
var data = pathData[j];
data.restLength = rope.restLength;
pathData[j] = data;
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fab1fee6e450d4e28a7a2770af3c94d9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,79 @@
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, ObiActorRenderer<ObiRopeChainRenderer>
{
[Serializable]
public struct LinkModifier
{
public Vector3 translation;
public Vector3 scale;
public Vector3 rotation;
public void Clear()
{
translation = Vector3.zero;
scale = Vector3.one;
rotation = Vector3.zero;
}
}
public Mesh linkMesh;
public Material linkMaterial;
public Vector3 linkScale = Vector3.one; /**< Scale of chain links.*/
[Range(0, 1)]
public float twistAnchor = 0; /**< Normalized position of twisting origin along rope.*/
public float linkTwist = 0; /**< Amount of twist applied to each section, in degrees.*/
public List<LinkModifier> linkModifiers = new List<LinkModifier>();
public RenderBatchParams renderParameters = new RenderBatchParams(true);
public ObiActor actor { get; private set; }
void Awake()
{
actor = GetComponent<ObiActor>();
}
public void OnEnable()
{
((ObiActorRenderer<ObiRopeChainRenderer>)this).EnableRenderer();
}
public void OnDisable()
{
((ObiActorRenderer<ObiRopeChainRenderer>)this).DisableRenderer();
}
public void OnValidate()
{
((ObiActorRenderer<ObiRopeChainRenderer>)this).SetRendererDirty(Oni.RenderingSystemType.ChainRope);
}
RenderSystem<ObiRopeChainRenderer> ObiRenderer<ObiRopeChainRenderer>.CreateRenderSystem(ObiSolver solver)
{
switch (solver.backendType)
{
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
case ObiSolver.BackendType.Burst: return new BurstChainRopeRenderSystem(solver);
#endif
case ObiSolver.BackendType.Compute:
default:
if (SystemInfo.supportsComputeShaders)
return new ComputeChainRopeRenderSystem(solver);
return null;
}
}
}
}

View File

@@ -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:

View File

@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using Unity.Profiling;
namespace Obi
{
[AddComponentMenu("Physics/Obi/Obi Rope Extruded Renderer", 883)]
[ExecuteInEditMode]
[RequireComponent(typeof(ObiPathSmoother))]
public class ObiRopeExtrudedRenderer : MonoBehaviour, ObiActorRenderer<ObiRopeExtrudedRenderer>
{
public ObiPathSmoother smoother { get; private set; } // Each renderer should have its own smoother. The renderer then has a method to get position and orientation at a point.
public Material material;
public RenderBatchParams renderParameters = new RenderBatchParams(true);
[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.*/
public ObiActor actor { get; private set; }
public void Awake()
{
actor = GetComponent<ObiActor>();
}
public void OnEnable()
{
smoother = GetComponent<ObiPathSmoother>();
((ObiActorRenderer<ObiRopeExtrudedRenderer>)this).EnableRenderer();
}
public void OnDisable()
{
((ObiActorRenderer<ObiRopeExtrudedRenderer>)this).DisableRenderer();
}
public void OnValidate()
{
((ObiActorRenderer<ObiRopeExtrudedRenderer>)this).SetRendererDirty(Oni.RenderingSystemType.AllSmoothedRopes);
}
RenderSystem<ObiRopeExtrudedRenderer> ObiRenderer<ObiRopeExtrudedRenderer>.CreateRenderSystem(ObiSolver solver)
{
switch (solver.backendType)
{
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
case ObiSolver.BackendType.Burst: return new BurstExtrudedRopeRenderSystem(solver);
#endif
case ObiSolver.BackendType.Compute:
default:
if (SystemInfo.supportsComputeShaders)
return new ComputeExtrudedRopeRenderSystem(solver);
return null;
}
}
}
}

View File

@@ -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:

View File

@@ -0,0 +1,68 @@
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(ObiPathSmoother))]
public class ObiRopeLineRenderer : MonoBehaviour, ObiActorRenderer<ObiRopeLineRenderer>
{
public ObiActor actor { get; private set; }
public Material material;
public RenderBatchParams renderParams = new RenderBatchParams(true);
[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.*/
public void Awake()
{
actor = GetComponent<ObiActor>();
}
void OnEnable()
{
((ObiActorRenderer<ObiRopeLineRenderer>)this).EnableRenderer();
}
void OnDisable()
{
((ObiActorRenderer<ObiRopeLineRenderer>)this).DisableRenderer();
}
public void OnValidate()
{
((ObiActorRenderer<ObiRopeLineRenderer>)this).SetRendererDirty(Oni.RenderingSystemType.LineRope);
}
RenderSystem<ObiRopeLineRenderer> ObiRenderer<ObiRopeLineRenderer>.CreateRenderSystem(ObiSolver solver)
{
switch (solver.backendType)
{
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
case ObiSolver.BackendType.Burst: return new BurstLineRopeRenderSystem(solver);
#endif
case ObiSolver.BackendType.Compute:
default:
if (SystemInfo.supportsComputeShaders)
return new ComputeLineRopeRenderSystem(solver);
return null;
}
}
}
}

View File

@@ -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:

View File

@@ -0,0 +1,86 @@
using System.Collections.Generic;
using UnityEngine;
namespace Obi
{
[AddComponentMenu("Physics/Obi/Obi Rope Mesh Renderer", 886)]
[ExecuteInEditMode]
[RequireComponent(typeof(ObiPathSmoother))]
public class ObiRopeMeshRenderer : MonoBehaviour, ObiActorRenderer<ObiRopeMeshRenderer>, IMeshDataProvider
{
public Renderer sourceRenderer { get; protected set; }
public ObiActor actor { get; private set; }
public uint meshInstances { get {return instances;} }
[field: SerializeField]
public Mesh sourceMesh { get; set; }
[field: SerializeField]
public Material[] materials { get; set; }
public virtual int vertexCount { get { return sourceMesh ? sourceMesh.vertexCount : 0; } }
public virtual int triangleCount { get { return sourceMesh ? sourceMesh.triangles.Length / 3 : 0; } }
public RenderBatchParams renderParameters = new RenderBatchParams(true);
public ObiPathFrame.Axis axis;
public float volumeScaling = 0;
public bool stretchWithRope = true;
public bool spanEntireLength = true;
public uint instances = 1;
public float instanceSpacing = 0;
public float offset = 0;
public Vector3 scale = Vector3.one;
public void Awake()
{
actor = GetComponent<ObiActor>();
sourceRenderer = GetComponent<MeshRenderer>();
}
public void OnEnable()
{
((ObiActorRenderer<ObiRopeMeshRenderer>)this).EnableRenderer();
}
public void OnDisable()
{
((ObiActorRenderer<ObiRopeMeshRenderer>)this).DisableRenderer();
}
public void OnValidate()
{
((ObiActorRenderer<ObiRopeMeshRenderer>)this).SetRendererDirty(Oni.RenderingSystemType.MeshRope);
}
RenderSystem<ObiRopeMeshRenderer> ObiRenderer<ObiRopeMeshRenderer>.CreateRenderSystem(ObiSolver solver)
{
switch (solver.backendType)
{
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
case ObiSolver.BackendType.Burst: return new BurstMeshRopeRenderSystem(solver);
#endif
case ObiSolver.BackendType.Compute:
default:
if (SystemInfo.supportsComputeShaders)
return new ComputeMeshRopeRenderSystem(solver);
return null;
}
}
public virtual void GetVertices(List<Vector3> vertices) { sourceMesh.GetVertices(vertices); }
public virtual void GetNormals(List<Vector3> normals) { sourceMesh.GetNormals(normals); }
public virtual void GetTangents(List<Vector4> tangents) { sourceMesh.GetTangents(tangents); }
public virtual void GetColors(List<Color> colors) { sourceMesh.GetColors(colors); }
public virtual void GetUVs(int channel, List<Vector2> uvs) { sourceMesh.GetUVs(channel, uvs); }
public virtual void GetTriangles(List<int> triangles) { triangles.Clear(); triangles.AddRange(sourceMesh.triangles); }
}
}

View File

@@ -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:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 55cc87411ee7647b4a078becb6a5ed01
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,337 @@
using System;
using UnityEngine;
namespace Obi
{
[AddComponentMenu("Physics/Obi/Obi Pinhole", 820)]
[RequireComponent(typeof(ObiRopeBase))]
[ExecuteInEditMode]
public class ObiPinhole : MonoBehaviour
{
[SerializeField] [HideInInspector] private ObiRopeBase m_Rope;
[SerializeField] [HideInInspector] private Transform m_Target;
[Range(0, 1)]
[SerializeField] [HideInInspector] private float m_Position = 0;
[SerializeField] [HideInInspector] private bool m_LimitRange = false;
[MinMax(0, 1)]
[SerializeField] [HideInInspector] private Vector2 m_Range = new Vector2(0, 1);
[Range(0, 1)]
[SerializeField] [HideInInspector] private float m_Friction = 0;
[SerializeField] [HideInInspector] private float m_MotorSpeed = 0;
[SerializeField] [HideInInspector] private float m_MotorForce = 0;
[SerializeField] [HideInInspector] private float m_Compliance = 0;
[SerializeField] [HideInInspector] private bool m_ClampAtEnds = true;
[SerializeField] [HideInInspector] private ObiPinholeConstraintsBatch.PinholeEdge currentEdge;
[SerializeField] [HideInInspector] public ObiPinholeConstraintsBatch.PinholeEdge firstEdge;
[SerializeField] [HideInInspector] public ObiPinholeConstraintsBatch.PinholeEdge lastEdge;
// private variables are serialized during script reloading, to keep their value. Must mark them explicitly as non-serialized.
[NonSerialized] private ObiPinholeConstraintsBatch pinBatch;
[NonSerialized] private ObiColliderBase attachedCollider;
[NonSerialized] private int attachedColliderHandleIndex;
[NonSerialized] private Vector3 m_PositionOffset;
[NonSerialized] private bool m_ParametersDirty = true;
[NonSerialized] private bool m_PositionDirty = false;
[NonSerialized] private bool m_RangeDirty = false;
/// <summary>
/// The rope this attachment is added to.
/// </summary>
public ObiActor rope
{
get { return m_Rope; }
}
public float edgeCoordinate
{
get { return currentEdge.coordinate; }
}
public int edgeIndex
{
get { return currentEdge.edgeIndex; }
}
/// <summary>
/// The target transform that the pinhole should be attached to.
/// </summary>
public Transform target
{
get { return m_Target; }
set
{
if (value != m_Target)
{
m_Target = value;
Bind();
}
}
}
/// <summary>
/// Normalized coordinate of the point along the rope where the pinhole is positioned.
/// </summary>
public float position
{
get { return m_Position; }
set
{
if (!Mathf.Approximately(value, m_Position))
{
m_Position = value;
CalculateMu();
}
}
}
public bool limitRange
{
get { return m_LimitRange; }
set
{
if (m_LimitRange != value)
{
m_LimitRange = value;
CalculateRange();
}
}
}
/// <summary>
/// Normalized coordinate of the point along the rope where the pinhole is positioned.
/// </summary>
public Vector2 range
{
get { return m_Range; }
set
{
m_Range = value;
CalculateRange();
}
}
/// <summary>
/// Whether this pinhole is currently bound or not.
/// </summary>
public bool isBound
{
get { return m_Target != null && currentEdge.edgeIndex >= 0; }
}
/// <summary>
/// Constraint compliance.
/// </summary>
/// High compliance values will increase the pinhole's elasticity.
public float compliance
{
get { return m_Compliance; }
set
{
if (!Mathf.Approximately(value, m_Compliance))
{
m_Compliance = value;
m_ParametersDirty = true;
}
}
}
public float friction
{
get { return m_Friction; }
set
{
if (!Mathf.Approximately(value, m_Friction))
{
m_Friction = value;
m_ParametersDirty = true;
}
}
}
public float motorSpeed
{
get { return m_MotorSpeed; }
set
{
if (!Mathf.Approximately(value, m_MotorSpeed))
{
m_MotorSpeed = value;
m_ParametersDirty = true;
}
}
}
public float motorForce
{
get { return m_MotorForce; }
set
{
if (!Mathf.Approximately(value, m_MotorForce))
{
m_MotorForce = value;
m_ParametersDirty = true;
}
}
}
public bool clampAtEnds
{
get { return m_ClampAtEnds; }
set
{
if (m_ClampAtEnds != value)
{
m_ClampAtEnds = value;
m_ParametersDirty = true;
}
}
}
/// <summary>
/// Force threshold above which the pinhole should break.
/// </summary>
[Delayed] public float breakThreshold = float.PositiveInfinity;
public float relativeVelocity { get; private set; }
private void OnEnable()
{
m_Rope = GetComponent<ObiRopeBase>();
m_Rope.OnBlueprintLoaded += Actor_OnBlueprintLoaded;
m_Rope.OnSimulationStart += Actor_OnSimulationStart;
m_Rope.OnRequestReadback += Actor_OnRequestReadback;
if (m_Rope.solver != null)
Actor_OnBlueprintLoaded(m_Rope, m_Rope.sourceBlueprint);
EnablePinhole();
}
private void OnDisable()
{
DisablePinhole();
m_Rope.OnBlueprintLoaded -= Actor_OnBlueprintLoaded;
m_Rope.OnSimulationStart -= Actor_OnSimulationStart;
m_Rope.OnRequestReadback -= Actor_OnRequestReadback;
}
private void OnValidate()
{
m_Rope = GetComponent<ObiRopeBase>();
m_ParametersDirty = true;
m_PositionDirty = true;
m_RangeDirty = true;
}
private void Actor_OnBlueprintLoaded(ObiActor act, ObiActorBlueprint blueprint)
{
Bind();
}
private void Actor_OnSimulationStart(ObiActor act, float stepTime, float substepTime)
{
// Attachments must be updated at the start of the step, before performing any simulation.
UpdatePinhole();
// if there's any broken constraint, flag pinhole constraints as dirty for remerging at the start of the next step.
BreakPinhole(substepTime);
}
private void Actor_OnRequestReadback(ObiActor actor)
{
if (enabled && m_Rope.isLoaded && isBound)
{
var solver = m_Rope.solver;
var actorConstraints = m_Rope.GetConstraintsByType(Oni.ConstraintType.Pinhole) as ObiConstraints<ObiPinholeConstraintsBatch>;
var solverConstraints = solver.GetConstraintsByType(Oni.ConstraintType.Pinhole) as ObiConstraints<ObiPinholeConstraintsBatch>;
if (actorConstraints != null && pinBatch != null && actorConstraints.batchCount <= solverConstraints.batchCount)
{
int pinBatchIndex = actorConstraints.batches.IndexOf(pinBatch);
if (pinBatchIndex >= 0 && pinBatchIndex < rope.solverBatchOffsets[(int)Oni.ConstraintType.Pinhole].Count)
{
var solverBatch = solverConstraints.batches[pinBatchIndex];
solverBatch.particleIndices.Readback();
solverBatch.edgeMus.Readback();
solverBatch.relativeVelocities.Readback();
}
}
}
}
private void ClampMuToRange()
{
if (m_LimitRange)
{
float maxCoord = lastEdge.GetRopeCoordinate(m_Rope);
float minCoord = firstEdge.GetRopeCoordinate(m_Rope);
if (m_Position > maxCoord)
{
m_Position = maxCoord;
currentEdge.edgeIndex = m_Rope.GetEdgeAt(m_Position, out currentEdge.coordinate);
m_PositionDirty = true;
}
else if (m_Position < minCoord)
{
m_Position = minCoord;
currentEdge.edgeIndex = m_Rope.GetEdgeAt(m_Position, out currentEdge.coordinate);
m_PositionDirty = true;
}
}
}
public void CalculateMu()
{
currentEdge.edgeIndex = m_Rope.GetEdgeAt(m_Position, out currentEdge.coordinate);
ClampMuToRange();
m_PositionDirty = true;
}
public void CalculateRange()
{
if (m_LimitRange)
{
firstEdge.edgeIndex = m_Rope.GetEdgeAt(m_Range.x, out firstEdge.coordinate);
lastEdge.edgeIndex = m_Rope.GetEdgeAt(m_Range.y, out lastEdge.coordinate);
}
else
{
firstEdge.edgeIndex = m_Rope.GetEdgeAt(0, out firstEdge.coordinate);
lastEdge.edgeIndex = m_Rope.GetEdgeAt(1, out lastEdge.coordinate);
firstEdge.coordinate = -float.MaxValue;
lastEdge.coordinate = float.MaxValue;
}
ClampMuToRange();
m_RangeDirty = true;
}
public void Bind()
{
// Disable pinhole.
DisablePinhole();
if (m_Target != null && m_Rope.isLoaded)
{
Matrix4x4 bindMatrix = m_Target.worldToLocalMatrix * m_Rope.solver.transform.localToWorldMatrix;
var ropeBlueprint = m_Rope.sharedBlueprint as ObiRopeBlueprintBase;
if (ropeBlueprint != null && ropeBlueprint.deformableEdges != null)
{
currentEdge.edgeIndex = m_Rope.GetEdgeAt(m_Position, out currentEdge.coordinate);
if (currentEdge.edgeIndex >= 0)
{
CalculateRange();

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2aadf7c5471054b0db8c70bd317bc272
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 214df93f7c2ed4c1094bb6105c050575, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
using UnityEngine;
namespace Obi
{
public class ObiRopeAttach : MonoBehaviour
{
public ObiPathSmoother smoother;
[Range(0,1)]
public float m;
public void LateUpdate()
{
if (smoother != null && smoother.actor.isLoaded)
{
var trfm = smoother.actor.solver.transform;
ObiPathFrame section = smoother.GetSectionAt(m);
transform.position = trfm.TransformPoint(section.position);
transform.rotation = trfm.rotation * Quaternion.LookRotation(section.tangent, section.binormal);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 93cd44c04a2944349b4fe311545b7e05
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,108 @@
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(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>();
GetComponent<ObiActor>().OnInterpolate += UpdatePlugs;
}
void OnDisable()
{
GetComponent<ObiActor>().OnInterpolate -= 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, float simulatedTime, float substepTime)
{
if (!actor.isLoaded)
return;
// cache the rope's transform matrix/quaternion:
Matrix4x4 l2w = smoother.actor.solver.transform.localToWorldMatrix;
Quaternion l2wRot = l2w.rotation;
int instanceIndex = 0;
var system = actor.solver.GetRenderSystem<ObiPathSmoother>() as ObiPathSmootherRenderSystem;
int chunkCount = system.GetChunkCount(smoother.indexInSystem);
// place prefabs at the start/end of each curve:
for (int c = 0; c < chunkCount; ++c)
{
if ((plugTears && c > 0) ||
(plugStart && c == 0))
{
var instance = GetOrCreatePrefabInstance(instanceIndex++);
instance.SetActive(true);
ObiPathFrame frame = system.GetFrameAt(smoother.indexInSystem, c, 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 < chunkCount - 1) ||
(plugEnd && c == chunkCount - 1))
{
var instance = GetOrCreatePrefabInstance(instanceIndex++);
instance.SetActive(true);
int frameCount = system.GetSmoothFrameCount(smoother.indexInSystem, c);
ObiPathFrame frame = system.GetFrameAt(smoother.indexInSystem, c, frameCount-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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4514513daf5b14cb689daa23e9d8575e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,62 @@
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 = 4;
public float inSpeed = 2;
public float maxLength = 10;
private float restLength;
public void Awake()
{
cursor = GetComponent<ObiRopeCursor>();
rope = GetComponent<ObiRope>();
restLength = rope.restLength;
}
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();
// calculate difference between current length and rest length:
float diff = Mathf.Max(0, length - restLength);
float lengthChange = 0;
// if the rope has been stretched beyond the reel out threshold, increase its rest length:
if (diff > outThreshold)
lengthChange = outSpeed * Time.deltaTime;
// if the rope is not stretched past the reel in threshold, decrease its rest length:
if (diff < inThreshold)
lengthChange = -inSpeed * Time.deltaTime;
// make sure not to exceed maxLength:
lengthChange -= Mathf.Max(0, restLength + lengthChange - maxLength);
// set the new rest length:
restLength = cursor.ChangeLength(lengthChange);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4793f08b8350d44db99d1bc98daf625b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: