重新导入obi

This commit is contained in:
2026-04-06 11:35:18 +08:00
parent 05fa2d6e5e
commit ae3002a0e2
1643 changed files with 232496 additions and 13 deletions

View File

@@ -0,0 +1,54 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Obi
{
[CreateAssetMenu(fileName = "rope section", menuName = "Obi/Rope Section", order = 142)]
public class ObiRopeSection : ScriptableObject
{
[HideInInspector] public List<Vector2> vertices;
public int snapX = 0;
public int snapY = 0;
public int Segments{
get{return vertices.Count-1;}
}
public void OnEnable(){
if (vertices == null){
vertices = new List<Vector2>();
CirclePreset(8);
}
}
public void CirclePreset(int segments){
vertices.Clear();
for (int j = 0; j <= segments; ++j){
float angle = 2 * Mathf.PI / segments * j;
vertices.Add(Mathf.Cos(angle)*Vector2.right + Mathf.Sin(angle)*Vector2.up);
}
}
/**
* Snaps a float value to the nearest multiple of snapInterval.
*/
public static int SnapTo(float val, int snapInterval, int threshold){
int intVal = (int) val;
if (snapInterval <= 0)
return intVal;
int under = Mathf.FloorToInt(val / snapInterval) * snapInterval;
int over = under + snapInterval;
if (intVal - under < threshold) return under;
if (over - intVal < threshold) return over;
return intVal;
}
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: ee7737c43f5734f87be9e49d1bbfba78
labels:
- ObiRope
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: fdb742a900c8d453ea5ce5027e80ad00, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,16 @@
using UnityEngine;
using System.Collections;
namespace Obi
{
// Abstracts rope topolgy as a list of elements.
[System.Serializable]
public class ObiStructuralElement
{
public int particle1;
public int particle2;
public float restLength;
public float constraintForce;
public float tearResistance;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,88 @@
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
namespace Obi
{
public interface IObiPathDataChannel
{
int Count { get; }
bool Dirty { get; }
void Clean();
void RemoveAt(int index);
}
public abstract class ObiPathDataChannel<T,U> : IObiPathDataChannel
{
protected ObiInterpolator<U> interpolator;
protected bool dirty = false;
public List<T> data = new List<T>();
public int Count
{
get { return data.Count; }
}
public bool Dirty
{
get { return dirty; }
}
public void Clean()
{
dirty = false;
}
public ObiPathDataChannel(ObiInterpolator<U> interpolator)
{
this.interpolator = interpolator;
}
public T this[int i]
{
get { return data[i]; }
set { data[i] = value; dirty = true; }
}
public void RemoveAt(int index)
{
data.RemoveAt(index);
dirty = true;
}
public U Evaluate(U v0, U v1, U v2, U v3, float mu)
{
return interpolator.Evaluate(v0, v1, v2, v3, mu);
}
public U EvaluateFirstDerivative(U v0, U v1, U v2, U v3, float mu)
{
return interpolator.EvaluateFirstDerivative(v0, v1, v2, v3, mu);
}
public U EvaluateSecondDerivative(U v0, U v1, U v2, U v3, float mu)
{
return interpolator.EvaluateSecondDerivative(v0, v1, v2, v3, mu);
}
public int GetSpanCount(bool closed)
{
int cps = Count;
if (cps < 2)
return 0;
return closed ? cps : cps - 1;
}
public int GetSpanControlPointAtMu(bool closed, float mu, out float spanMu)
{
int spanCount = GetSpanCount(closed);
spanMu = mu * spanCount;
int i = (mu >= 1f) ? (spanCount - 1) : (int)spanMu;
spanMu -= i;
return i;
}
}
}

View File

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

View File

@@ -0,0 +1,94 @@
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
namespace Obi
{
public abstract class ObiPathDataChannelIdentity<T> : ObiPathDataChannel<T,T>
{
public ObiPathDataChannelIdentity(ObiInterpolator<T> interpolator) : base(interpolator)
{
}
public T GetFirstDerivative(int index)
{
int nextCP = (index + 1) % Count;
return EvaluateFirstDerivative(this[index],
this[index],
this[nextCP],
this[nextCP], 0);
}
public T GetSecondDerivative(int index)
{
int nextCP = (index + 1) % Count;
return EvaluateSecondDerivative(this[index],
this[index],
this[nextCP],
this[nextCP], 0);
}
public T GetAtMu(bool closed, float mu)
{
int cps = Count;
if (cps >= 2)
{
float p;
int i = GetSpanControlPointAtMu(closed, mu, out p);
int nextCP = (i + 1) % cps;
return Evaluate(this[i],
this[i],
this[nextCP],
this[nextCP], p);
}
else
{
throw new InvalidOperationException("Cannot get property in path because it has less than 2 control points.");
}
}
public T GetFirstDerivativeAtMu(bool closed, float mu)
{
int cps = Count;
if (cps >= 2)
{
float p;
int i = GetSpanControlPointAtMu(closed, mu, out p);
int nextCP = (i + 1) % cps;
return EvaluateFirstDerivative(this[i],
this[i],
this[nextCP],
this[nextCP], p);
}
else
{
throw new InvalidOperationException("Cannot get derivative in path because it has less than 2 control points.");
}
}
public T GetSecondDerivativeAtMu(bool closed, float mu)
{
int cps = Count;
if (cps >= 2)
{
float p;
int i = GetSpanControlPointAtMu(closed, mu, out p);
int nextCP = (i + 1) % cps;
return EvaluateSecondDerivative(this[i],
this[i],
this[nextCP],
this[nextCP], p);
}
else
{
throw new InvalidOperationException("Cannot get second derivative in path because it has less than 2 control points.");
}
}
}
}

View File

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

View File

@@ -0,0 +1,125 @@
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
namespace Obi
{
[Serializable]
public class ObiPointsDataChannel : ObiPathDataChannel<ObiWingedPoint, Vector3>
{
public ObiPointsDataChannel() : base(new ObiCatmullRomInterpolator3D()) { }
public Vector3 GetTangent(int index)
{
int nextCP = (index + 1) % Count;
var wp1 = this[index];
var wp2 = this[nextCP];
return EvaluateFirstDerivative(wp1.position,
wp1.outTangentEndpoint,
wp2.inTangentEndpoint,
wp2.position, 0);
}
public Vector3 GetAcceleration(int index)
{
int nextCP = (index + 1) % Count;
var wp1 = this[index];
var wp2 = this[nextCP];
return EvaluateSecondDerivative(wp1.position,
wp1.outTangentEndpoint,
wp2.inTangentEndpoint,
wp2.position, 0);
}
/**
* Returns spline position at time mu, with 0<=mu<=1 where 0 is the start of the spline
* and 1 is the end.
*/
public Vector3 GetPositionAtMu(bool closed,float mu)
{
int cps = Count;
if (cps >= 2)
{
float p;
int i = GetSpanControlPointAtMu(closed, mu, out p);
int nextCP = (i + 1) % cps;
var wp1 = this[i];
var wp2 = this[nextCP];
return Evaluate(wp1.position,
wp1.outTangentEndpoint,
wp2.inTangentEndpoint,
wp2.position, p);
}
else
{
throw new InvalidOperationException("Cannot get position in path because it has zero control points.");
}
}
/**
* Returns normal tangent vector at time mu, with 0<=mu<=1 where 0 is the start of the spline
* and 1 is the end.
*/
public Vector3 GetTangentAtMu(bool closed, float mu)
{
int cps = Count;
if (cps >= 2)
{
float p;
int i = GetSpanControlPointAtMu(closed, mu, out p);
int nextCP = (i + 1) % cps;
var wp1 = this[i];
var wp2 = this[nextCP];
return EvaluateFirstDerivative(wp1.position,
wp1.outTangentEndpoint,
wp2.inTangentEndpoint,
wp2.position, p);
}
else
{
throw new InvalidOperationException("Cannot get derivative in path because it has less than 2 control points.");
}
}
/**
* Returns acceleration at time mu, with 0<=mu<=1 where 0 is the start of the spline
* and 1 is the end.
*/
public Vector3 GetAccelerationAtMu(bool closed, float mu)
{
int cps = Count;
if (cps >= 2)
{
float p;
int i = GetSpanControlPointAtMu(closed, mu, out p);
int nextCP = (i + 1) % cps;
var wp1 = this[i];
var wp2 = this[nextCP];
return EvaluateSecondDerivative(wp1.position,
wp1.outTangentEndpoint,
wp2.inTangentEndpoint,
wp2.position, p);
}
else
{
throw new InvalidOperationException("Cannot get second derivative in path because it has less than 2 control points.");
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,49 @@
using UnityEngine;
using System.Collections;
namespace Obi
{
public class ObiCatmullRomInterpolator : ObiInterpolator<float>
{
/**
* 1D bezier spline interpolation
*/
public float Evaluate(float y0, float y1, float y2, float y3, float mu)
{
float imu = 1 - mu;
return imu * imu * imu * y0 +
3f * imu * imu * mu * y1 +
3f * imu * mu * mu * y2 +
mu * mu * mu * y3;
}
/**
* 1D catmull rom spline second derivative
*/
public float EvaluateFirstDerivative(float y0, float y1, float y2, float y3, float mu)
{
float imu = 1 - mu;
return 3f * imu * imu * (y1 - y0) +
6f * imu * mu * (y2 - y1) +
3f * mu * mu * (y3 - y2);
}
/**
* 1D catmull rom spline second derivative
*/
public float EvaluateSecondDerivative(float y0, float y1, float y2, float y3, float mu)
{
float imu = 1 - mu;
return 3f * imu * imu * (y1 - y0) +
6f * imu * mu * (y2 - y1) +
3f * mu * mu * (y3 - y2);
}
}
}

View File

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

View File

@@ -0,0 +1,46 @@
using UnityEngine;
using System.Collections;
namespace Obi
{
public class ObiCatmullRomInterpolator3D : ObiInterpolator<Vector3>
{
private ObiCatmullRomInterpolator interpolator = new ObiCatmullRomInterpolator();
/**
* 3D spline interpolation
*/
public Vector3 Evaluate(Vector3 y0, Vector3 y1, Vector3 y2, Vector3 y3, float mu)
{
return new Vector3(interpolator.Evaluate(y0.x, y1.x, y2.x, y3.x, mu),
interpolator.Evaluate(y0.y, y1.y, y2.y, y3.y, mu),
interpolator.Evaluate(y0.z, y1.z, y2.z, y3.z, mu));
}
/**
* 3D spline first derivative
*/
public Vector3 EvaluateFirstDerivative(Vector3 y0, Vector3 y1, Vector3 y2, Vector3 y3, float mu)
{
return new Vector3(interpolator.EvaluateFirstDerivative(y0.x, y1.x, y2.x, y3.x, mu),
interpolator.EvaluateFirstDerivative(y0.y, y1.y, y2.y, y3.y, mu),
interpolator.EvaluateFirstDerivative(y0.z, y1.z, y2.z, y3.z, mu));
}
/**
* 3D spline second derivative
*/
public Vector3 EvaluateSecondDerivative(Vector3 y0, Vector3 y1, Vector3 y2, Vector3 y3, float mu)
{
return new Vector3(interpolator.EvaluateSecondDerivative(y0.x, y1.x, y2.x, y3.x, mu),
interpolator.EvaluateSecondDerivative(y0.y, y1.y, y2.y, y3.y, mu),
interpolator.EvaluateSecondDerivative(y0.z, y1.z, y2.z, y3.z, mu));
}
}
}

View File

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

View File

@@ -0,0 +1,49 @@
using UnityEngine;
using System.Collections;
namespace Obi
{
public class ObiColorInterpolator3D : ObiInterpolator<Color>
{
private ObiCatmullRomInterpolator interpolator = new ObiCatmullRomInterpolator();
/**
* 3D spline interpolation
*/
public Color Evaluate(Color y0, Color y1, Color y2, Color y3, float mu)
{
return new Color(interpolator.Evaluate(y0.r, y1.r, y2.r, y3.r, mu),
interpolator.Evaluate(y0.g, y1.g, y2.g, y3.g, mu),
interpolator.Evaluate(y0.b, y1.b, y2.b, y3.b, mu),
interpolator.Evaluate(y0.a, y1.a, y2.a, y3.a, mu));
}
/**
* 3D spline first derivative
*/
public Color EvaluateFirstDerivative(Color y0, Color y1, Color y2, Color y3, float mu)
{
return new Color(interpolator.EvaluateFirstDerivative(y0.r, y1.r, y2.r, y3.r, mu),
interpolator.EvaluateFirstDerivative(y0.g, y1.g, y2.g, y3.g, mu),
interpolator.EvaluateFirstDerivative(y0.b, y1.b, y2.b, y3.b, mu),
interpolator.EvaluateFirstDerivative(y0.a, y1.a, y2.a, y3.a, mu));
}
/**
* 3D spline second derivative
*/
public Color EvaluateSecondDerivative(Color y0, Color y1, Color y2, Color y3, float mu)
{
return new Color(interpolator.EvaluateSecondDerivative(y0.r, y1.r, y2.r, y3.r, mu),
interpolator.EvaluateSecondDerivative(y0.g, y1.g, y2.g, y3.g, mu),
interpolator.EvaluateSecondDerivative(y0.b, y1.b, y2.b, y3.b, mu),
interpolator.EvaluateSecondDerivative(y0.a, y1.a, y2.a, y3.a, mu));
}
}
}

View File

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

View File

@@ -0,0 +1,33 @@
using UnityEngine;
using System.Collections;
namespace Obi
{
public class ObiConstantInterpolator : ObiInterpolator<int>
{
/**
* constant interpolator
*/
public int Evaluate(int y0, int y1, int y2, int y3, float mu)
{
return mu < 0.5f ? y1 : y2;
}
/**
* derivative of constant value:
*/
public int EvaluateFirstDerivative(int y0, int y1, int y2, int y3, float mu)
{
return 0;
}
/**
* second derivative of constant value:
*/
public int EvaluateSecondDerivative(int y0, int y1, int y2, int y3, float mu)
{
return 0;
}
}
}

View File

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

View File

@@ -0,0 +1,13 @@
using UnityEngine;
using System.Collections;
namespace Obi
{
public interface ObiInterpolator<T>
{
T Evaluate(T v0, T v1, T v2, T v3, float mu);
T EvaluateFirstDerivative(T v0, T v1, T v2, T v3, float mu);
T EvaluateSecondDerivative(T v0, T v1, T v2, T v3, float mu);
}
}

View File

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

View File

@@ -0,0 +1,408 @@
using UnityEngine;
using UnityEngine.Events;
using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using UnityEngine.Serialization;
namespace Obi
{
[System.Serializable]
public class PathControlPointEvent : UnityEvent<int>
{
}
[Serializable]
public class ObiPath
{
[HideInInspector] [SerializeField] List<string> m_Names = new List<string>();
[HideInInspector] [SerializeField] public ObiPointsDataChannel m_Points = new ObiPointsDataChannel();
[HideInInspector] [SerializeField] ObiNormalDataChannel m_Normals = new ObiNormalDataChannel();
[HideInInspector] [SerializeField] ObiColorDataChannel m_Colors = new ObiColorDataChannel();
[HideInInspector] [SerializeField] ObiThicknessDataChannel m_Thickness = new ObiThicknessDataChannel();
[HideInInspector] [SerializeField] ObiMassDataChannel m_Masses = new ObiMassDataChannel();
[HideInInspector] [SerializeField] ObiRotationalMassDataChannel m_RotationalMasses = new ObiRotationalMassDataChannel();
[FormerlySerializedAs("m_Phases")]
[HideInInspector] [SerializeField] ObiPhaseDataChannel m_Filters = new ObiPhaseDataChannel();
[HideInInspector] [SerializeField] private bool m_Closed = false;
protected bool dirty = false;
protected const int arcLenghtSamples = 20;
[HideInInspector] [SerializeField] protected List<float> m_ArcLengthTable = new List<float>();
[HideInInspector] [SerializeField] protected float m_TotalSplineLenght = 0.0f;
public UnityEvent OnPathChanged = new UnityEvent();
public PathControlPointEvent OnControlPointAdded = new PathControlPointEvent();
public PathControlPointEvent OnControlPointRemoved = new PathControlPointEvent();
public PathControlPointEvent OnControlPointRenamed = new PathControlPointEvent();
private IEnumerable<IObiPathDataChannel> GetDataChannels()
{
yield return m_Points;
yield return m_Normals;
yield return m_Colors;
yield return m_Thickness;
yield return m_Masses;
yield return m_RotationalMasses;
yield return m_Filters;
}
public ObiPointsDataChannel points { get { return m_Points; }}
public ObiNormalDataChannel normals { get { return m_Normals; } }
public ObiColorDataChannel colors { get { return m_Colors; } }
public ObiThicknessDataChannel thicknesses { get { return m_Thickness; } }
public ObiMassDataChannel masses { get { return m_Masses; } }
public ObiRotationalMassDataChannel rotationalMasses { get { return m_RotationalMasses; } }
public ObiPhaseDataChannel filters { get { return m_Filters; } }
public ReadOnlyCollection<float> ArcLengthTable
{
get { return m_ArcLengthTable.AsReadOnly(); }
}
public float Length
{
get { return m_TotalSplineLenght; }
}
public int ArcLengthSamples
{
get { return arcLenghtSamples; }
}
public int ControlPointCount
{
get { return m_Points.Count;}
}
public bool Closed
{
get { return m_Closed; }
set
{
if (value != m_Closed)
{
m_Closed = value;
dirty = true;
}
}
}
public int GetSpanCount()
{
return m_Points.GetSpanCount(m_Closed);
}
public int GetSpanControlPointForMu(float mu, out float spanMu)
{
return m_Points.GetSpanControlPointAtMu(m_Closed, mu, out spanMu);
}
public int GetClosestControlPointIndex(float mu)
{
float spanMu;
int cp = GetSpanControlPointForMu(mu, out spanMu);
if (spanMu > 0.5f)
return (cp + 1) % ControlPointCount;
else
return cp % ControlPointCount;
}
/**
* Returns the curve parameter (mu) at a certain length of the curve, using linear interpolation
* of the values cached in arcLengthTable.
*/
public float GetMuAtLenght(float length)
{
if (length <= 0) return 0;
if (length >= m_TotalSplineLenght) return 1;
int i;
for (i = 1; i < m_ArcLengthTable.Count; ++i)
{
if (length < m_ArcLengthTable[i]) break;
}
float prevMu = (i - 1) / (float)(m_ArcLengthTable.Count - 1);
float nextMu = i / (float)(m_ArcLengthTable.Count - 1);
float s = (length - m_ArcLengthTable[i - 1]) / (m_ArcLengthTable[i] - m_ArcLengthTable[i - 1]);
return prevMu + (nextMu - prevMu) * s;
}
/**
* Recalculates spline arc lenght in world space using Gauss-Lobatto adaptive integration.
* @param acc minimum accuray desired (eg 0.00001f)
* @param maxevals maximum number of spline evaluations we want to allow per segment.
*/
public float RecalculateLenght(Matrix4x4 referenceFrame, float acc, int maxevals)
{
if (referenceFrame == null)
{
m_TotalSplineLenght = 0;
return 0;
}
m_TotalSplineLenght = 0.0f;
m_ArcLengthTable.Clear();
m_ArcLengthTable.Add(0);
float step = 1 / (float)(arcLenghtSamples + 1);
int controlPoints = ControlPointCount;
if (controlPoints >= 2)
{
int spans = GetSpanCount();
for (int cp = 0; cp < spans; ++cp)
{
int nextCP = (cp + 1) % controlPoints;
var wp1 = m_Points[cp];
var wp2 = m_Points[nextCP];
Vector3 _p = referenceFrame.MultiplyPoint3x4(wp1.position);
Vector3 p = referenceFrame.MultiplyPoint3x4(wp1.outTangentEndpoint);
Vector3 p_ = referenceFrame.MultiplyPoint3x4(wp2.inTangentEndpoint);
Vector3 p__ = referenceFrame.MultiplyPoint3x4(wp2.position);
for (int i = 0; i <= Mathf.Max(1, arcLenghtSamples); ++i)
{
float a = i * step;
float b = (i + 1) * step;
float segmentLength = GaussLobattoIntegrationStep(_p, p, p_, p__, a, b,
m_Points.EvaluateFirstDerivative(_p, p, p_, p__, a).magnitude,
m_Points.EvaluateFirstDerivative(_p, p, p_, p__, b).magnitude, 0, maxevals, acc);
m_TotalSplineLenght += segmentLength;
m_ArcLengthTable.Add(m_TotalSplineLenght);
}
}
}
else
{
Debug.LogWarning("A path needs at least 2 control points to be defined.");
}
return m_TotalSplineLenght;
}
/**
* One step of the adaptive integration method using Gauss-Lobatto quadrature.
* Takes advantage of the fact that the arc lenght of a vector function is equal to the
* integral of the magnitude of first derivative.
*/
private float GaussLobattoIntegrationStep(Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4,
float a, float b,
float fa, float fb, int nevals, int maxevals, float acc)
{
if (nevals >= maxevals) return 0;
// Constants used in the algorithm
float alpha = Mathf.Sqrt(2.0f / 3.0f);
float beta = 1.0f / Mathf.Sqrt(5.0f);
// Here the abcissa points and function values for both the 4-point
// and the 7-point rule are calculated (the points at the end of
// interval come from the function call, i.e., fa and fb. Also note
// the 7-point rule re-uses all the points of the 4-point rule.)
float h = (b - a) / 2;
float m = (a + b) / 2;
float mll = m - alpha * h;
float ml = m - beta * h;
float mr = m + beta * h;
float mrr = m + alpha * h;
nevals += 5;
float fmll = m_Points.EvaluateFirstDerivative(p1, p2, p3, p4, mll).magnitude;
float fml = m_Points.EvaluateFirstDerivative(p1, p2, p3, p4, ml).magnitude;
float fm = m_Points.EvaluateFirstDerivative(p1, p2, p3, p4, m).magnitude;
float fmr = m_Points.EvaluateFirstDerivative(p1, p2, p3, p4, mr).magnitude;
float fmrr = m_Points.EvaluateFirstDerivative(p1, p2, p3, p4, mrr).magnitude;
// Both the 4-point and 7-point rule integrals are evaluted
float integral4 = (h / 6) * (fa + fb + 5 * (fml + fmr));
float integral7 = (h / 1470) * (77 * (fa + fb) + 432 * (fmll + fmrr) + 625 * (fml + fmr) + 672 * fm);
// The difference betwen the 4-point and 7-point integrals is the
// estimate of the accuracy
if ((integral4 - integral7) < acc || mll <= a || b <= mrr)
{
if (!(m > a && b > m))
{
Debug.LogError("Spline integration reached an interval with no more machine numbers");
}
return integral7;
}
else
{
return GaussLobattoIntegrationStep(p1, p2, p3, p4, a, mll, fa, fmll, nevals, maxevals, acc)
+ GaussLobattoIntegrationStep(p1, p2, p3, p4, mll, ml, fmll, fml, nevals, maxevals, acc)
+ GaussLobattoIntegrationStep(p1, p2, p3, p4, ml, m, fml, fm, nevals, maxevals, acc)
+ GaussLobattoIntegrationStep(p1, p2, p3, p4, m, mr, fm, fmr, nevals, maxevals, acc)
+ GaussLobattoIntegrationStep(p1, p2, p3, p4, mr, mrr, fmr, fmrr, nevals, maxevals, acc)
+ GaussLobattoIntegrationStep(p1, p2, p3, p4, mrr, b, fmrr, fb, nevals, maxevals, acc);
}
}
public void SetName(int index, string name)
{
m_Names[index] = name;
if (OnControlPointRenamed != null)
OnControlPointRenamed.Invoke(index);
dirty = true;
}
public string GetName(int index)
{
return m_Names[index];
}
public void AddControlPoint(Vector3 position, Vector3 inTangentVector, Vector3 outTangentVector, Vector3 normal, float mass, float rotationalMass, float thickness, int filter, Color color, string name)
{
InsertControlPoint(ControlPointCount, position, inTangentVector, outTangentVector, normal, mass, rotationalMass, thickness, filter, color, name);
}
public void InsertControlPoint(int index, Vector3 position, Vector3 inTangentVector, Vector3 outTangentVector, Vector3 normal, float mass, float rotationalMass, float thickness, int filter, Color color, string name)
{
m_Points.data.Insert(index, new ObiWingedPoint(inTangentVector,position,outTangentVector));
m_Colors.data.Insert(index, color);
m_Normals.data.Insert(index, normal);
m_Thickness.data.Insert(index, thickness);
m_Masses.data.Insert(index, mass);
m_RotationalMasses.data.Insert(index, rotationalMass);
m_Filters.data.Insert(index, filter);
m_Names.Insert(index,name);
if (OnControlPointAdded != null)
OnControlPointAdded.Invoke(index);
dirty = true;
}
public int InsertControlPoint(float mu)
{
int controlPoints = ControlPointCount;
if (controlPoints >= 2)
{
if (!System.Single.IsNaN(mu))
{
float p;
int i = GetSpanControlPointForMu(mu, out p);
int next = (i + 1) % controlPoints;
var wp1 = m_Points[i];
var wp2 = m_Points[next];
Vector3 P0_1 = (1 - p) * wp1.position + p * wp1.outTangentEndpoint;
Vector3 P1_2 = (1 - p) * wp1.outTangentEndpoint + p * wp2.inTangentEndpoint;
Vector3 P2_3 = (1 - p) * wp2.inTangentEndpoint + p * wp2.position;
Vector3 P01_12 = (1 - p) * P0_1 + p * P1_2;
Vector3 P12_23 = (1 - p) * P1_2 + p * P2_3;
Vector3 P0112_1223 = (1 - p) * P01_12 + p * P12_23;
wp1.SetOutTangentEndpoint(P0_1);
wp2.SetInTangentEndpoint(P2_3);
m_Points[i] = wp1;
m_Points[next] = wp2;
Color color = m_Colors.Evaluate(m_Colors[i],
m_Colors[i],
m_Colors[next],
m_Colors[next], p);
Vector3 normal = m_Normals.Evaluate(m_Normals[i],
m_Normals[i],
m_Normals[next],
m_Normals[next], p);
float thickness = m_Thickness.Evaluate(m_Thickness[i],
m_Thickness[i],
m_Thickness[next],
m_Thickness[next], p);
float mass = m_Masses.Evaluate(m_Masses[i],
m_Masses[i],
m_Masses[next],
m_Masses[next], p);
float rotationalMass = m_RotationalMasses.Evaluate(m_RotationalMasses[i],
m_RotationalMasses[i],
m_RotationalMasses[next],
m_RotationalMasses[next], p);
int filter = m_Filters.Evaluate(m_Filters[i],
m_Filters[i],
m_Filters[next],
m_Filters[next], p);
InsertControlPoint(i + 1, P0112_1223, P01_12 - P0112_1223, P12_23 - P0112_1223, normal, mass,rotationalMass, thickness, filter, color, GetName(i));
return i + 1;
}
}
return -1;
}
public void Clear()
{
for (int i = ControlPointCount-1; i >= 0; --i)
RemoveControlPoint(i);
m_TotalSplineLenght = 0.0f;
m_ArcLengthTable.Clear();
m_ArcLengthTable.Add(0);
}
public void RemoveControlPoint(int index)
{
foreach (var channel in GetDataChannels())
channel.RemoveAt(index);
m_Names.RemoveAt(index);
if (OnControlPointRemoved != null)
OnControlPointRemoved.Invoke(index);
dirty = true;
}
public void FlushEvents()
{
bool isDirty = dirty;
foreach (var channel in GetDataChannels())
{
isDirty |= channel.Dirty;
channel.Clean();
}
if (OnPathChanged != null && isDirty)
{
dirty = false;
OnPathChanged.Invoke();
}
}
}
}

View File

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

View File

@@ -0,0 +1,168 @@
using System;
using UnityEngine;
namespace Obi
{
public struct ObiPathFrame
{
public enum Axis
{
X = 0,
Y = 1,
Z = 2
}
public Vector3 position;
public Vector3 tangent;
public Vector3 normal;
public Vector3 binormal;
public Vector4 color;
public float thickness;
public ObiPathFrame(Vector3 position, Vector3 tangent, Vector3 normal, Vector3 binormal, Vector4 color, float thickness){
this.position = position;
this.normal = normal;
this.tangent = tangent;
this.binormal = binormal;
this.color = color;
this.thickness = thickness;
}
public void Reset()
{
position = Vector3.zero;
tangent = Vector3.forward;
normal = Vector3.up;
binormal = Vector3.right;
color = Color.white;
thickness = 0;
}
public static ObiPathFrame operator +(ObiPathFrame c1, ObiPathFrame c2)
{
return new ObiPathFrame(c1.position + c2.position,c1.tangent + c2.tangent,c1.normal + c2.normal,c1.binormal + c2.binormal,c1.color + c2.color, c1.thickness + c2.thickness);
}
public static ObiPathFrame operator *(float f,ObiPathFrame c)
{
return new ObiPathFrame(c.position * f, c.tangent * f, c.normal * f, c.binormal * f,c.color * f, c.thickness * f);
}
public static void WeightedSum(float w1, float w2, float w3, ref ObiPathFrame c1, ref ObiPathFrame c2, ref ObiPathFrame c3, ref ObiPathFrame sum)
{
sum.position.x = c1.position.x * w1 + c2.position.x * w2 + c3.position.x * w3;
sum.position.y = c1.position.y * w1 + c2.position.y * w2 + c3.position.y * w3;
sum.position.z = c1.position.z * w1 + c2.position.z * w2 + c3.position.z * w3;
sum.tangent.x = c1.tangent.x * w1 + c2.tangent.x * w2 + c3.tangent.x * w3;
sum.tangent.y = c1.tangent.y * w1 + c2.tangent.y * w2 + c3.tangent.y * w3;
sum.tangent.z = c1.tangent.z * w1 + c2.tangent.z * w2 + c3.tangent.z * w3;
sum.normal.x = c1.normal.x * w1 + c2.normal.x * w2 + c3.normal.x * w3;
sum.normal.y = c1.normal.y * w1 + c2.normal.y * w2 + c3.normal.y * w3;
sum.normal.z = c1.normal.z * w1 + c2.normal.z * w2 + c3.normal.z * w3;
sum.binormal.x = c1.binormal.x * w1 + c2.binormal.x * w2 + c3.binormal.x * w3;
sum.binormal.y = c1.binormal.y * w1 + c2.binormal.y * w2 + c3.binormal.y * w3;
sum.binormal.z = c1.binormal.z * w1 + c2.binormal.z * w2 + c3.binormal.z * w3;
sum.color.x = c1.color.x * w1 + c2.color.x * w2 + c3.color.x * w3;
sum.color.y = c1.color.y * w1 + c2.color.y * w2 + c3.color.y * w3;
sum.color.z = c1.color.z * w1 + c2.color.z * w2 + c3.color.z * w3;
sum.color.w = c1.color.w * w1 + c2.color.w * w2 + c3.color.w * w3;
sum.thickness = c1.thickness * w1 + c2.thickness * w2 + c3.thickness * w3;
}
public void SetTwist(float twist)
{
Quaternion twistQ = Quaternion.AngleAxis(twist, tangent);
normal = twistQ * normal;
binormal = twistQ * binormal;
}
public void SetTwistAndTangent(float twist, Vector3 tangent)
{
this.tangent = tangent;
normal = new Vector3(tangent.y, tangent.x, 0).normalized;
binormal = Vector3.Cross(normal, tangent);
Quaternion twistQ = Quaternion.AngleAxis(twist, tangent);
normal = twistQ * normal;
binormal = twistQ * binormal;
}
public void Transport(ObiPathFrame frame, float twist)
{
// Calculate delta rotation:
Quaternion rotQ = Quaternion.FromToRotation(tangent, frame.tangent);
Quaternion twistQ = Quaternion.AngleAxis(twist, frame.tangent);
Quaternion finalQ = twistQ * rotQ;
// Rotate previous frame axes to obtain the new ones:
normal = finalQ * normal;
binormal = finalQ * binormal;
tangent = frame.tangent;
position = frame.position;
thickness = frame.thickness;
color = frame.color;
}
public void Transport(Vector3 newPosition, Vector3 newTangent, float twist)
{
// Calculate delta rotation:
Quaternion rotQ = Quaternion.FromToRotation(tangent, newTangent);
Quaternion twistQ = Quaternion.AngleAxis(twist, newTangent);
Quaternion finalQ = twistQ * rotQ;
// Rotate previous frame axes to obtain the new ones:
normal = finalQ * normal;
binormal = finalQ * binormal;
tangent = newTangent;
position = newPosition;
}
// Transport, hinting the normal.
public void Transport(Vector3 newPosition, Vector3 newTangent, Vector3 newNormal, float twist)
{
normal = Quaternion.AngleAxis(twist, newTangent) * newNormal;
tangent = newTangent;
binormal = Vector3.Cross(normal, tangent);
position = newPosition;
}
public Matrix4x4 ToMatrix(Axis mainAxis)
{
Matrix4x4 basis = new Matrix4x4();
int xo = ((int)mainAxis) % 3 * 4;
int yo = ((int)mainAxis + 1) % 3 * 4;
int zo = ((int)mainAxis + 2) % 3 * 4;
basis[xo] = tangent[0];
basis[xo + 1] = tangent[1];
basis[xo + 2] = tangent[2];
basis[yo] = binormal[0];
basis[yo + 1] = binormal[1];
basis[yo + 2] = binormal[2];
basis[zo] = normal[0];
basis[zo + 1] = normal[1];
basis[zo + 2] = normal[2];
return basis;
}
public void DebugDraw(float size)
{
Debug.DrawRay(position, binormal * size, Color.red);
Debug.DrawRay(position, normal * size, Color.green);
Debug.DrawRay(position, tangent * size, Color.blue);
}
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,100 @@
using UnityEngine;
using System;
using System.Collections;
namespace Obi
{
[Serializable]
public struct ObiWingedPoint
{
public enum TangentMode
{
Aligned,
Mirrored,
Free,
}
public TangentMode tangentMode;
public Vector3 inTangent;
public Vector3 position;
public Vector3 outTangent;
public Vector3 inTangentEndpoint
{
get { return position + inTangent; }
}
public Vector3 outTangentEndpoint
{
get { return position + outTangent; }
}
public ObiWingedPoint(Vector3 inTangent, Vector3 point, Vector3 outTangent)
{
this.tangentMode = TangentMode.Aligned;
this.inTangent = inTangent;
this.position = point;
this.outTangent = outTangent;
}
public void SetInTangentEndpoint(Vector3 value)
{
Vector3 newTangent = value - position;
switch (tangentMode)
{
case TangentMode.Mirrored: outTangent = -newTangent; break;
case TangentMode.Aligned: outTangent = -newTangent.normalized * outTangent.magnitude; break;
}
inTangent = newTangent;
}
public void SetOutTangentEndpoint(Vector3 value)
{
Vector3 newTangent = value - position;
switch (tangentMode)
{
case TangentMode.Mirrored: inTangent = -newTangent; break;
case TangentMode.Aligned: inTangent = -newTangent.normalized * inTangent.magnitude; break;
}
outTangent = newTangent;
}
public void SetInTangent(Vector3 value)
{
Vector3 newTangent = value;
switch (tangentMode)
{
case TangentMode.Mirrored: outTangent = -newTangent; break;
case TangentMode.Aligned: outTangent = -newTangent.normalized * outTangent.magnitude; break;
}
inTangent = newTangent;
}
public void SetOutTangent(Vector3 value)
{
Vector3 newTangent = value;
switch (tangentMode)
{
case TangentMode.Mirrored: inTangent = -newTangent; break;
case TangentMode.Aligned: inTangent = -newTangent.normalized * inTangent.magnitude; break;
}
outTangent = newTangent;
}
public void Transform(Vector3 translation, Quaternion rotation, Vector3 scale)
{
position += translation;
inTangent = rotation * Vector3.Scale(inTangent, scale);
outTangent = rotation * Vector3.Scale(outTangent, scale);
}
}
}

View File

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