升级obi
This commit is contained in:
@@ -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)
|
||||
|
||||
192
Assets/Obi/Scripts/RopeAndRod/Actors/ObiBoneOverride.cs
Normal file
192
Assets/Obi/Scripts/RopeAndRod/Actors/ObiBoneOverride.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Obi/Scripts/RopeAndRod/Actors/ObiBoneOverride.cs.meta
Normal file
11
Assets/Obi/Scripts/RopeAndRod/Actors/ObiBoneOverride.cs.meta
Normal 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:
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
|
||||
@@ -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++)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Obi
|
||||
|
||||
@@ -63,7 +63,6 @@ namespace Obi
|
||||
{
|
||||
throw new InvalidOperationException("Cannot get position in path because it has zero control points.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
icon: {fileID: 2800000, guid: 8791eecf125744cbeadea65319c29d5a, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9a4cb8b10f8b049c2ae0a97c50c9b33c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e19ecda6559144921868e71085db8408
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: feb78fd6bed7a49979dee4205d74deaa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ad311e324f23a480d867e8e9b7f89cfe
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fab1fee6e450d4e28a7a2770af3c94d9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
337
Assets/Obi/Scripts/RopeAndRod/Utils/ObiPinhole.cs
Normal file
337
Assets/Obi/Scripts/RopeAndRod/Utils/ObiPinhole.cs
Normal 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();
|
||||
11
Assets/Obi/Scripts/RopeAndRod/Utils/ObiPinhole.cs.meta
Normal file
11
Assets/Obi/Scripts/RopeAndRod/Utils/ObiPinhole.cs.meta
Normal 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:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user