升级obi

This commit is contained in:
2026-01-22 22:08:21 +08:00
parent 120b8cda26
commit 20f14322bc
1067 changed files with 149894 additions and 29583 deletions

View File

@@ -7,7 +7,8 @@ namespace Obi
[AddComponentMenu("Physics/Obi/Obi Bone", 882)]
[ExecuteInEditMode]
[DisallowMultipleComponent]
public class ObiBone : ObiActor, IStretchShearConstraintsUser, IBendTwistConstraintsUser, ISkinConstraintsUser
[DefaultExecutionOrder(100)] // make sure ObiBone's LateUpdate is updated after ObiSolver's.
public class ObiBone : ObiActor, IStretchShearConstraintsUser, IBendTwistConstraintsUser, ISkinConstraintsUser, IAerodynamicConstraintsUser
{
[Serializable]
public class BonePropertyCurve
@@ -31,7 +32,7 @@ namespace Obi
[Serializable]
public class IgnoredBone
{
public Transform bone;
public Transform bone;
public bool ignoreChildren;
}
@@ -39,9 +40,9 @@ namespace Obi
[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);
[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;
@@ -62,6 +63,11 @@ namespace Obi
[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);
@@ -236,7 +242,34 @@ namespace Obi
public BonePropertyCurve plasticCreep
{
get { return _plasticCreep; }
set { _plasticCreep = value; SetConstraintsDirty(Oni.ConstraintType.BendTwist); }
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); }
}
@@ -262,6 +295,7 @@ namespace Obi
protected override void Awake()
{
// TODO: guard against having another ObiBone above it in hierarchy.
m_BoneBlueprint = ScriptableObject.CreateInstance<ObiBoneBlueprint>();
UpdateBlueprint();
base.Awake();
@@ -297,18 +331,45 @@ namespace Obi
}
}
public override void LoadBlueprint(ObiSolver solver)
internal override void LoadBlueprint()
{
base.LoadBlueprint(solver);
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();
}
public override void UnloadBlueprint(ObiSolver solver)
internal override void UnloadBlueprint()
{
ResetParticles();
CopyParticleDataToTransforms();
base.UnloadBlueprint(solver);
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()
@@ -316,10 +377,21 @@ namespace Obi
SetConstraintsDirty(Oni.ConstraintType.Skin);
SetConstraintsDirty(Oni.ConstraintType.StretchShear);
SetConstraintsDirty(Oni.ConstraintType.BendTwist);
SetConstraintsDirty(Oni.ConstraintType.Aerodynamics);
SetSelfCollisions(selfCollisions);
SetSimplicesDirty();
UpdateFilter();
UpdateCollisionMaterials();
}
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()
@@ -337,10 +409,10 @@ namespace Obi
solver.angularVelocities[rootIndex] = Vector4.zero;
// take particle rest position in actor space (which is always zero), converts to solver space:
solver.renderablePositions[rootIndex] = solver.positions[rootIndex] = actor2Solver.MultiplyPoint3x4(Vector3.zero);
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.renderableOrientations[rootIndex] = solver.orientations[rootIndex] = actor2SolverR * boneBlueprint.orientations[0];
solver.startOrientations[rootIndex] = solver.endOrientations[rootIndex] = solver.orientations[rootIndex] = actor2SolverR * boneBlueprint.orientations[0];
}
}
@@ -358,8 +430,8 @@ namespace Obi
{
for (int i = 0; i < particleCount; ++i)
{
var normalizedCoord = boneBlueprint.normalizedLengths[i];
var radii = Vector3.one * radius.Evaluate(normalizedCoord);
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)
@@ -371,9 +443,9 @@ namespace Obi
{
for (int i = 0; i < particleCount; ++i)
{
var normalizedCoord = boneBlueprint.normalizedLengths[i];
var invMass = ObiUtils.MassToInvMass(mass.Evaluate(normalizedCoord));
var invRotMass = ObiUtils.MassToInvMass(rotationalMass.Evaluate(normalizedCoord));
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;
@@ -388,20 +460,24 @@ namespace Obi
public Vector3 GetSkinRadiiBackstop(ObiSkinConstraintsBatch batch, int constraintIndex)
{
float normalizedCoord = boneBlueprint.normalizedLengths[batch.particleIndices[constraintIndex]];
return new Vector3(skinRadius.Evaluate(normalizedCoord),0,0);
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)
{
float normalizedCoord = boneBlueprint.normalizedLengths[batch.particleIndices[constraintIndex]];
return skinCompliance.Evaluate(normalizedCoord);
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)
{
float normalizedCoord = boneBlueprint.normalizedLengths[batch.particleIndices[constraintIndex * 2]];
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));
@@ -409,22 +485,51 @@ namespace Obi
public Vector2 GetBendTwistPlasticity(ObiBendTwistConstraintsBatch batch, int constraintIndex)
{
float normalizedCoord = boneBlueprint.normalizedLengths[batch.particleIndices[constraintIndex * 2]];
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)
{
float normalizedCoord = boneBlueprint.normalizedLengths[batch.particleIndices[constraintIndex * 2]];
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 override void BeginStep(float stepTime)
public float GetDrag(ObiAerodynamicConstraintsBatch batch, int constraintIndex)
{
base.BeginStep(stepTime);
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();
@@ -432,18 +537,10 @@ namespace Obi
UpdateRestShape();
}
public override void PrepareFrame()
{
ResetReferenceOrientations();
base.PrepareFrame();
}
public override void Interpolate()
public void LateUpdate()
{
if (Application.isPlaying && isActiveAndEnabled)
CopyParticleDataToTransforms();
base.Interpolate();
}
/// <summary>
@@ -463,11 +560,49 @@ namespace Obi
solver.velocities[solverIndex] = Vector4.zero;
solver.angularVelocities[solverIndex] = Vector4.zero;
solver.renderablePositions[solverIndex] = solver.positions[solverIndex] = world2Solver.MultiplyPoint3x4(trfm.position);
solver.startPositions[solverIndex] = solver.endPositions[solverIndex] = solver.positions[solverIndex] = world2Solver.MultiplyPoint3x4(trfm.position);
var boneDeltaAWS = trfm.rotation * Quaternion.Inverse(boneBlueprint.restOrientations[i]);
solver.renderableOrientations[solverIndex] = solver.orientations[solverIndex] = world2Solver.rotation * boneDeltaAWS * boneBlueprint.root2WorldR * boneBlueprint.orientations[i];
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()
@@ -484,14 +619,20 @@ namespace Obi
var sbc = solver.GetConstraintsByType(Oni.ConstraintType.BendTwist) as ObiConstraints<ObiBendTwistConstraintsBatch>;
if (bendTwistConstraintsEnabled && bc != null && sbc != null)
for (int j = 0; j < bc.GetBatchCount(); ++j)
{
// 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)solverBatch.constraintType][j];
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];
@@ -507,34 +648,45 @@ namespace Obi
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)
for (int j = 0; j < sc.GetBatchCount(); ++j)
{
// 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)solverBatch.constraintType][j];
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 (boneBlueprint != null)
if (isLoaded && boneBlueprint != null)
{
// copy current particle transforms to bones:
for (int i = 1; i < particleCount; ++i)

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

@@ -183,22 +183,35 @@ namespace Obi
SetupRuntimeConstraints();
}
public override void LoadBlueprint(ObiSolver solver)
internal override void LoadBlueprint()
{
base.LoadBlueprint(solver);
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.BendTwist);
SetConstraintsDirty(Oni.ConstraintType.Chain);
SetConstraintsDirty(Oni.ConstraintType.Aerodynamics);
SetSelfCollisions(selfCollisions);
SetMassScale(m_MassScale);
RecalculateRestLength();
SetSimplicesDirty();
UpdateCollisionMaterials();
}
public Vector3 GetBendTwistCompliance(ObiBendTwistConstraintsBatch batch, int constraintIndex)
@@ -219,7 +232,7 @@ namespace Obi
protected override void RebuildElementsFromConstraintsInternal()
{
var dc = GetConstraintsByType(Oni.ConstraintType.StretchShear) as ObiConstraints<ObiStretchShearConstraintsBatch>;
if (dc == null || dc.GetBatchCount() < 2)
if (dc == null || dc.batchCount < 2)
return;
int constraintCount = dc.batches[0].activeConstraintCount + dc.batches[1].activeConstraintCount;

View File

@@ -178,20 +178,20 @@ namespace Obi
SetupRuntimeConstraints();
}
public override void LoadBlueprint(ObiSolver solver)
internal override void LoadBlueprint()
{
// create a copy of the blueprint for this cloth:
if (Application.isPlaying)
m_RopeBlueprintInstance = this.blueprint as ObiRopeBlueprint;
base.LoadBlueprint(solver);
base.LoadBlueprint();
RebuildElementsFromConstraints();
SetupRuntimeConstraints();
}
public override void UnloadBlueprint(ObiSolver solver)
internal override void UnloadBlueprint()
{
base.UnloadBlueprint(solver);
base.UnloadBlueprint();
// delete the blueprint instance:
if (m_RopeBlueprintInstance != null)
@@ -202,26 +202,25 @@ namespace Obi
{
SetConstraintsDirty(Oni.ConstraintType.Distance);
SetConstraintsDirty(Oni.ConstraintType.Bending);
SetConstraintsDirty(Oni.ConstraintType.Aerodynamics);
SetSelfCollisions(selfCollisions);
SetMassScale(m_MassScale);
RecalculateRestLength();
SetSimplicesDirty();
UpdateCollisionMaterials();
}
public override void Substep(float substepTime)
// 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.Substep(substepTime);
base.SimulationStart(timeToSimulate, substepTime);
if (isActiveAndEnabled)
if (isActiveAndEnabled && tearingEnabled)
ApplyTearing(substepTime);
}
protected void ApplyTearing(float substepTime)
{
if (!tearingEnabled)
return;
float sqrTime = substepTime * substepTime;
tornElements.Clear();
@@ -230,24 +229,28 @@ namespace Obi
var sc = this.solver.GetConstraintsByType(Oni.ConstraintType.Distance) as ObiConstraints<ObiDistanceConstraintsBatch>;
if (dc != null && sc != null)
for (int j = 0; j < dc.GetBatchCount(); ++j)
{
var batch = dc.GetBatch(j) as ObiDistanceConstraintsBatch;
var solverBatch = sc.batches[j] as ObiDistanceConstraintsBatch;
for (int i = 0; i < batch.activeConstraintCount; i++)
// 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)
{
int elementIndex = j + 2 * i;
var batch = dc.GetBatch(j) as ObiDistanceConstraintsBatch;
var solverBatch = sc.batches[j] as ObiDistanceConstraintsBatch;
// 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)
for (int i = 0; i < batch.activeConstraintCount; i++)
{
tornElements.Add(elements[elementIndex]);
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]);
}
}
}
}
@@ -282,7 +285,8 @@ namespace Obi
m_Solver.invMasses[splitIndex] *= 2;
CopyParticle(solver.particleToActor[splitIndex].indexInActor, activeParticleCount);
ActivateParticle(activeParticleCount);
ActivateParticle();
SetRenderingDirty(Oni.RenderingSystemType.AllRopes);
return solverIndices[activeParticleCount - 1];
}
@@ -309,8 +313,7 @@ namespace Obi
element.particle1 = SplitParticle(element.particle1);
if (OnRopeTorn != null)
OnRopeTorn(this, new ObiRopeTornEventArgs(element, element.particle1));
OnRopeTorn?.Invoke(this, new ObiRopeTornEventArgs(element, element.particle1));
return true;
}
@@ -318,7 +321,7 @@ namespace Obi
protected override void RebuildElementsFromConstraintsInternal()
{
var dc = GetConstraintsByType(Oni.ConstraintType.Distance) as ObiConstraints<ObiDistanceConstraintsBatch>;
if (dc == null || dc.GetBatchCount() < 2)
if (dc == null || dc.batchCount < 2)
return;
int constraintCount = dc.batches[0].activeConstraintCount + dc.batches[1].activeConstraintCount;
@@ -356,13 +359,26 @@ namespace Obi
// 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;
@@ -372,6 +388,7 @@ namespace Obi
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;
@@ -384,7 +401,7 @@ namespace Obi
int indexA = elements[i].particle1;
int indexB = elements[i + 1].particle2;
int indexC = elements[i].particle2;
float restBend = ObiUtils.RestBendingConstraint(solver.restPositions[indexA], solver.restPositions[indexB], solver.restPositions[indexC]);
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;
@@ -430,16 +447,21 @@ namespace Obi
loopClosingBatch.ActivateConstraint(0);
}
// edge simplices:
sharedBlueprint.edges = new int[elements.Count*2];
// 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)
{
sharedBlueprint.edges[i * 2] = solver.particleToActor[elements[i].particle1].indexInActor;
sharedBlueprint.edges[i * 2 + 1] = solver.particleToActor[elements[i].particle2].indexInActor;
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

@@ -4,7 +4,7 @@ using System.Collections.Generic;
namespace Obi
{
public abstract class ObiRopeBase : ObiActor
public abstract class ObiRopeBase : ObiActor, IAerodynamicConstraintsUser
{
[SerializeField] protected bool m_SelfCollisions = false;
@@ -12,11 +12,44 @@ namespace Obi
[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 {
@@ -25,6 +58,37 @@ namespace Obi
}
}
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>
@@ -105,5 +169,25 @@ namespace Obi
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

@@ -20,6 +20,7 @@ namespace Obi
ObiStructuralElement m_CursorElement = null;
private int m_SourceIndex = -1;
private float lengthChange = 0;
public float cursorMu
{
@@ -66,6 +67,7 @@ namespace Obi
rope = GetComponent<ObiRope>();
rope.OnElementsGenerated += Actor_OnElementsGenerated;
rope.OnSimulationStart += Rope_OnSimulate;
if (rope.elements != null && rope.elements.Count > 0)
Actor_OnElementsGenerated(rope);
}
@@ -73,6 +75,7 @@ namespace Obi
private void OnDisable()
{
rope.OnElementsGenerated -= Actor_OnElementsGenerated;
rope.OnSimulationStart -= Rope_OnSimulate;
}
private void Actor_OnElementsGenerated(ObiActor actor)
@@ -81,64 +84,13 @@ namespace Obi
UpdateSource();
}
public void UpdateCursor()
private void Rope_OnSimulate(ObiActor actor, float simulatedTime, float substepTime)
{
rope = GetComponent<ObiRope>();
m_CursorElement = null;
if (rope.isLoaded)
{
float elmMu;
m_CursorElement = rope.GetElementAt(cursorMu, out elmMu);
}
}
public void UpdateSource()
{
rope = GetComponent<ObiRope>();
m_SourceIndex = -1;
if (rope.isLoaded)
{
float elmMu;
var elm = rope.GetElementAt(sourceMu, out elmMu);
if (elm != null && rope.solver != null)
{
m_SourceIndex = elmMu < 0.5f ? elm.particle1 : elm.particle2;
}
}
}
private int AddParticleAt(int index)
{
// Copy data from the particle where we will insert new particles, to the particles we will insert:
int targetIndex = rope.activeParticleCount;
rope.CopyParticle(rope.solver.particleToActor[m_SourceIndex].indexInActor, targetIndex);
// Move the new particle to the one at the place where we will insert it:
rope.TeleportParticle(targetIndex, rope.solver.positions[rope.solverIndices[index]]);
// Activate the particle:
rope.ActivateParticle(targetIndex);
return rope.solverIndices[targetIndex];
}
private void RemoveParticleAt(int index)
{
rope.DeactivateParticle(index);
}
public void ChangeLength(float newLength)
{
if (!rope.isLoaded)
if (!rope.isLoaded || Mathf.Abs(lengthChange) < ObiUtils.epsilon)
return;
var solver = rope.solver;
// clamp new length to sane limits:
newLength = Mathf.Clamp(newLength, 0, (rope.sourceBlueprint.particleCount - 1) * rope.ropeBlueprint.interParticleDistance);
// calculate the change in rope length:
float lengthChange = newLength - rope.restLength;
// remove:
if (lengthChange < 0)
{
@@ -148,6 +100,10 @@ namespace Obi
{
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)
@@ -165,8 +121,8 @@ namespace Obi
m_CursorElement = rope.elements[index];
}
else
m_CursorElement = rope.elements[Mathf.Max(0,index - 1)];
else
m_CursorElement = rope.elements[Mathf.Max(0, index - 1)];
}
else // negative direction:
{
@@ -264,6 +220,69 @@ namespace Obi
// 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

@@ -20,6 +20,8 @@ namespace Obi
[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;
@@ -38,6 +40,24 @@ namespace Obi
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();
@@ -164,12 +184,21 @@ namespace Obi
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();
@@ -185,9 +214,23 @@ namespace Obi
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];
@@ -198,9 +241,29 @@ namespace Obi
}
}
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)
@@ -228,7 +291,7 @@ namespace Obi
int cIndex = constraintIndices[i];
// Add a new batch if needed:
if (color >= stretchShearConstraintsData.GetBatchCount())
if (color >= stretchShearConstraintsData.batchCount)
stretchShearConstraintsData.AddBatch(new ObiStretchShearConstraintsBatch());
int index1 = particleIndices[cIndex];
@@ -274,7 +337,7 @@ namespace Obi
int cIndex = constraintIndices[i];
// Add a new batch if needed:
if (color >= bendTwistConstraintsData.GetBatchCount())
if (color >= bendTwistConstraintsData.batchCount)
bendTwistConstraintsData.AddBatch(new ObiBendTwistConstraintsBatch());
int index1 = particleIndices[cIndex];

View File

@@ -127,6 +127,9 @@ namespace Obi
yield return new CoroutineJob.ProgressInfo("ObiRod: generating particles...", i / (float)m_ActiveParticleCount);
}
// Deformable edges:
CreateDeformableEdges(numSegments);
// Create edge simplices:
CreateSimplices(numSegments);
@@ -142,6 +145,12 @@ namespace Obi
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();
@@ -150,7 +159,6 @@ namespace Obi
}
protected virtual IEnumerator CreateStretchShearConstraints(List<Vector3> particleNormals)
{
stretchShearConstraintsData = new ObiStretchShearConstraintsData();
@@ -159,8 +167,7 @@ namespace Obi
stretchShearConstraintsData.AddBatch(new ObiStretchShearConstraintsBatch());
// rotation minimizing frame:
ObiPathFrame frame = new ObiPathFrame();
frame.Reset();
ObiPathFrame frame = ObiPathFrame.Identity;
for (int i = 0; i < totalParticles - 1; i++)
{

View File

@@ -10,7 +10,6 @@ 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;
@@ -113,6 +112,9 @@ namespace Obi
yield return new CoroutineJob.ProgressInfo("ObiRope: generating particles...", i / (float)m_ActiveParticleCount);
}
// Deformable edges:
CreateDeformableEdges(numSegments);
// Create edge simplices:
CreateSimplices(numSegments);
@@ -128,6 +130,12 @@ namespace Obi
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)
@@ -193,7 +201,7 @@ namespace Obi
var batch = bendConstraintsData.batches[i % 3] as ObiBendConstraintsBatch;
Vector3Int indices = new Vector3Int(i, i + 2, i + 1);
float restBend = ObiUtils.RestBendingConstraint(restPositions[indices[0]], restPositions[indices[1]], restPositions[indices[2]]);
float restBend = 0;//ObiUtils.RestBendingConstraint(restPositions[indices[0]], restPositions[indices[1]], restPositions[indices[2]]);
batch.AddConstraint(indices, restBend);
if (i < m_ActiveParticleCount - 2)

View File

@@ -19,6 +19,7 @@ namespace Obi
[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
@@ -62,13 +63,44 @@ namespace Obi
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 % totalParticles;
edges[i * 2 + 1] = (i + 1) % totalParticles;
edges[i * 2] = i % activeParticleCount;
edges[i * 2 + 1] = (i + 1) % activeParticleCount;
}
}

View File

@@ -1,6 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Generic;
using UnityEngine;
namespace Obi

View File

@@ -63,7 +63,6 @@ namespace Obi
{
throw new InvalidOperationException("Cannot get position in path because it has zero control points.");
}
}
/**

View File

@@ -12,7 +12,9 @@ namespace Obi
Z = 2
}
public Vector3 position;
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;
@@ -28,7 +30,7 @@ namespace Obi
this.binormal = binormal;
this.color = color;
this.thickness = thickness;
}
}
public void Reset()
{
@@ -50,7 +52,7 @@ namespace Obi
return new ObiPathFrame(c.position * f, c.tangent * f, c.normal * f, c.binormal * f,c.color * f, c.thickness * f);
}
public static void WeightedSum(float w1, float w2, float w3, ref ObiPathFrame c1, ref ObiPathFrame c2, ref ObiPathFrame c3, ref ObiPathFrame sum)
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;

View File

@@ -1,21 +1,12 @@
using UnityEngine;
using Unity.Profiling;
using System;
using System.Collections;
using System.Collections.Generic;
namespace Obi
{
[ExecuteInEditMode]
[RequireComponent(typeof(ObiRopeBase))]
public class ObiPathSmoother : MonoBehaviour
public class ObiPathSmoother : MonoBehaviour, ObiActorRenderer<ObiPathSmoother>
{
static ProfilerMarker m_AllocateRawChunksPerfMarker = new ProfilerMarker("AllocateRawChunks");
static ProfilerMarker m_GenerateSmoothChunksPerfMarker = new ProfilerMarker("GenerateSmoothChunks");
private Matrix4x4 w2l;
private Quaternion w2lRotation;
[Range(0, 1)]
[Tooltip("Curvature threshold below which the path will be decimated. A value of 0 won't apply any decimation. As you increase the value, decimation will become more aggresive.")]
public float decimation = 0;
@@ -27,353 +18,84 @@ namespace Obi
[Tooltip("Twist in degrees applied to each sucessive path section.")]
public float twist = 0;
public event ObiActor.ActorCallback OnCurveGenerated;
public ObiActor actor { get; private set; }
protected float smoothLength = 0;
protected int smoothSections = 0;
[HideInInspector] public ObiList<ObiList<ObiPathFrame>> rawChunks = new ObiList<ObiList<ObiPathFrame>>();
[HideInInspector] public ObiList<ObiList<ObiPathFrame>> smoothChunks = new ObiList<ObiList<ObiPathFrame>>();
private Stack<Vector2Int> stack = new Stack<Vector2Int>();
private BitArray decimateBitArray = new BitArray(0);
[HideInInspector] public int indexInSystem = 0;
public float SmoothLength
{
get { return 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 { return smoothSections; }
get {
if (actor.isLoaded)
{
var system = actor.solver.GetRenderSystem<ObiPathSmoother>() as ObiPathSmootherRenderSystem;
if (system != null)
return system.GetSmoothFrameCount(indexInSystem);
}
return 0;
}
}
private void OnEnable()
public void OnEnable()
{
GetComponent<ObiRopeBase>().OnInterpolate += Actor_OnInterpolate;
actor = GetComponent<ObiActor>();
((ObiActorRenderer<ObiPathSmoother>)this).EnableRenderer();
}
private void OnDisable()
{
GetComponent<ObiRopeBase>().OnInterpolate -= Actor_OnInterpolate;
((ObiActorRenderer<ObiPathSmoother>)this).DisableRenderer();
}
void Actor_OnInterpolate(ObiActor actor)
private void OnValidate()
{
GenerateSmoothChunks(((ObiRopeBase)actor), smoothing);
if (OnCurveGenerated != null)
OnCurveGenerated(actor);
}
private void AllocateChunk(int sections)
{
if (sections > 1)
{
if (rawChunks.Data[rawChunks.Count] == null)
{
rawChunks.Data[rawChunks.Count] = new ObiList<ObiPathFrame>();
smoothChunks.Data[smoothChunks.Count] = new ObiList<ObiPathFrame>();
}
rawChunks.Data[rawChunks.Count].SetCount(sections);
rawChunks.SetCount(rawChunks.Count + 1);
smoothChunks.SetCount(smoothChunks.Count + 1);
}
}
private float CalculateChunkLength(ObiList<ObiPathFrame> chunk)
{
float length = 0;
for (int i = 1; i < chunk.Count; ++i)
length += Vector3.Distance(chunk[i].position, chunk[i - 1].position);
return length;
}
/**
* Generates raw curve chunks from the rope description.
*/
private void AllocateRawChunks(ObiRopeBase actor)
{
using (m_AllocateRawChunksPerfMarker.Auto())
{
rawChunks.Clear();
if (actor.path == null)
return;
// Count particles for each chunk.
int particles = 0;
for (int i = 0; i < actor.elements.Count; ++i)
{
particles++;
// At discontinuities, start a new chunk.
if (i < actor.elements.Count - 1 && actor.elements[i].particle2 != actor.elements[i + 1].particle1)
{
AllocateChunk(++particles);
particles = 0;
}
}
AllocateChunk(++particles);
}
}
private void PathFrameFromParticle(ObiRopeBase actor, ref ObiPathFrame frame, int particleIndex, bool interpolateOrientation = true)
{
// Update current frame values from particles:
frame.position = w2l.MultiplyPoint3x4(actor.GetParticlePosition(particleIndex));
frame.thickness = actor.GetParticleMaxRadius(particleIndex);
frame.color = actor.GetParticleColor(particleIndex);
// Use particle orientation if possible:
if (actor.usesOrientedParticles)
{
Quaternion current = actor.GetParticleOrientation(particleIndex);
Quaternion previous = actor.GetParticleOrientation(Mathf.Max(0, particleIndex - 1));
Quaternion average = w2lRotation * (interpolateOrientation ? Quaternion.SlerpUnclamped(current, previous, 0.5f) : current);
frame.normal = average * Vector3.up;
frame.binormal = average * Vector3.right;
frame.tangent = average * Vector3.forward;
}
}
/**
* Generates smooth curve chunks.
*/
public void GenerateSmoothChunks(ObiRopeBase actor, uint smoothingLevels)
{
using (m_GenerateSmoothChunksPerfMarker.Auto())
{
smoothChunks.Clear();
smoothSections = 0;
smoothLength = 0;
if (!Application.isPlaying)
actor.RebuildElementsFromConstraints();
AllocateRawChunks(actor);
w2l = actor.transform.worldToLocalMatrix;
w2lRotation = w2l.rotation;
// keep track of the first element of each chunk
int chunkStart = 0;
ObiPathFrame frame_0 = new ObiPathFrame(); // "next" frame
ObiPathFrame frame_1 = new ObiPathFrame(); // current frame
ObiPathFrame frame_2 = new ObiPathFrame(); // previous frame
// generate curve for each rope chunk:
for (int i = 0; i < rawChunks.Count; ++i)
{
int elementCount = rawChunks[i].Count - 1;
// Initialize frames:
frame_0.Reset();
frame_1.Reset();
frame_2.Reset();
PathFrameFromParticle(actor, ref frame_1, actor.elements[chunkStart].particle1, false);
frame_2 = frame_1;
for (int m = 1; m <= rawChunks[i].Count; ++m)
{
int index;
if (m >= elementCount)
// second particle of last element in the chunk.
index = actor.elements[chunkStart + elementCount - 1].particle2;
else
//first particle of current element.
index = actor.elements[chunkStart + m].particle1;
// generate curve frame from particle:
PathFrameFromParticle(actor, ref frame_0, index);
if (actor.usesOrientedParticles)
{
// copy frame directly.
frame_2 = frame_1;
}
else
{
// perform parallel transport, using forward / backward average to calculate tangent.
frame_1.tangent = ((frame_1.position - frame_2.position) + (frame_0.position - frame_1.position)).normalized;
frame_2.Transport(frame_1, twist);
}
// in case we wrapped around the rope, average first and last frames:
if (chunkStart + m > actor.activeParticleCount)
{
frame_2 = rawChunks[0][0] = 0.5f * frame_2 + 0.5f * rawChunks[0][0];
}
frame_1 = frame_0;
rawChunks[i][m - 1] = frame_2;
}
// increment chunkStart by the amount of elements in this chunk:
chunkStart += elementCount;
// adaptive curvature-based decimation:
if (Decimate(rawChunks[i], smoothChunks[i], decimation))
{
// if any decimation took place, swap raw and smooth chunks:
var aux = rawChunks[i];
rawChunks[i] = smoothChunks[i];
smoothChunks[i] = aux;
}
// get smooth curve points:
Chaikin(rawChunks[i], smoothChunks[i], smoothingLevels);
// count total curve sections and total curve length:
smoothSections += smoothChunks[i].Count;
smoothLength += CalculateChunkLength(smoothChunks[i]);
}
}
((ObiActorRenderer<ObiPathSmoother>)this).SetRendererDirty(Oni.RenderingSystemType.AllSmoothedRopes);
}
public ObiPathFrame GetSectionAt(float mu)
{
float edgeMu = smoothSections * Mathf.Clamp(mu,0,0.9999f);
int index = (int)edgeMu;
float sectionMu = edgeMu - index;
int counter = 0;
int chunkIndex = -1;
int indexInChunk = -1;
for (int i = 0; i < smoothChunks.Count; ++i)
if (actor.isLoaded)
{
if (counter + smoothChunks[i].Count > index)
{
chunkIndex = i;
indexInChunk = index - counter;
break;
}
counter += smoothChunks[i].Count;
var system = actor.solver.GetRenderSystem<ObiPathSmoother>() as ObiPathSmootherRenderSystem;
if (system != null)
return system.GetFrameAt(indexInSystem, mu);
}
ObiList<ObiPathFrame> chunk = smoothChunks[chunkIndex];
ObiPathFrame s1 = chunk[indexInChunk];
ObiPathFrame s2 = chunk[Mathf.Min(indexInChunk + 1, chunk.Count - 1)];
return (1 - sectionMu) * s1 + sectionMu * s2;
return ObiPathFrame.Identity;
}
/**
* Iterative version of the Ramer-Douglas-Peucker path decimation algorithm.
*/
private bool Decimate(ObiList<ObiPathFrame> input, ObiList<ObiPathFrame> output, float threshold)
RenderSystem<ObiPathSmoother> ObiRenderer<ObiPathSmoother>.CreateRenderSystem(ObiSolver solver)
{
// no decimation, no work to do, just return:
if (threshold < 0.00001f || input.Count < 3)
return false;
float scaledThreshold = threshold * threshold * 0.01f;
stack.Push(new Vector2Int(0, input.Count - 1));
decimateBitArray.Length = Mathf.Max(decimateBitArray.Length, input.Count);
decimateBitArray.SetAll(true);
while (stack.Count > 0)
switch (solver.backendType)
{
var range = stack.Pop();
float dmax = 0;
int index = range.x;
float mu;
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
case ObiSolver.BackendType.Burst: return new BurstPathSmootherRenderSystem(solver);
#endif
case ObiSolver.BackendType.Compute:
default:
for (int i = index + 1; i < range.y; ++i)
{
if (decimateBitArray[i])
{
float d = Vector3.SqrMagnitude(ObiUtils.ProjectPointLine(input[i].position, input[range.x].position, input[range.y].position, out mu) - input[i].position);
if (SystemInfo.supportsComputeShaders)
return new ComputePathSmootherRenderSystem(solver);
return null;
if (d > dmax)
{
index = i;
dmax = d;
}
}
}
if (dmax > scaledThreshold)
{
stack.Push(new Vector2Int(range.x, index));
stack.Push(new Vector2Int(index, range.y));
}
else
{
for (int i = range.x + 1; i < range.y; ++i)
decimateBitArray[i] = false;
}
}
output.Clear();
for (int i = 0; i < input.Count; ++i)
if (decimateBitArray[i])
output.Add(input[i]);
return true;
}
/**
* This method uses a variant of Chainkin's algorithm to produce a smooth curve from a set of control points. It is specially fast
* because it directly calculates subdivision level k, instead of recursively calculating levels 1..k.
*/
private void Chaikin(ObiList<ObiPathFrame> input, ObiList<ObiPathFrame> output, uint k)
{
// no subdivision levels, no work to do. just copy the input to the output:
if (k == 0 || input.Count < 3)
{
output.SetCount(input.Count);
for (int i = 0; i < input.Count; ++i)
output[i] = input[i];
return;
}
// calculate amount of new points generated by each inner control point:
int pCount = (int)Mathf.Pow(2, k);
// precalculate some quantities:
int n0 = input.Count - 1;
float twoRaisedToMinusKPlus1 = Mathf.Pow(2, -(k + 1));
float twoRaisedToMinusK = Mathf.Pow(2, -k);
float twoRaisedToMinus2K = Mathf.Pow(2, -2 * k);
float twoRaisedToMinus2KMinus1 = Mathf.Pow(2, -2 * k - 1);
// allocate ouput:
output.SetCount((n0 - 1) * pCount + 2);
// calculate initial curve points:
output[0] = (0.5f + twoRaisedToMinusKPlus1) * input[0] + (0.5f - twoRaisedToMinusKPlus1) * input[1];
output[pCount * n0 - pCount + 1] = (0.5f - twoRaisedToMinusKPlus1) * input[n0 - 1] + (0.5f + twoRaisedToMinusKPlus1) * input[n0];
// calculate internal points:
for (int j = 1; j <= pCount; ++j)
{
// precalculate coefficients:
float F = 0.5f - twoRaisedToMinusKPlus1 - (j - 1) * (twoRaisedToMinusK - j * twoRaisedToMinus2KMinus1);
float G = 0.5f + twoRaisedToMinusKPlus1 + (j - 1) * (twoRaisedToMinusK - j * twoRaisedToMinus2K);
float H = (j - 1) * j * twoRaisedToMinus2KMinus1;
for (int i = 1; i < n0; ++i)
ObiPathFrame.WeightedSum(F, G, H,
ref input.Data[i - 1],
ref input.Data[i],
ref input.Data[i + 1],
ref output.Data[(i - 1) * pCount + j]);
}
// make first and last curve points coincide with original points:
output[0] = input[0];
output[output.Count - 1] = input[input.Count - 1];
}
}
}

View File

@@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
icon: {fileID: 2800000, guid: 8791eecf125744cbeadea65319c29d5a, type: 3}
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

@@ -7,163 +7,71 @@ namespace Obi
{
[AddComponentMenu("Physics/Obi/Obi Rope Chain Renderer", 885)]
[ExecuteInEditMode]
public class ObiRopeChainRenderer : MonoBehaviour
public class ObiRopeChainRenderer : MonoBehaviour, ObiActorRenderer<ObiRopeChainRenderer>
{
static ProfilerMarker m_UpdateChainRopeRendererChunksPerfMarker = new ProfilerMarker("UpdateChainRopeRenderer");
[Serializable]
public struct LinkModifier
{
public Vector3 translation;
public Vector3 scale;
public Vector3 rotation;
[HideInInspector] [SerializeField] public List<GameObject> linkInstances = new List<GameObject>();
[SerializeProperty("RandomizeLinks")]
[SerializeField] private bool randomizeLinks = false;
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.*/
public List<GameObject> linkPrefabs = new List<GameObject>();
[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 float sectionTwist = 0; /**< Amount of twist applied to each section, in degrees.*/
public List<LinkModifier> linkModifiers = new List<LinkModifier>();
ObiPathFrame frame = new ObiPathFrame();
public RenderBatchParams renderParameters = new RenderBatchParams(true);
public ObiActor actor { get; private set; }
void Awake()
{
ClearChainLinkInstances();
actor = GetComponent<ObiActor>();
}
public bool RandomizeLinks
public void OnEnable()
{
get { return randomizeLinks; }
set
{
if (value != randomizeLinks)
{
randomizeLinks = value;
CreateChainLinkInstances(GetComponent<ObiRopeBase>());
}
}
((ObiActorRenderer<ObiRopeChainRenderer>)this).EnableRenderer();
}
void OnEnable()
public void OnDisable()
{
GetComponent<ObiRopeBase>().OnInterpolate += UpdateRenderer;
((ObiActorRenderer<ObiRopeChainRenderer>)this).DisableRenderer();
}
void OnDisable()
public void OnValidate()
{
GetComponent<ObiRopeBase>().OnInterpolate -= UpdateRenderer;
ClearChainLinkInstances();
((ObiActorRenderer<ObiRopeChainRenderer>)this).SetRendererDirty(Oni.RenderingSystemType.ChainRope);
}
/**
* Destroys all chain link instances. Used when the chain must be re-created from scratch, and when the actor is disabled/destroyed.
*/
public void ClearChainLinkInstances()
RenderSystem<ObiRopeChainRenderer> ObiRenderer<ObiRopeChainRenderer>.CreateRenderSystem(ObiSolver solver)
{
if (linkInstances == null)
return;
for (int i = 0; i < linkInstances.Count; ++i)
{
if (linkInstances[i] != null)
GameObject.DestroyImmediate(linkInstances[i]);
}
linkInstances.Clear();
}
public void CreateChainLinkInstances(ObiRopeBase rope)
{
ClearChainLinkInstances();
if (linkPrefabs.Count > 0)
switch (solver.backendType)
{
for (int i = 0; i < rope.particleCount; ++i)
{
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
case ObiSolver.BackendType.Burst: return new BurstChainRopeRenderSystem(solver);
#endif
case ObiSolver.BackendType.Compute:
default:
int index = randomizeLinks ? UnityEngine.Random.Range(0, linkPrefabs.Count) : i % linkPrefabs.Count;
GameObject linkInstance = null;
if (linkPrefabs[index] != null)
{
linkInstance = GameObject.Instantiate(linkPrefabs[index]);
linkInstance.transform.SetParent(rope.transform, false);
linkInstance.hideFlags = HideFlags.HideAndDontSave;
linkInstance.SetActive(false);
}
linkInstances.Add(linkInstance);
}
}
}
public void UpdateRenderer(ObiActor actor)
{
using (m_UpdateChainRopeRendererChunksPerfMarker.Auto())
{
var rope = actor as ObiRopeBase;
// In case there are no link prefabs to instantiate:
if (linkPrefabs.Count == 0)
return;
// Regenerate instances if needed:
if (linkInstances == null || linkInstances.Count < rope.particleCount)
{
CreateChainLinkInstances(rope);
}
var blueprint = rope.sourceBlueprint;
int elementCount = rope.elements.Count;
float twist = -sectionTwist * elementCount * twistAnchor;
//we will define and transport a reference frame along the curve using parallel transport method:
frame.Reset();
frame.SetTwist(twist);
int lastParticle = -1;
for (int i = 0; i < elementCount; ++i)
{
ObiStructuralElement elm = rope.elements[i];
Vector3 pos = rope.GetParticlePosition(elm.particle1);
Vector3 nextPos = rope.GetParticlePosition(elm.particle2);
Vector3 linkVector = nextPos - pos;
Vector3 tangent = linkVector.normalized;
if (rope.sourceBlueprint.usesOrientedParticles)
{
frame.Transport(nextPos, tangent, rope.GetParticleOrientation(elm.particle1) * Vector3.up, twist);
twist += sectionTwist;
}
else
{
frame.Transport(nextPos, tangent, sectionTwist);
}
if (linkInstances[i] != null)
{
linkInstances[i].SetActive(true);
Transform linkTransform = linkInstances[i].transform;
linkTransform.position = pos + linkVector * 0.5f;
linkTransform.localScale = rope.GetParticleMaxRadius(elm.particle1) * 2 * linkScale;
linkTransform.rotation = Quaternion.LookRotation(tangent, frame.normal);
}
lastParticle = elm.particle2;
}
for (int i = elementCount; i < linkInstances.Count; ++i)
{
if (linkInstances[i] != null)
linkInstances[i].SetActive(false);
}
if (SystemInfo.supportsComputeShaders)
return new ComputeChainRopeRenderSystem(solver);
return null;
}
}
}

View File

@@ -7,23 +7,14 @@ namespace Obi
{
[AddComponentMenu("Physics/Obi/Obi Rope Extruded Renderer", 883)]
[ExecuteInEditMode]
[RequireComponent(typeof(MeshRenderer))]
[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(ObiPathSmoother))]
public class ObiRopeExtrudedRenderer : MonoBehaviour
public class ObiRopeExtrudedRenderer : MonoBehaviour, ObiActorRenderer<ObiRopeExtrudedRenderer>
{
static ProfilerMarker m_UpdateExtrudedRopeRendererChunksPerfMarker = new ProfilerMarker("UpdateExtrudedRopeRenderer");
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.
private List<Vector3> vertices = new List<Vector3>();
private List<Vector3> normals = new List<Vector3>();
private List<Vector4> tangents = new List<Vector4>();
private List<Vector2> uvs = new List<Vector2>();
private List<Color> vertColors = new List<Color>();
private List<int> tris = new List<int>();
public Material material;
ObiPathSmoother smoother; // Each renderer should have its own smoother. The renderer then has a method to get position and orientation at a point.
[HideInInspector] [NonSerialized] public Mesh extrudedMesh;
public RenderBatchParams renderParameters = new RenderBatchParams(true);
[Range(0, 1)]
public float uvAnchor = 0; /**< Normalized position of texture coordinate origin along rope.*/
@@ -36,140 +27,47 @@ namespace Obi
public float thicknessScale = 0.8f; /**< Scales section thickness.*/
void OnEnable()
public ObiActor actor { get; private set; }
public void Awake()
{
actor = GetComponent<ObiActor>();
}
public void OnEnable()
{
smoother = GetComponent<ObiPathSmoother>();
smoother.OnCurveGenerated += UpdateRenderer;
CreateMeshIfNeeded();
((ObiActorRenderer<ObiRopeExtrudedRenderer>)this).EnableRenderer();
}
void OnDisable()
public void OnDisable()
{
smoother.OnCurveGenerated -= UpdateRenderer;
GameObject.DestroyImmediate(extrudedMesh);
((ObiActorRenderer<ObiRopeExtrudedRenderer>)this).DisableRenderer();
}
private void CreateMeshIfNeeded()
public void OnValidate()
{
if (extrudedMesh == null)
((ObiActorRenderer<ObiRopeExtrudedRenderer>)this).SetRendererDirty(Oni.RenderingSystemType.AllSmoothedRopes);
}
RenderSystem<ObiRopeExtrudedRenderer> ObiRenderer<ObiRopeExtrudedRenderer>.CreateRenderSystem(ObiSolver solver)
{
switch (solver.backendType)
{
extrudedMesh = new Mesh();
extrudedMesh.name = "extrudedMesh";
extrudedMesh.MarkDynamic();
GetComponent<MeshFilter>().mesh = extrudedMesh;
#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;
}
}
public void UpdateRenderer(ObiActor actor)
{
using (m_UpdateExtrudedRopeRendererChunksPerfMarker.Auto())
{
if (section == null)
return;
var rope = actor as ObiRopeBase;
CreateMeshIfNeeded();
ClearMeshData();
int sectionIndex = 0;
int sectionSegments = section.Segments;
int verticesPerSection = sectionSegments + 1; // the last vertex in each section must be duplicated, due to uv wraparound.
float vCoord = -uvScale.y * rope.restLength * uvAnchor; // v texture coordinate.
float actualToRestLengthRatio = smoother.SmoothLength / rope.restLength;
Vector3 vertex = Vector3.zero, normal = Vector3.zero;
Vector4 texTangent = Vector4.zero;
Vector2 uv = Vector2.zero;
for (int c = 0; c < smoother.smoothChunks.Count; ++c)
{
ObiList<ObiPathFrame> curve = smoother.smoothChunks[c];
for (int i = 0; i < curve.Count; ++i)
{
// Calculate previous and next curve indices:
int prevIndex = Mathf.Max(i - 1, 0);
// advance v texcoord:
vCoord += uvScale.y * (Vector3.Distance(curve.Data[i].position, curve.Data[prevIndex].position) /
(normalizeV ? smoother.SmoothLength : actualToRestLengthRatio));
// calculate section thickness and scale the basis vectors by it:
float sectionThickness = curve.Data[i].thickness * thicknessScale;
// Loop around each segment:
int nextSectionIndex = sectionIndex + 1;
for (int j = 0; j <= sectionSegments; ++j)
{
// make just one copy of the section vertex:
Vector2 sectionVertex = section.vertices[j];
// calculate normal using section vertex, curve normal and binormal:
normal.x = (sectionVertex.x * curve.Data[i].normal.x + sectionVertex.y * curve.Data[i].binormal.x) * sectionThickness;
normal.y = (sectionVertex.x * curve.Data[i].normal.y + sectionVertex.y * curve.Data[i].binormal.y) * sectionThickness;
normal.z = (sectionVertex.x * curve.Data[i].normal.z + sectionVertex.y * curve.Data[i].binormal.z) * sectionThickness;
// offset curve position by normal:
vertex.x = curve.Data[i].position.x + normal.x;
vertex.y = curve.Data[i].position.y + normal.y;
vertex.z = curve.Data[i].position.z + normal.z;
// cross(normal, curve tangent)
texTangent.x = normal.y * curve.Data[i].tangent.z - normal.z * curve.Data[i].tangent.y;
texTangent.y = normal.z * curve.Data[i].tangent.x - normal.x * curve.Data[i].tangent.z;
texTangent.z = normal.x * curve.Data[i].tangent.y - normal.y * curve.Data[i].tangent.x;
texTangent.w = -1;
uv.x = (j / (float)sectionSegments) * uvScale.x;
uv.y = vCoord;
vertices.Add(vertex);
normals.Add(normal);
tangents.Add(texTangent);
vertColors.Add(curve.Data[i].color);
uvs.Add(uv);
if (j < sectionSegments && i < curve.Count - 1)
{
tris.Add(sectionIndex * verticesPerSection + j);
tris.Add(nextSectionIndex * verticesPerSection + j);
tris.Add(sectionIndex * verticesPerSection + (j + 1));
tris.Add(sectionIndex * verticesPerSection + (j + 1));
tris.Add(nextSectionIndex * verticesPerSection + j);
tris.Add(nextSectionIndex * verticesPerSection + (j + 1));
}
}
sectionIndex++;
}
}
CommitMeshData();
}
}
private void ClearMeshData()
{
extrudedMesh.Clear();
vertices.Clear();
normals.Clear();
tangents.Clear();
uvs.Clear();
vertColors.Clear();
tris.Clear();
}
private void CommitMeshData()
{
extrudedMesh.SetVertices(vertices);
extrudedMesh.SetNormals(normals);
extrudedMesh.SetTangents(tangents);
extrudedMesh.SetColors(vertColors);
extrudedMesh.SetUVs(0, uvs);
extrudedMesh.SetTriangles(tris, 0, true);
}
}
}

View File

@@ -8,28 +8,14 @@ namespace Obi
{
[AddComponentMenu("Physics/Obi/Obi Rope Line Renderer", 884)]
[ExecuteInEditMode]
[RequireComponent(typeof(MeshRenderer))]
[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(ObiPathSmoother))]
public class ObiRopeLineRenderer : MonoBehaviour
[RequireComponent(typeof(ObiPathSmoother))]
public class ObiRopeLineRenderer : MonoBehaviour, ObiActorRenderer<ObiRopeLineRenderer>
{
static ProfilerMarker m_UpdateLineRopeRendererChunksPerfMarker = new ProfilerMarker("UpdateLineRopeRenderer");
public ObiActor actor { get; private set; }
private List<Vector3> vertices = new List<Vector3>();
private List<Vector3> normals = new List<Vector3>();
private List<Vector4> tangents = new List<Vector4>();
private List<Vector2> uvs = new List<Vector2>();
private List<Color> vertColors = new List<Color>();
private List<int> tris = new List<int>();
public Material material;
ObiRopeBase rope;
ObiPathSmoother smoother;
#if (UNITY_2019_1_OR_NEWER)
System.Action<ScriptableRenderContext, Camera> renderCallback;
#endif
[HideInInspector] [NonSerialized] public Mesh lineMesh;
public RenderBatchParams renderParams = new RenderBatchParams(true);
[Range(0, 1)]
public float uvAnchor = 0; /**< Normalized position of texture coordinate origin along rope.*/
@@ -40,159 +26,42 @@ namespace Obi
public float thicknessScale = 0.8f; /**< Scales section thickness.*/
public void Awake()
{
actor = GetComponent<ObiActor>();
}
void OnEnable()
{
CreateMeshIfNeeded();
#if (UNITY_2019_1_OR_NEWER)
renderCallback = new System.Action<ScriptableRenderContext, Camera>((cntxt, cam) => { UpdateRenderer(cam); });
RenderPipelineManager.beginCameraRendering += renderCallback;
#endif
Camera.onPreCull += UpdateRenderer;
rope = GetComponent<ObiRopeBase>();
smoother = GetComponent<ObiPathSmoother>();
((ObiActorRenderer<ObiRopeLineRenderer>)this).EnableRenderer();
}
void OnDisable()
{
((ObiActorRenderer<ObiRopeLineRenderer>)this).DisableRenderer();
}
#if (UNITY_2019_1_OR_NEWER)
RenderPipelineManager.beginCameraRendering -= renderCallback;
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
Camera.onPreCull -= UpdateRenderer;
case ObiSolver.BackendType.Compute:
default:
GameObject.DestroyImmediate(lineMesh);
}
private void CreateMeshIfNeeded()
{
if (lineMesh == null)
{
lineMesh = new Mesh();
lineMesh.name = "extrudedMesh";
lineMesh.MarkDynamic();
GetComponent<MeshFilter>().mesh = lineMesh;
if (SystemInfo.supportsComputeShaders)
return new ComputeLineRopeRenderSystem(solver);
return null;
}
}
public void UpdateRenderer(Camera camera)
{
using (m_UpdateLineRopeRendererChunksPerfMarker.Auto())
{
if (camera == null || !rope.gameObject.activeInHierarchy)
return;
CreateMeshIfNeeded();
ClearMeshData();
float actualToRestLengthRatio = smoother.SmoothLength / rope.restLength;
float vCoord = -uvScale.y * rope.restLength * uvAnchor; // v texture coordinate.
int sectionIndex = 0;
Vector3 localSpaceCamera = rope.transform.InverseTransformPoint(camera.transform.position);
Vector3 vertex = Vector3.zero, normal = Vector3.zero;
Vector4 bitangent = Vector4.zero;
Vector2 uv = Vector2.zero;
for (int c = 0; c < smoother.smoothChunks.Count; ++c)
{
ObiList<ObiPathFrame> curve = smoother.smoothChunks[c];
for (int i = 0; i < curve.Count; ++i)
{
// Calculate previous and next curve indices:
int prevIndex = Mathf.Max(i - 1, 0);
// advance v texcoord:
vCoord += uvScale.y * (Vector3.Distance(curve.Data[i].position, curve.Data[prevIndex].position) /
(normalizeV ? smoother.SmoothLength : actualToRestLengthRatio));
// calculate section thickness (either constant, or particle radius based):
float sectionThickness = curve.Data[i].thickness * thicknessScale;
normal.x = curve.Data[i].position.x - localSpaceCamera.x;
normal.y = curve.Data[i].position.y - localSpaceCamera.y;
normal.z = curve.Data[i].position.z - localSpaceCamera.z;
normal.Normalize();
bitangent.x = -(normal.y * curve.Data[i].tangent.z - normal.z * curve.Data[i].tangent.y);
bitangent.y = -(normal.z * curve.Data[i].tangent.x - normal.x * curve.Data[i].tangent.z);
bitangent.z = -(normal.x * curve.Data[i].tangent.y - normal.y * curve.Data[i].tangent.x);
bitangent.w = 0;
bitangent.Normalize();
vertex.x = curve.Data[i].position.x - bitangent.x * sectionThickness;
vertex.y = curve.Data[i].position.y - bitangent.y * sectionThickness;
vertex.z = curve.Data[i].position.z - bitangent.z * sectionThickness;
vertices.Add(vertex);
vertex.x = curve.Data[i].position.x + bitangent.x * sectionThickness;
vertex.y = curve.Data[i].position.y + bitangent.y * sectionThickness;
vertex.z = curve.Data[i].position.z + bitangent.z * sectionThickness;
vertices.Add(vertex);
normals.Add(-normal);
normals.Add(-normal);
bitangent.w = 1;
tangents.Add(bitangent);
tangents.Add(bitangent);
vertColors.Add(curve.Data[i].color);
vertColors.Add(curve.Data[i].color);
uv.x = 0; uv.y = vCoord;
uvs.Add(uv);
uv.x = 1;
uvs.Add(uv);
if (i < curve.Count - 1)
{
tris.Add(sectionIndex * 2);
tris.Add((sectionIndex + 1) * 2);
tris.Add(sectionIndex * 2 + 1);
tris.Add(sectionIndex * 2 + 1);
tris.Add((sectionIndex + 1) * 2);
tris.Add((sectionIndex + 1) * 2 + 1);
}
sectionIndex++;
}
}
CommitMeshData();
}
}
private void ClearMeshData()
{
lineMesh.Clear();
vertices.Clear();
normals.Clear();
tangents.Clear();
uvs.Clear();
vertColors.Clear();
tris.Clear();
}
private void CommitMeshData()
{
lineMesh.SetVertices(vertices);
lineMesh.SetNormals(normals);
lineMesh.SetTangents(tangents);
lineMesh.SetColors(vertColors);
lineMesh.SetUVs(0, uvs);
lineMesh.SetTriangles(tris, 0, true);
}
}
}

View File

@@ -1,259 +1,85 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using Unity.Profiling;
namespace Obi
{
[AddComponentMenu("Physics/Obi/Obi Rope Mesh Renderer", 886)]
[ExecuteInEditMode]
[RequireComponent(typeof(MeshRenderer))]
[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(ObiPathSmoother))]
public class ObiRopeMeshRenderer : MonoBehaviour
public class ObiRopeMeshRenderer : MonoBehaviour, ObiActorRenderer<ObiRopeMeshRenderer>, IMeshDataProvider
{
static ProfilerMarker m_UpdateMeshRopeRendererChunksPerfMarker = new ProfilerMarker("UpdateMeshRopeRenderer");
public Renderer sourceRenderer { get; protected set; }
public ObiActor actor { get; private set; }
public uint meshInstances { get {return instances;} }
[SerializeProperty("SourceMesh")]
[SerializeField] private Mesh mesh;
[field: SerializeField]
public Mesh sourceMesh { get; set; }
[SerializeProperty("SweepAxis")]
[SerializeField] private ObiPathFrame.Axis axis;
[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;
[SerializeProperty("Instances")]
[SerializeField] private int instances = 1;
[SerializeProperty("InstanceSpacing")]
[SerializeField] private float instanceSpacing = 1;
public uint instances = 1;
public float instanceSpacing = 0;
public float offset = 0;
public Vector3 scale = Vector3.one;
[HideInInspector] [SerializeField] private float meshSizeAlongAxis = 1;
private Vector3[] inputVertices;
private Vector3[] inputNormals;
private Vector4[] inputTangents;
private Vector3[] vertices;
private Vector3[] normals;
private Vector4[] tangents;
private int[] orderedVertices = new int[0];
private ObiPathSmoother smoother;
public Mesh SourceMesh
public void Awake()
{
set { mesh = value; PreprocessInputMesh(); }
get { return mesh; }
actor = GetComponent<ObiActor>();
sourceRenderer = GetComponent<MeshRenderer>();
}
public ObiPathFrame.Axis SweepAxis
public void OnEnable()
{
set { axis = value; PreprocessInputMesh(); }
get { return axis; }
((ObiActorRenderer<ObiRopeMeshRenderer>)this).EnableRenderer();
}
public int Instances
public void OnDisable()
{
set { instances = value; PreprocessInputMesh(); }
get { return instances; }
((ObiActorRenderer<ObiRopeMeshRenderer>)this).DisableRenderer();
}
public float InstanceSpacing
public void OnValidate()
{
set { instanceSpacing = value; PreprocessInputMesh(); }
get { return instanceSpacing; }
((ObiActorRenderer<ObiRopeMeshRenderer>)this).SetRendererDirty(Oni.RenderingSystemType.MeshRope);
}
[HideInInspector] [NonSerialized] public Mesh deformedMesh;
void OnEnable()
RenderSystem<ObiRopeMeshRenderer> ObiRenderer<ObiRopeMeshRenderer>.CreateRenderSystem(ObiSolver solver)
{
smoother = GetComponent<ObiPathSmoother>();
smoother.OnCurveGenerated += UpdateRenderer;
PreprocessInputMesh();
}
void OnDisable()
{
smoother.OnCurveGenerated -= UpdateRenderer;
GameObject.DestroyImmediate(deformedMesh);
}
private void PreprocessInputMesh()
{
if (deformedMesh == null)
{
deformedMesh = new Mesh();
deformedMesh.name = "deformedMesh";
deformedMesh.MarkDynamic();
GetComponent<MeshFilter>().mesh = deformedMesh;
}
deformedMesh.Clear();
if (mesh == null)
{
orderedVertices = new int[0];
return;
}
// Clamp instance count to a positive value.
instances = Mathf.Max(0, instances);
// combine all mesh instances into a single mesh:
Mesh combinedMesh = new Mesh();
CombineInstance[] meshInstances = new CombineInstance[instances];
Vector3 pos = Vector3.zero;
// initial offset for the combined mesh is half the size of its bounding box in the swept axis:
pos[(int)axis] = mesh.bounds.extents[(int)axis];
for (int i = 0; i < instances; ++i)
{
meshInstances[i].mesh = mesh;
meshInstances[i].transform = Matrix4x4.TRS(pos, Quaternion.identity, Vector3.one);
pos[(int)axis] = mesh.bounds.extents[(int)axis] + (i + 1) * mesh.bounds.size[(int)axis] * instanceSpacing;
}
combinedMesh.CombineMeshes(meshInstances, true, true);
// get combined mesh data:
inputVertices = combinedMesh.vertices;
inputNormals = combinedMesh.normals;
inputTangents = combinedMesh.tangents;
// sort vertices along curve axis:
float[] keys = new float[inputVertices.Length];
orderedVertices = new int[inputVertices.Length];
for (int i = 0; i < keys.Length; ++i)
{
keys[i] = inputVertices[i][(int)axis];
orderedVertices[i] = i;
}
Array.Sort(keys, orderedVertices);
// Copy the combined mesh data to deform it:
deformedMesh.vertices = combinedMesh.vertices;
deformedMesh.normals = combinedMesh.normals;
deformedMesh.tangents = combinedMesh.tangents;
deformedMesh.uv = combinedMesh.uv;
deformedMesh.uv2 = combinedMesh.uv2;
deformedMesh.uv3 = combinedMesh.uv3;
deformedMesh.uv4 = combinedMesh.uv4;
deformedMesh.colors = combinedMesh.colors;
deformedMesh.triangles = combinedMesh.triangles;
vertices = deformedMesh.vertices;
normals = deformedMesh.normals;
tangents = deformedMesh.tangents;
// Calculate scale along swept axis so that the mesh spans the entire lenght of the rope if required.
meshSizeAlongAxis = combinedMesh.bounds.size[(int)axis];
// destroy combined mesh:
GameObject.DestroyImmediate(combinedMesh);
}
public void UpdateRenderer(ObiActor actor)
{
using (m_UpdateMeshRopeRendererChunksPerfMarker.Auto())
switch (solver.backendType)
{
if (mesh == null)
return;
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
case ObiSolver.BackendType.Burst: return new BurstMeshRopeRenderSystem(solver);
#endif
case ObiSolver.BackendType.Compute:
default:
if (smoother.smoothChunks.Count == 0)
return;
ObiList<ObiPathFrame> curve = smoother.smoothChunks[0];
if (curve.Count < 2)
return;
var rope = actor as ObiRopeBase;
float actualToRestLengthRatio = stretchWithRope ? smoother.SmoothLength / rope.restLength : 1;
// squashing factor, makes mesh thinner when stretched and thicker when compresssed.
float squashing = Mathf.Clamp(1 + volumeScaling * (1 / Mathf.Max(actualToRestLengthRatio, 0.01f) - 1), 0.01f, 2);
// Calculate scale along swept axis so that the mesh spans the entire lenght of the rope if required.
Vector3 actualScale = scale;
if (spanEntireLength)
actualScale[(int)axis] = rope.restLength / meshSizeAlongAxis;
float previousVertexValue = 0;
float meshLength = 0;
int index = 0;
int nextIndex = 1;
int prevIndex = 0;
float sectionMagnitude = Vector3.Distance(curve[index].position, curve[nextIndex].position);
// basis matrix for deforming the mesh:
Matrix4x4 basis = curve[0].ToMatrix(axis);
for (int i = 0; i < orderedVertices.Length; ++i)
{
int vIndex = orderedVertices[i];
float vertexValue = inputVertices[vIndex][(int)axis] * actualScale[(int)axis] + offset;
// Calculate how much we've advanced in the sort axis since the last vertex:
meshLength += (vertexValue - previousVertexValue) * actualToRestLengthRatio;
previousVertexValue = vertexValue;
// If we have advanced to the next section of the curve:
while (meshLength > sectionMagnitude && sectionMagnitude > Mathf.Epsilon)
{
meshLength -= sectionMagnitude;
index = Mathf.Min(index + 1, curve.Count - 1);
// Calculate previous and next curve indices:
nextIndex = Mathf.Min(index + 1, curve.Count - 1);
prevIndex = Mathf.Max(index - 1, 0);
// Calculate current tangent as the vector between previous and next curve points:
sectionMagnitude = Vector3.Distance(curve[index].position, curve[nextIndex].position);
// Update basis matrix:
basis = curve[index].ToMatrix(axis);
}
float sectionThickness = curve[index].thickness;
// calculate deformed vertex position:
Vector3 offsetFromCurve = Vector3.Scale(inputVertices[vIndex], actualScale * sectionThickness * squashing);
offsetFromCurve[(int)axis] = meshLength;
vertices[vIndex] = curve[index].position + basis.MultiplyVector(offsetFromCurve);
normals[vIndex] = basis.MultiplyVector(inputNormals[vIndex]);
tangents[vIndex] = basis * inputTangents[vIndex]; // avoids expensive implicit conversion from Vector4 to Vector3.
tangents[vIndex].w = inputTangents[vIndex].w;
}
CommitMeshData();
if (SystemInfo.supportsComputeShaders)
return new ComputeMeshRopeRenderSystem(solver);
return null;
}
}
private void CommitMeshData()
{
deformedMesh.vertices = vertices;
deformedMesh.normals = normals;
deformedMesh.tangents = tangents;
deformedMesh.RecalculateBounds();
}
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,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

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

View File

@@ -8,7 +8,6 @@ namespace Obi
/**
* This component plugs a prefab instance at each cut in the rope. Optionally, it will also place a couple instances at the start/end of an open rope.
*/
[RequireComponent(typeof(ObiRope))]
[RequireComponent(typeof(ObiPathSmoother))]
public class ObiRopePrefabPlugger : MonoBehaviour
{
@@ -25,12 +24,12 @@ namespace Obi
{
instances = new List<GameObject>();
smoother = GetComponent<ObiPathSmoother>();
smoother.OnCurveGenerated += UpdatePlugs;
GetComponent<ObiActor>().OnInterpolate += UpdatePlugs;
}
void OnDisable()
{
smoother.OnCurveGenerated -= UpdatePlugs;
GetComponent<ObiActor>().OnInterpolate -= UpdatePlugs;
ClearPrefabInstances();
}
@@ -54,43 +53,46 @@ namespace Obi
}
// Update is called once per frame
void UpdatePlugs(ObiActor actor)
void UpdatePlugs(ObiActor actor, float simulatedTime, float substepTime)
{
var rope = actor as ObiRopeBase;
if (!actor.isLoaded)
return;
// cache the rope's transform matrix/quaternion:
Matrix4x4 l2w = rope.transform.localToWorldMatrix;
Matrix4x4 l2w = smoother.actor.solver.transform.localToWorldMatrix;
Quaternion l2wRot = l2w.rotation;
int instanceIndex = 0;
// place prefabs at the start/end of each curve:
for (int c = 0; c < smoother.smoothChunks.Count; ++c)
{
ObiList<ObiPathFrame> curve = smoother.smoothChunks[c];
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 = curve[0];
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 < smoother.smoothChunks.Count - 1) ||
(plugEnd && c == smoother.smoothChunks.Count - 1))
{
if ((plugTears && c < chunkCount - 1) ||
(plugEnd && c == chunkCount - 1))
{
var instance = GetOrCreatePrefabInstance(instanceIndex++);
instance.SetActive(true);
ObiPathFrame frame = curve[curve.Count - 1];
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.rotation = l2wRot * Quaternion.LookRotation(frame.tangent, frame.binormal);
instance.transform.localScale = instanceScale;
}

View File

@@ -13,14 +13,19 @@ namespace Obi
public float inThreshold = 0.4f;
[Header("Roll out/in speeds")]
public float outSpeed = 0.05f;
public float inSpeed = 0.15f;
public float outSpeed = 4;
public float inSpeed = 2;
public void Awake()
public float maxLength = 10;
private float restLength;
public void Awake()
{
cursor = GetComponent<ObiRopeCursor>();
rope = GetComponent<ObiRope>();
}
restLength = rope.restLength;
}
public void OnValidate()
{
@@ -33,21 +38,25 @@ namespace Obi
{
// get current and rest lengths:
float length = rope.CalculateLength();
float restLength = rope.restLength;
// calculate difference between current length and rest length:
float diff = Mathf.Max(0, length - restLength);
float lengthChange = 0;
// if the rope has been stretched beyond the reel out threshold, increase its rest length:
if (diff > outThreshold)
restLength += diff * outSpeed;
lengthChange = outSpeed * Time.deltaTime;
// if the rope is not stretched past the reel in threshold, decrease its rest length:
if (diff < inThreshold)
restLength -= diff * inSpeed;
lengthChange = -inSpeed * Time.deltaTime;
// set the new rest length:
cursor.ChangeLength(restLength);
// make sure not to exceed maxLength:
lengthChange -= Mathf.Max(0, restLength + lengthChange - maxLength);
// set the new rest length:
restLength = cursor.ChangeLength(lengthChange);
}
}
}