You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1587 lines
71 KiB

using System;
using System.Collections.Generic;
using UnityEngine;
namespace BansheeGz.BGSpline.Curve
{
/// <summary>
/// Basic math operations for curves (Distance, Position, Tangent, Closest point ).
/// It caches some data for quick access and recalculate it if curve changes.
/// It uses uniform split with forward differencing algorithm, which is the fastest available (for uniform split).
/// </summary>
public class BGCurveBaseMath : BGCurveMathI, IDisposable
{
#region events
//===============================================================================================
// Events
//===============================================================================================
/// <summary> Change was requested by changed curve's data </summary>
public event EventHandler ChangeRequested;
/// <summary> Math was changed</summary>
public event EventHandler Changed;
#endregion
#region fields
//===============================================================================================
// Fields
//===============================================================================================
/// <summary> all possible attributes (fields) to calculate</summary>
public enum Field
{
Position = 1,
Tangent = 2,
}
/// <summary> which fields to precalculate </summary>
public enum Fields
{
Position = 1,
PositionAndTangent = 3,
}
//curve
protected readonly BGCurve curve;
//what and how to calc
protected Config config;
//cached data for length calculations
protected readonly List<SectionInfo> cachedSectionInfos = new List<SectionInfo>();
// pools for sections
protected readonly List<SectionInfo> poolSectionInfos = new List<SectionInfo>();
// pools for points
protected readonly List<SectionPointInfo> poolPointInfos = new List<SectionPointInfo>();
//curve's length
protected float cachedLength;
//should we calculate position? (This is probably always true)
protected bool cachePosition;
//should we calculate tangent?
protected bool cacheTangent;
//closest point calculator
protected BGCurveCalculatorClosestPoint closestPointCalculator;
// frame when data was recalculated
private int recalculatedAtFrame = -1;
// frame when math was created
private int createdAtFrame;
// force section recalculation, even if it's not changed
protected bool ignoreSectionChangedCheck;
//some coefficients (these are used by approximation algorithm)
private double h;
private double h2;
private double h3;
/// <summary>Suppress all warnings, printed to the log </summary>
public bool SuppressWarning { get; set; }
/// <summary>get Curve</summary>
public BGCurve Curve
{
get { return curve; }
}
/// <summary>Get calculated sections data</summary>
public List<SectionInfo> SectionInfos
{
get { return cachedSectionInfos; }
}
/// <summary>Access calculated section's data by index</summary>
public SectionInfo this[int i]
{
get { return cachedSectionInfos[i]; }
}
/// <summary>How much sections in total</summary>
public int SectionsCount
{
get { return cachedSectionInfos.Count; }
}
/// <summary>Get last used config</summary>
public Config Configuration
{
get { return config; }
}
// if tangent is calculated and formula is needed
protected bool NeedTangentFormula
{
get { return !config.UsePointPositionsToCalcTangents && cacheTangent; }
}
/// <summary> How much approximation points are used in total </summary>
public int PointsCount
{
get
{
if (SectionsCount == 0) return 0;
var count = 0;
var length = cachedSectionInfos.Count;
for (var i = 0; i < length; i++) count += cachedSectionInfos[i].PointsCount;
return count;
}
}
#endregion
#region constructors
//===============================================================================================
// Constructors
//===============================================================================================
/// <summary> Math with default config settings </summary>
public BGCurveBaseMath(BGCurve curve) : this(curve, new Config(Fields.Position))
{
}
/// <summary> Math with config settings </summary>
public BGCurveBaseMath(BGCurve curve, Config config)
{
this.curve = curve;
curve.Changed += CurveChanged;
Init(config ?? new Config(Fields.Position));
}
/// <summary>This is an old constructor, left for compatibility. Only Fields.PositionWorld is calculated.
/// Use another constructor to specify which fields and how need to be calculated. </summary>
[Obsolete("Use another constructors")]
public BGCurveBaseMath(BGCurve curve, bool traceChanges, int parts = 30, bool usePointPositionsToCalcTangents = false)
: this(curve, new Config(Fields.Position) {Parts = parts, UsePointPositionsToCalcTangents = usePointPositionsToCalcTangents})
{
}
#endregion
#region Init
//===============================================================================================
// Init
//===============================================================================================
/// <summary>Init math with new config and recalculate it's data </summary>
public virtual void Init(Config config)
{
//if we calculated positions before (but not tangent) we should skip the check if section was changed or not and force recalculation (cause positions may not change)
if (this.config != null)
{
ignoreSectionChangedCheck = this.config.Fields == Fields.Position && config.Fields == Fields.PositionAndTangent;
this.config.Update -= ConfigOnUpdate;
}
else
{
ignoreSectionChangedCheck = false;
}
//assign new config
this.config = config;
config.Parts = Mathf.Clamp(config.Parts, 1, 1000);
this.config.Update += ConfigOnUpdate;
createdAtFrame = Time.frameCount;
cachePosition = Field.Position.In(config.Fields.Val());
cacheTangent = Field.Tangent.In(config.Fields.Val());
//No fields- no need to calculate or trace anything.
if (!cachePosition && !cacheTangent)
throw new UnityException("No fields were chosen. Create math like this: new BGCurveBaseMath(curve, new BGCurveBaseMath.Config(BGCurveBaseMath.Fields.Position))");
//some additional init steps for subclasses
AfterInit(config);
//calculate data, based on new config
Recalculate(true);
}
//some additional initialization for subclasses
protected virtual void AfterInit(Config config)
{
}
#endregion
#region Public methods
//===============================================================================================
// Public methods
//===============================================================================================
//=========================================== Calculate by formula
/// <summary> Calculate point world position between 2 points by a formula. This is a slow method</summary>
/// <param name="t">Ratio between (0,1)</param>
/// <param name="useLocal">Use local coordinates instead of world</param>
public virtual Vector3 CalcPositionByT(BGCurvePoint @from, BGCurvePoint to, float t, bool useLocal = false)
{
t = Mathf.Clamp01(t);
var fromPos = useLocal ? from.PositionLocal : from.PositionWorld;
var toPos = useLocal ? to.PositionLocal : to.PositionWorld;
Vector3 result;
if (from.ControlType == BGCurvePoint.ControlTypeEnum.Absent && to.ControlType == BGCurvePoint.ControlTypeEnum.Absent)
{
//lerp
result = fromPos + ((toPos - fromPos)*t);
}
else
{
var fromPosHandle = useLocal ? from.ControlSecondLocal + fromPos : from.ControlSecondWorld;
var toPosHandle = useLocal ? to.ControlFirstLocal + toPos : to.ControlFirstWorld;
result = (from.ControlType != BGCurvePoint.ControlTypeEnum.Absent && to.ControlType != BGCurvePoint.ControlTypeEnum.Absent)
? BGCurveFormulas.BezierCubic(t, fromPos, fromPosHandle, toPosHandle, toPos)
: BGCurveFormulas.BezierQuadratic(t, fromPos, (@from.ControlType == BGCurvePoint.ControlTypeEnum.Absent) ? toPosHandle : fromPosHandle, toPos);
}
return result;
}
/// <summary> Calculate a tangent between 2 points by a formula. This is a slow method</summary>
/// <param name="t">Ratio between (0,1)</param>
/// <param name="useLocal">Use local coordinates instead of world</param>
public virtual Vector3 CalcTangentByT(BGCurvePoint @from, BGCurvePoint to, float t, bool useLocal = false)
{
if (Curve.PointsCount < 2) return Vector3.zero;
t = Mathf.Clamp01(t);
var fromPos = useLocal ? from.PositionLocal : from.PositionWorld;
var toPos = useLocal ? to.PositionLocal : to.PositionWorld;
Vector3 result;
if (from.ControlType == BGCurvePoint.ControlTypeEnum.Absent && to.ControlType == BGCurvePoint.ControlTypeEnum.Absent)
{
result = toPos - fromPos;
}
else
{
var fromPosHandle = useLocal ? from.ControlSecondLocal + fromPos : from.ControlSecondWorld;
var toPosHandle = useLocal ? to.ControlFirstLocal + toPos : to.ControlFirstWorld;
result = (from.ControlType != BGCurvePoint.ControlTypeEnum.Absent && to.ControlType != BGCurvePoint.ControlTypeEnum.Absent)
? BGCurveFormulas.BezierCubicDerivative(t, fromPos, fromPosHandle, toPosHandle, toPos)
: BGCurveFormulas.BezierQuadraticDerivative(t, fromPos, (@from.ControlType == BGCurvePoint.ControlTypeEnum.Absent) ? toPosHandle : fromPosHandle, toPos);
}
return result.normalized;
}
//=========================================== Generic methods
//using useLocal is significantly slower. See interface for more comments
public virtual Vector3 CalcByDistanceRatio(float distanceRatio, out Vector3 tangent, bool useLocal = false)
{
return CalcByDistance(cachedLength*distanceRatio, out tangent, useLocal);
}
//using useLocal is significantly slower. See interface for more comments
public virtual Vector3 CalcByDistance(float distance, out Vector3 tangent, bool useLocal = false)
{
if (distance < 0f) distance = 0f;
else if (distance > cachedLength) distance = cachedLength;
Vector3 position;
BinarySearchByDistance(distance, out position, out tangent, true, true);
if (useLocal)
{
position = curve.transform.InverseTransformPoint(position);
tangent = curve.transform.InverseTransformDirection(tangent);
}
return position;
}
//using useLocal is significantly slower. See interface for more comments
public virtual Vector3 CalcByDistanceRatio(Field field, float distanceRatio, bool useLocal = false)
{
return CalcByDistance(field, cachedLength*distanceRatio, useLocal);
}
//using useLocal is significantly slower. See interface for more comments
public virtual Vector3 CalcByDistance(Field field, float distance, bool useLocal = false)
{
var pointsCount = curve.PointsCount;
switch (pointsCount)
{
case 0:
return Vector3.zero;
case 1:
return field == Field.Position ? curve[0].PositionWorld : Vector3.zero;
}
if (distance < 0f) distance = 0f;
else if (distance > cachedLength) distance = cachedLength;
var calcPosition = field == Field.Position;
Vector3 position, tangent;
BinarySearchByDistance(distance, out position, out tangent, calcPosition, !calcPosition);
if (useLocal)
{
switch (field)
{
case Field.Position:
position = curve.transform.InverseTransformPoint(position);
break;
case Field.Tangent:
tangent = curve.transform.InverseTransformDirection(tangent);
break;
}
}
return calcPosition ? position : tangent;
}
//=========================================== Position and Tangent
//using useLocal is significantly slower. See interface for more comments
public virtual Vector3 CalcPositionAndTangentByDistanceRatio(float distanceRatio, out Vector3 tangent, bool useLocal = false)
{
return CalcByDistanceRatio(distanceRatio, out tangent, useLocal);
}
//using useLocal is significantly slower. See interface for more comments
public virtual Vector3 CalcPositionAndTangentByDistance(float distance, out Vector3 tangent, bool useLocal = false)
{
return CalcByDistance(distance, out tangent, useLocal);
}
//=========================================== Position
//using useLocal is significantly slower. See interface for more comments
public virtual Vector3 CalcPositionByDistanceRatio(float distanceRatio, bool useLocal = false)
{
return CalcByDistanceRatio(Field.Position, distanceRatio, useLocal);
}
//using useLocal is significantly slower. See interface for more comments
public virtual Vector3 CalcPositionByDistance(float distance, bool useLocal = false)
{
return CalcByDistance(Field.Position, distance, useLocal);
}
//=========================================== Tangent
//using useLocal is significantly slower. See interface for more comments
public virtual Vector3 CalcTangentByDistanceRatio(float distanceRatio, bool useLocal = false)
{
return CalcByDistanceRatio(Field.Tangent, distanceRatio, useLocal);
}
//using useLocal is significantly slower. See interface for more comments
public virtual Vector3 CalcTangentByDistance(float distance, bool useLocal = false)
{
return CalcByDistance(Field.Tangent, distance, useLocal);
}
//=========================================== Find Secton's index
//see interface for comments
public int CalcSectionIndexByDistance(float distance)
{
return FindSectionIndexByDistance(ClampDistance(distance));
}
//see interface for comments
public int CalcSectionIndexByDistanceRatio(float ratio)
{
return FindSectionIndexByDistance(DistanceByRatio(ratio));
}
//=========================================== Closest Point
//see interface for comments
public Vector3 CalcPositionByClosestPoint(Vector3 point, bool skipSectionsOptimization = false, bool skipPointsOptimization = false)
{
if (closestPointCalculator == null) closestPointCalculator = new BGCurveCalculatorClosestPoint(this);
float distance;
Vector3 tangent;
return closestPointCalculator.CalcPositionByClosestPoint(point, out distance, out tangent, skipSectionsOptimization, skipPointsOptimization);
}
//see interface for comments
public Vector3 CalcPositionByClosestPoint(Vector3 point, out float distance, bool skipSectionsOptimization = false, bool skipPointsOptimization = false)
{
if (closestPointCalculator == null) closestPointCalculator = new BGCurveCalculatorClosestPoint(this);
Vector3 tangent;
return closestPointCalculator.CalcPositionByClosestPoint(point, out distance, out tangent, skipSectionsOptimization, skipPointsOptimization);
}
//see interface for comments
public Vector3 CalcPositionByClosestPoint(Vector3 point, out float distance, out Vector3 tangent, bool skipSectionsOptimization = false, bool skipPointsOptimization = false)
{
if (closestPointCalculator == null) closestPointCalculator = new BGCurveCalculatorClosestPoint(this);
return closestPointCalculator.CalcPositionByClosestPoint(point, out distance, out tangent, skipSectionsOptimization, skipPointsOptimization);
}
//=========================================== Total Distance
//see interface for comments
public virtual float GetDistance()
{
return cachedLength;
}
//=========================================== Curve's point world coordinates (faster then using point.positionWord etc.)
/// <summary> Get point's world position </summary>
public Vector3 GetPosition(int pointIndex)
{
var count = cachedSectionInfos.Count;
if (count == 0 || count <= pointIndex) return curve[pointIndex].PositionWorld;
return pointIndex < count ? cachedSectionInfos[pointIndex].OriginalFrom : cachedSectionInfos[pointIndex - 1].OriginalTo;
}
/// <summary> Get point's world first control position </summary>
public Vector3 GetControlFirst(int pointIndex)
{
var count = cachedSectionInfos.Count;
if (count == 0) return curve[pointIndex].ControlFirstWorld;
if (pointIndex == 0) return curve.Closed ? cachedSectionInfos[count - 1].OriginalToControl : curve[0].ControlFirstWorld;
return cachedSectionInfos[pointIndex - 1].OriginalToControl;
}
/// <summary> Get point's world second control position </summary>
public Vector3 GetControlSecond(int pointIndex)
{
var count = cachedSectionInfos.Count;
if (count == 0) return curve[pointIndex].ControlSecondWorld;
if (pointIndex == count) return curve.Closed ? cachedSectionInfos[count - 1].OriginalFromControl : curve[pointIndex].ControlSecondWorld;
return cachedSectionInfos[pointIndex].OriginalFromControl;
}
//=========================================== Misc
/// <summary>Is given field is calculated?</summary>
/// <returns>true if field is calculated</returns>
public virtual bool IsCalculated(Field field)
{
return ((int) field & (int) config.Fields) != 0;
}
// C# standard dispose
public virtual void Dispose()
{
curve.Changed -= CurveChanged;
config.Update -= ConfigOnUpdate;
cachedSectionInfos.Clear();
poolSectionInfos.Clear();
}
//=========================================== Calculate and cache required data
/// <summary>Calculates and cache all required data for performance reason. It is an expensive operation. </summary>
public virtual void Recalculate(bool force = false)
{
if (ChangeRequested != null) ChangeRequested(this, null);
if (!force && config.ShouldUpdate != null && !config.ShouldUpdate()) return;
var currentSectionsCount = cachedSectionInfos.Count;
if (curve.PointsCount < 2)
{
cachedLength = 0;
if (currentSectionsCount > 0) cachedSectionInfos.Clear();
if (Changed != null) Changed(this, null);
return;
}
//we should at least warn about non-optimal usage of Recalculate (with more than 1 update per frame)
if (recalculatedAtFrame != Time.frameCount || Time.frameCount == createdAtFrame) recalculatedAtFrame = Time.frameCount;
else
Warning("We noticed you are updating math more than once per frame. This is not optimal. " +
"If you use curve.ImmediateChangeEvents by some reason, try to use curve.Transaction to wrap all the changes to one single event.");
var pointsCount = curve.PointsCount;
var sectionsCount = curve.Closed ? pointsCount : pointsCount - 1;
//coefficients
h = 1.0/config.Parts;
h2 = h*h;
h3 = h2*h;
//ensure all sections inited
if (currentSectionsCount != sectionsCount)
{
if (currentSectionsCount < sectionsCount)
{
//not enough sections
var toAddCount = sectionsCount - currentSectionsCount;
//try to get from the pool
var poolCount = poolSectionInfos.Count;
var toAddBeforePoolCount = toAddCount;
for (var i = poolCount - 1; i >= 0 && toAddCount > 0; i--, toAddCount--) cachedSectionInfos.Add(poolSectionInfos[i]);
//pool was used
var usedPoolCount = toAddBeforePoolCount - toAddCount;
if (usedPoolCount != 0) poolSectionInfos.RemoveRange(poolSectionInfos.Count - usedPoolCount, usedPoolCount);
// we need to create new sections (pool is empty)
if (toAddCount > 0) for (var i = 0; i < toAddCount; i++) cachedSectionInfos.Add(new SectionInfo());
}
else
{
//too many sections
var toRemoveCount = currentSectionsCount - sectionsCount;
for (var i = sectionsCount; i < currentSectionsCount; i++) poolSectionInfos.Add(cachedSectionInfos[i]);
cachedSectionInfos.RemoveRange(currentSectionsCount - toRemoveCount, toRemoveCount);
}
}
//calculate fields for each section
for (var i = 0; i < pointsCount - 1; i++) CalculateSection(i, cachedSectionInfos[i], i == 0 ? null : cachedSectionInfos[i - 1], curve[i], curve[i + 1]);
var lastSection = cachedSectionInfos[sectionsCount - 1];
if (curve.Closed)
{
CalculateSection(sectionsCount - 1, lastSection, cachedSectionInfos[sectionsCount - 2], curve[pointsCount - 1], curve[0]);
if (cacheTangent) AdjustBoundaryPointsTangents(cachedSectionInfos[0], lastSection);
}
cachedLength = lastSection.DistanceFromEndToOrigin;
if (Changed != null) Changed(this, null);
}
#endregion
#region protected methods
//print a message to console if condition is met and calls callback method
protected virtual void Warning(string message, bool condition = true, Action callback = null)
{
if (!condition || !Application.isPlaying) return;
if (!SuppressWarning) Debug.Log("BGCurve[BGCurveBaseMath] Warning! " + message + ". You can suppress all warnings by using BGCurveBaseMath.SuppressWarning=true;");
if (callback != null) callback();
}
//calculates one single section data
// Performance:
// * for example: ~100 000 section points (1000 curve's points with 100 parts each) with both controls
// ~7.8 ms for Position
// ~13.5 ms for PositionAndTangent
protected virtual void CalculateSection(int index, SectionInfo section, SectionInfo prevSection, BGCurvePointI @from, BGCurvePointI to)
{
if (section == null) section = new SectionInfo();
section.DistanceFromStartToOrigin = prevSection == null ? 0 : prevSection.DistanceFromEndToOrigin;
var straightAndOptimized = config.OptimizeStraightLines && @from.ControlType == BGCurvePoint.ControlTypeEnum.Absent && to.ControlType == BGCurvePoint.ControlTypeEnum.Absent;
var pointsCount = straightAndOptimized ? 2 : config.Parts + 1;
// do we need to recalc points?
if (Reset(section, @from, to, pointsCount))
{
if (straightAndOptimized)
{
// ===================================================== Straight section with 2 points
Resize(section.points, 2);
var startPoint = section.points[0];
var endPoint = section.points[1];
startPoint.Position = section.OriginalFrom;
endPoint.Position = section.OriginalTo;
//distance
endPoint.DistanceToSectionStart = Vector3.Distance(section.OriginalFrom, section.OriginalTo);
//tangents
if (cacheTangent) startPoint.Tangent = endPoint.Tangent = (endPoint.Position - startPoint.Position).normalized;
}
else
{
// ===================================================== Section with {parts} smaller sections
CalculateSplitSection(section, @from, to);
}
if (cacheTangent)
{
section.OriginalFirstPointTangent = section[0].Tangent;
section.OriginalLastPointTangent = section[section.PointsCount - 1].Tangent;
}
}
// we should adjust tangents for previous section's last point and first point of the current section
if (cacheTangent && prevSection != null) AdjustBoundaryPointsTangents(section, prevSection);
section.DistanceFromEndToOrigin = section.DistanceFromStartToOrigin + section[section.PointsCount - 1].DistanceToSectionStart;
}
//adjust neighbour adjacent points tangents
private void AdjustBoundaryPointsTangents(SectionInfo section, SectionInfo prevSection)
{
if (IsUseDistanceToAdjustTangents(section, prevSection))
{
var distance1 = Vector3.SqrMagnitude(section[0].Position - section[1].Position);
var distance2 = Vector3.SqrMagnitude(prevSection[prevSection.PointsCount - 1].Position - prevSection[prevSection.PointsCount - 2].Position);
var overallDistance = distance1 + distance2;
if (Math.Abs(overallDistance) < BGCurve.Epsilon) return;
var ratio = distance1/overallDistance;
var reverseRatio = 1 - ratio;
section[0].Tangent = prevSection[prevSection.PointsCount - 1].Tangent = Vector3.Normalize(new Vector3(
section.OriginalFirstPointTangent.x*ratio + prevSection.OriginalLastPointTangent.x*reverseRatio,
section.OriginalFirstPointTangent.y*ratio + prevSection.OriginalLastPointTangent.y*reverseRatio,
section.OriginalFirstPointTangent.z*ratio + prevSection.OriginalLastPointTangent.z*reverseRatio));
}
else
{
//both tangents get adjusted
section[0].Tangent = prevSection[prevSection.PointsCount - 1].Tangent = Vector3.Normalize(new Vector3(
section.OriginalFirstPointTangent.x + prevSection.OriginalLastPointTangent.x,
section.OriginalFirstPointTangent.y + prevSection.OriginalLastPointTangent.y,
section.OriginalFirstPointTangent.z + prevSection.OriginalLastPointTangent.z));
}
}
//should we use distance between approximation points for adjusting boundary tangents
protected virtual bool IsUseDistanceToAdjustTangents(SectionInfo section, SectionInfo prevSection)
{
return config.OptimizeStraightLines && section.OriginalFromControlType == BGCurvePoint.ControlTypeEnum.Absent &&
(section.OriginalToControlType == BGCurvePoint.ControlTypeEnum.Absent || prevSection.OriginalFromControlType == BGCurvePoint.ControlTypeEnum.Absent);
}
//reset section's data and returns if recalculation is needed
protected virtual bool Reset(SectionInfo section, BGCurvePointI @from, BGCurvePointI to, int pointsCount)
{
return section.Reset(@from, to, pointsCount, ignoreSectionChangedCheck);
}
// this method contains some intentional 1) copy/paste 2) methods inlining 3) operators inlining- to increase performance
/**
* Calculates one split section's data using forward differencing algorithm
*
* The technique of forward differencing allows us to compute the polynomial at t=0,
* and then incrementally calculate points by adding (and updating) the differences.
*
* Thanks to Russel Lindsay (https://gist.github.com/rlindsay/c55be560ec41144f521f)
*
* Formulas for difference between t and t+h for cubic, quadratic, and linear polynomials
* (notice that the difference between t and t+h for a cubic polynomial is quadratic ((3ak)t^2 + (3ak^2 + 2bk)t + ak^3 + bk^2 + ck),
* likewise the difference in the quadratic case is linear, and in the linear case is a constant)
*
* --------------------
* Cubic polynomial (4 points, a, b, c, d)
* C(t) = a*t^3 + b*t^2 + c*t + d
*
* Cubic polynomial difference
* C(t + h) - C(t) http://www.wolframalpha.com/input/?i=%28a*%28t+%2B+h%29%5E3+%2B+b*%28t+%2B+h%29%5E2+%2B+c*%28t+%2B+h%29+%2B+d%29+-+%28a*t%5E3+%2B+b*t%5E2+%2B+c*t+%2B+d%29+
* = ah^3 + 3ah^2 t + 3aht^2 + bh^2 + 2bht + ch
---> i) = (3ah)t^2 + (3ah^2 + 2bh)t + ah^3 + bh^2 + ch
*
* --------------------
* quadratic polynomial
* Q(t) = at^2 + bt + c
*
* quadratic polynomial difference
* Q(t + h) - Q(t) http://www.wolframalpha.com/input/?i=%28a%28t%2Bh%29%5E2+%2B+b%28t%2Bh%29+%2B+c%29+-+%28at%5E2+%2B+bt+%2B+c%29
---> ii) = (2ah)t + ah^2 + bh
*
* ------------------
* linear polynomial
* L(t) = at + b
*
* linear polynomial difference
* L(t + h) - L(t) http://www.wolframalpha.com/input/?i=%28a%28t+%2B+h%29+%2B+b%29+-+%28at+%2B+b%29
---> iii) = ah
*/
protected virtual void CalculateSplitSection(SectionInfo section, BGCurvePointI @from, BGCurvePointI to)
{
var parts = config.Parts;
Resize(section.points, parts + 1);
var points = section.points;
// all section's values
var fromPos = section.OriginalFrom;
var toPos = section.OriginalTo;
var control1 = section.OriginalFromControl;
var control2 = section.OriginalToControl;
var controlFromAbsent = @from.ControlType == BGCurvePoint.ControlTypeEnum.Absent;
var controlToAbsent = to.ControlType == BGCurvePoint.ControlTypeEnum.Absent;
var noControls = controlFromAbsent && controlToAbsent;
var bothControls = !controlFromAbsent && !controlToAbsent;
if (!noControls && !bothControls && controlFromAbsent) control1 = control2;
//snapping
var snapIsOn = curve.SnapType == BGCurve.SnapTypeEnum.Curve;
//assign first last points directly to avoid accumulation errors
var firstPoint = points[0] ?? (section.points[0] = new SectionPointInfo());
firstPoint.Position = fromPos;
var lastPoint = points[parts] ?? (section.points[parts] = new SectionPointInfo());
lastPoint.Position = toPos;
if (noControls)
{
// ======================================================== NoControls
var currentX = (double) fromPos.x;
var currentY = (double) fromPos.y;
var currentZ = (double) fromPos.z;
var stepX = ((double) toPos.x - fromPos.x)/parts;
var stepY = ((double) toPos.y - fromPos.y)/parts;
var stepZ = ((double) toPos.z - fromPos.z)/parts;
var fromToTangentWorld = Vector3.zero;
if (cacheTangent) fromToTangentWorld = (toPos - fromPos).normalized;
lastPoint.DistanceToSectionStart = Vector3.Distance(toPos, fromPos);
var onePartDistance = lastPoint.DistanceToSectionStart/parts;
//-------------------------------- Critical section
for (var i = 1; i < parts; i++)
{
var point = points[i];
currentX += stepX;
currentY += stepY;
currentZ += stepZ;
var pos = new Vector3((float) currentX, (float) currentY, (float) currentZ);
if (snapIsOn) curve.ApplySnapping(ref pos);
point.Position = pos;
//---------- tangents
if (cacheTangent)
{
if (config.UsePointPositionsToCalcTangents)
{
//-------- Calc by point's positions
var prevPoint = section[i - 1];
var prevPosition = prevPoint.Position;
var tangent = new Vector3(pos.x - prevPosition.x, pos.y - prevPosition.y, pos.z - prevPosition.z);
//normalized inlined
var marnitude = (float) Math.Sqrt((double) tangent.x*(double) tangent.x + (double) tangent.y*(double) tangent.y + (double) tangent.z*(double) tangent.z);
tangent = ((double) marnitude > 9.99999974737875E-06) ? new Vector3(tangent.x/marnitude, tangent.y/marnitude, tangent.z/marnitude) : Vector3.zero;
prevPoint.Tangent = point.Tangent = tangent;
}
else point.Tangent = fromToTangentWorld;
}
point.DistanceToSectionStart = onePartDistance*i;
}
//-------------------------------- Critical section ends
//assign last point separately to get rid of accumulation errors
if (cacheTangent) firstPoint.Tangent = lastPoint.Tangent = fromToTangentWorld;
}
else
{
//for tangents
double tX = 0, tY = 0, tZ = 0, firstTDx = 0, firstTDy = 0, firstTDz = 0;
if (bothControls)
{
// ======================================================== Both Controls (Cubic Bezier)
/* coefficients for control points (it's a standard Bezier matrix)
* you can get these numbers by converting parametric equation to standard polinomial form
*
* | 1 0 0 0| |A| | A |
* |-3 3 0 0| * |B| = | -3A + 3B |
* | 3 -6 3 0| |C| | 3A - 6B + 3C |
* |-1 3 -3 1| |D| |-A + 3B - 3C + D|
*
* http://www.wolframalpha.com/input/?i=%7B%7B1,+0,+0,+0%7D,%7B-3,+3,+0,+0%7D,+%7B3,+-6,+3,+0%7D,+%7B-1,+3,+-3,+1%7D%7D*%7B%7Bp_0%7D,+%7Bp_1%7D,+%7Bp_2%7D,+%7Bp_3%7D%7D
*
*/
// the same as in above matrix (top down)
var cx = 3*((double) control1.x - fromPos.x);
var cy = 3*((double) control1.y - fromPos.y);
var cz = 3*((double) control1.z - fromPos.z);
var bx = 3*((double) control2.x - control1.x) - cx;
var by = 3*((double) control2.y - control1.y) - cy;
var bz = 3*((double) control2.z - control1.z) - cz;
var ax = (double) toPos.x - fromPos.x - cx - bx;
var ay = (double) toPos.y - fromPos.y - cy - by;
var az = (double) toPos.z - fromPos.z - cz - bz;
var pointX = (double) fromPos.x;
var pointY = (double) fromPos.y;
var pointZ = (double) fromPos.z;
var axH3 = ax*h3;
var ax6H3 = 6*axH3;
var ayH3 = ay*h3;
var ay6H3 = 6*ayH3;
var azH3 = az*h3;
var az6H3 = 6*azH3;
var bxH2 = bx*h2;
var byH2 = by*h2;
var bzH2 = bz*h2;
/* First Difference
* the difference between t and t+h. Since the curve is cubic, difference is (from Formula i, see header)
* i) (3ah)t^2 + (3ah^2 + 2bh)t + ah^3 + bh^2 + ch
*
* Since we are calculating from the start of the curve, t = 0.
*
* http://www.wolframalpha.com/input/?i=(a*(0+%2B+h)%5E3+%2B+b*(0+%2B+h)%5E2+%2B+c*(0+%2B+h)+%2B+d)+-+(a*0%5E3+%2B+b*0%5E2+%2B+c*0+%2B+d)
* D1 = ah^3 + bh^2 + ch
*/
var firstDx = axH3 + bxH2 + cx*h;
var firstDy = ayH3 + byH2 + cy*h;
var firstDz = azH3 + bzH2 + cz*h;
/*
* Second Difference:
*
* the difference between two successive first differences. Since the form of the first difference is quadratic, the difference
* between two of them is (from Formula ii, see header )
* ii) (2ah)t + ah^2 + bh
*
* Substituting
* t = 0 to calculate the difference at the start of the curve,
* a = 3ah,
* b = 3ah^2 + 2bh (see D1 calculation, Formula i )
* we get
* D2 = (6ah^2)t + 6ah^3 + 2bh^2 http://www.wolframalpha.com/input/?i=%282*3*a*h*h%29t+%2B+3*a*h*h%5E2+%2B+%283*a*h%5E2+%2B+2*b*h%29h
* = 6ah^3 + 2bh^2
*/
var secondDx = ax6H3 + 2*bxH2;
var secondDy = ay6H3 + 2*byH2;
var secondDz = az6H3 + 2*bzH2;
/*
* Third difference:
*
* the difference between two successive second differences. since the form is linear
* iii) ah
* substituting
* a = 6ah^2 (see D2 calculation)
* D3 = 6ah^3 http://www.wolframalpha.com/input/?i=6*a*h%5E2+*+h
*
*/
var thirdDx = ax6H3;
var thirdDy = ay6H3;
var thirdDz = az6H3;
double secondTDx = 0, secondTDy = 0, secondTDz = 0;
if (cacheTangent && !config.UsePointPositionsToCalcTangents)
{
//the same thing as with positions
//parametric to polinomial standard
//3*(1-t)^2*(p1-p0) + 6*(1-t)*t*(p2-p1) + 3*t^2*(p3-p2)=(-3*p0+9*p1-9*p2+3*p3)*t^2 + (6*p0-12*p1+6*p2)*t + (3*p1-3*p0)
var tbx = 6*((double) fromPos.x - 2*control1.x + control2.x);
var tby = 6*((double) fromPos.y - 2*control1.y + control2.y);
var tbz = 6*((double) fromPos.z - 2*control1.z + control2.z);
var tax = 3*((double) -fromPos.x + 3*control1.x - 3*control2.x + toPos.x);
var tay = 3*((double) -fromPos.y + 3*control1.y - 3*control2.y + toPos.y);
var taz = 3*((double) -fromPos.z + 3*control1.z - 3*control2.z + toPos.z);
//temp
var taxH2 = tax*h2;
var tayH2 = tay*h2;
var tazH2 = taz*h2;
// ii) (2ah)t + ah^2 + bh, t=0
firstTDx = taxH2 + tbx*h;
firstTDy = tayH2 + tby*h;
firstTDz = tazH2 + tbz*h;
// iii) = ah, a=2ah
secondTDx = 2*taxH2;
secondTDy = 2*tayH2;
secondTDz = 2*tazH2;
tX = cx;
tY = cy;
tZ = cz;
//normalized inlined
var magnitude = Math.Sqrt(tX*tX + tY*tY + tZ*tZ);
firstPoint.Tangent = magnitude > 9.99999974737875E-06 ? new Vector3((float) (tX/magnitude), (float) (tY/magnitude), (float) (tZ/magnitude)) : Vector3.zero;
}
//-------------------------------- Critical section
for (var i = 1; i < parts; i++)
{
var point = points[i];
pointX += firstDx;
pointY += firstDy;
pointZ += firstDz;
firstDx += secondDx;
firstDy += secondDy;
firstDz += secondDz;
secondDx += thirdDx;
secondDy += thirdDy;
secondDz += thirdDz;
var pos = new Vector3((float) pointX, (float) pointY, (float) pointZ);
if (snapIsOn) curve.ApplySnapping(ref pos);
point.Position = pos;
if (cacheTangent)
{
if (config.UsePointPositionsToCalcTangents)
{
//-------- Calc by point's positions
var prevPoint = section[i - 1];
var prevPosition = prevPoint.Position;
var tangent = new Vector3(pos.x - prevPosition.x, pos.y - prevPosition.y, pos.z - prevPosition.z);
//normalized inlined
var marnitude = (float) Math.Sqrt((double) tangent.x*(double) tangent.x + (double) tangent.y*(double) tangent.y + (double) tangent.z*(double) tangent.z);
tangent = ((double) marnitude > 9.99999974737875E-06) ? new Vector3(tangent.x/marnitude, tangent.y/marnitude, tangent.z/marnitude) : Vector3.zero;
prevPoint.Tangent = point.Tangent = tangent;
}
else
{
tX += firstTDx;
tY += firstTDy;
tZ += firstTDz;
firstTDx += secondTDx;
firstTDy += secondTDy;
firstTDz += secondTDz;
//normalized inlined
var magnitude = Math.Sqrt(tX*tX + tY*tY + tZ*tZ);
point.Tangent = magnitude > 9.99999974737875E-06 ? new Vector3((float) (tX/magnitude), (float) (tY/magnitude), (float) (tZ/magnitude)) : Vector3.zero;
}
}
// ---------- distance to section start (Vector3.Distance inlined)
var prevPos = section[i - 1].Position;
double deltaX = pos.x - prevPos.x;
double deltaY = pos.y - prevPos.y;
double deltaZ = pos.z - prevPos.z;
point.DistanceToSectionStart = section[i - 1].DistanceToSectionStart + ((float) Math.Sqrt(deltaX*deltaX + deltaY*deltaY + deltaZ*deltaZ));
}
//-------------------------------- Critical section ends
}
else
{
// ======================================================== One Control (Quadratic Bezier)
// see comments for cubic bezier for more details
// parametric to polinomial standard form
// (1-t)^2*p0 + 2(1-t)*t*p1+t^2*p2 = (p0-2p1+p2)*t^2 + (-2p0+2p1)*t + p0
var bx = 2*((double) control1.x - fromPos.x);
var by = 2*((double) control1.y - fromPos.y);
var bz = 2*((double) control1.z - fromPos.z);
var ax = (double) fromPos.x - 2*control1.x + toPos.x;
var ay = (double) fromPos.y - 2*control1.y + toPos.y;
var az = (double) fromPos.z - 2*control1.z + toPos.z;
// ii) = (2ah)t + ah^2 + bh, t=0
var firstDx = ax*h2 + bx*h;
var firstDy = ay*h2 + by*h;
var firstDz = az*h2 + bz*h;
// iii) ah, a=2ah
var secondDx = 2*ax*h2;
var secondDy = 2*ay*h2;
var secondDz = 2*az*h2;
var pointX = (double) fromPos.x;
var pointY = (double) fromPos.y;
var pointZ = (double) fromPos.z;
if (cacheTangent && !config.UsePointPositionsToCalcTangents)
{
//the same thing as with positions
//parametric to polinomial standard
// 2*(1-t)*(p1-p0) + 2t*(p2-p1) = (2*p0-4*p1+2*p2)*t + (2*p1-2*p0)
var tax = 2*((double) fromPos.x - 2*control1.x + toPos.x);
var tay = 2*((double) fromPos.y - 2*control1.y + toPos.y);
var taz = 2*((double) fromPos.z - 2*control1.z + toPos.z);
// iii) = ah
firstTDx = tax*h;
firstTDy = tay*h;
firstTDz = taz*h;
tX = 2*((double) control1.x - fromPos.x);
tY = 2*((double) control1.y - fromPos.y);
tZ = 2*((double) control1.z - fromPos.z);
//normalized inlined
var magnitude = Math.Sqrt(tX*tX + tY*tY + tZ*tZ);
firstPoint.Tangent = magnitude > 9.99999974737875E-06 ? new Vector3((float) (tX/magnitude), (float) (tY/magnitude), (float) (tZ/magnitude)) : Vector3.zero;
}
//-------------------------------- Critical section
for (var i = 1; i < parts; i++)
{
var point = points[i];
pointX += firstDx;
pointY += firstDy;
pointZ += firstDz;
firstDx += secondDx;
firstDy += secondDy;
firstDz += secondDz;
var pos = new Vector3((float) pointX, (float) pointY, (float) pointZ);
if (snapIsOn) curve.ApplySnapping(ref pos);
point.Position = pos;
if (cacheTangent)
{
if (config.UsePointPositionsToCalcTangents)
{
//-------- Calc by point's positions
var prevPoint = section[i - 1];
var prevPosition = prevPoint.Position;
var tangent = new Vector3(pos.x - prevPosition.x, pos.y - prevPosition.y, pos.z - prevPosition.z);
//normalized inlined
var marnitude = (float) Math.Sqrt((double) tangent.x*(double) tangent.x + (double) tangent.y*(double) tangent.y + (double) tangent.z*(double) tangent.z);
tangent = ((double) marnitude > 9.99999974737875E-06) ? new Vector3(tangent.x/marnitude, tangent.y/marnitude, tangent.z/marnitude) : Vector3.zero;
prevPoint.Tangent = point.Tangent = tangent;
}
else
{
tX += firstTDx;
tY += firstTDy;
tZ += firstTDz;
//normalized inlined
var magnitude = Math.Sqrt(tX*tX + tY*tY + tZ*tZ);
point.Tangent = magnitude > 9.99999974737875E-06 ? new Vector3((float) (tX/magnitude), (float) (tY/magnitude), (float) (tZ/magnitude)) : Vector3.zero;
}
}
// ---------- distance to section start (Vector3.Distance inlined)
var prevPos = section[i - 1].Position;
double deltaX = pos.x - prevPos.x;
double deltaY = pos.y - prevPos.y;
double deltaZ = pos.z - prevPos.z;
point.DistanceToSectionStart = section[i - 1].DistanceToSectionStart + ((float) Math.Sqrt(deltaX*deltaX + deltaY*deltaY + deltaZ*deltaZ));
}
//-------------------------------- Critical section ends
}
//last point's tangent
if (cacheTangent && !config.UsePointPositionsToCalcTangents)
{
tX += firstTDx;
tY += firstTDy;
tZ += firstTDz;
var magnitude = Math.Sqrt(tX*tX + tY*tY + tZ*tZ);
lastPoint.Tangent = magnitude > 9.99999974737875E-06 ? new Vector3((float) (tX/magnitude), (float) (tY/magnitude), (float) (tZ/magnitude)) : Vector3.zero;
}
}
//last Point's distance
var beforeLastPoint = section[parts - 1];
var beforeLastPos = beforeLastPoint.Position;
var lastPointPos = lastPoint.Position;
double dX = lastPointPos.x - beforeLastPos.x;
double dY = lastPointPos.y - beforeLastPos.y;
double dZ = lastPointPos.z - beforeLastPos.z;
lastPoint.DistanceToSectionStart = beforeLastPoint.DistanceToSectionStart + ((float) Math.Sqrt(dX*dX + dY*dY + dZ*dZ));
//last point's tangent
if (cacheTangent && config.UsePointPositionsToCalcTangents)
{
//-------- Calc by point's positions
var tangent = new Vector3((float) dX, (float) dY, (float) dZ);
//normalized inlined
var marnitude = (float) Math.Sqrt((double) tangent.x*(double) tangent.x + (double) tangent.y*(double) tangent.y + (double) tangent.z*(double) tangent.z);
tangent = ((double) marnitude > 9.99999974737875E-06) ? new Vector3(tangent.x/marnitude, tangent.y/marnitude, tangent.z/marnitude) : Vector3.zero;
lastPoint.Tangent = tangent;
}
}
//search cached data and returns point's position or tangent at given distance from curve's start
// * for example (1000 sections with 100 points each, so 100 000 points) with 10000 Random searches ~7ms
protected virtual void BinarySearchByDistance(float distance, out Vector3 position, out Vector3 tangent, bool calculatePosition, bool calculateTangent)
{
var pointsCount = Curve.PointsCount;
if (pointsCount < 2 || cachedSectionInfos.Count == 0)
{
position = Vector3.zero;
tangent = Vector3.zero;
if (pointsCount == 1 && calculatePosition) position = Curve[0].PositionWorld;
}
// field was not set in the constructor, so it was not calculated and can not be accessed.
// Example, use new BGCurveBaseMath(new BGCurveBaseMath.Params(GetComponent<BGCurve>(), BGCurveBaseMath.Fields.PositionAndTangent))
// to calculate world's position and tangent
if (calculateTangent && ((int) Field.Tangent & (int) config.Fields) == 0)
throw new UnityException("Can not calculate tangent, cause it was not included in the 'fields' constructor parameter. " +
"For example, use new BGCurveBaseMath(curve, new BGCurveBaseMath.Config(" +
"BGCurveBaseMath.Fields.PositionAndTangent))" +
"to calculate world's position and tangent");
var targetSection = cachedSectionInfos[FindSectionIndexByDistance(distance)];
//after we found a section, let's search within this section
targetSection.CalcByDistance(distance - targetSection.DistanceFromStartToOrigin, out position, out tangent, calculatePosition, calculateTangent);
}
//find section by distance
protected int FindSectionIndexByDistance(float distance)
{
// ----- critical section start
int low = 0, mid = 0, high = cachedSectionInfos.Count, i = 0;
while (low < high)
{
mid = (low + high) >> 1;
var item = cachedSectionInfos[mid];
//success
if (distance >= item.DistanceFromStartToOrigin && distance <= item.DistanceFromEndToOrigin) break;
if (distance < item.DistanceFromStartToOrigin) high = mid;
else low = mid + 1;
//just in case
if (i++ > 100) throw new UnityException("Something wrong: more than 100 iterations inside BinarySearch");
}
// ----- critical section end
return mid;
}
//convert ratio to distance
protected float DistanceByRatio(float distanceRatio)
{
return GetDistance()*Mathf.Clamp01(distanceRatio);
}
//ensure distance is within proper range
protected float ClampDistance(float distance)
{
return Mathf.Clamp(distance, 0, GetDistance());
}
public override string ToString()
{
return "Base Math for curve (" + Curve + "), sections=" + SectionsCount;
}
#endregion
#region private methods
//===============================================================================================
// Private functions
//===============================================================================================
protected void Resize(List<SectionPointInfo> points, int size)
{
var pointCount = points.Count;
if (pointCount == size) return;
//size mismatch
if (pointCount < size)
{
//we need more points
var poolIndex = poolPointInfos.Count - 1;
for (var i = pointCount; i < size; i++) points.Add(poolIndex >= 0 ? poolPointInfos[poolIndex--] : new SectionPointInfo());
//pool was used
if (poolIndex != poolPointInfos.Count - 1) poolPointInfos.RemoveRange(poolIndex + 1, poolPointInfos.Count - 1 - poolIndex);
}
else
{
//we need less points
for (var i = size; i < pointCount; i++) poolPointInfos.Add(points[i]);
points.RemoveRange(size, pointCount - size);
}
}
private void CurveChanged(object sender, BGCurveChangedArgs e)
{
ignoreSectionChangedCheck = e != null && e.ChangeType == BGCurveChangedArgs.ChangeTypeEnum.Snap;
Recalculate();
ignoreSectionChangedCheck = false;
}
private void ConfigOnUpdate(object sender, EventArgs eventArgs)
{
Recalculate(true);
}
#endregion
#region Helper model classes
//===============================================================================================
// Helper classes
//===============================================================================================
/// <summary>params for calculations </summary>
public class Config
{
/// <summary>Which fields to calculate and cache. Do not use more fields then needed.</summary>
public Fields Fields = Fields.Position;
/// <summary>number of parts each curve's section will be devided to. Range[1, 1000]</summary>
public int Parts = 30;
/// <summary>Use points position instead of formula to calc tangents. This can increase performace.</summary>
public bool UsePointPositionsToCalcTangents;
/// <summary>Do not split straight lines during precalculation (use only 2 points per section).Tangents may suffer</summary>
public bool OptimizeStraightLines;
/// <summary>If not null it can control if math should update it's data. Updating cached data takes some resources, so this param can control updating strategy </summary>
public Func<bool> ShouldUpdate;
/// <summary>If not null math will update on this event </summary>
public event EventHandler Update;
public Config()
{
}
/// <param name="fields">Fields (from BGCurveBaseMath.Field) you want to precalculate and cache.
/// 'None' means- no precalculation and caching will occur, but as a result you will be able to use CalcPositionByT and CalcTangentByT methods only.
/// See class documentation for more info.</param>
public Config(Fields fields)
{
Fields = fields;
}
protected bool Equals(Config other)
{
return Fields == other.Fields && Parts == other.Parts && UsePointPositionsToCalcTangents == other.UsePointPositionsToCalcTangents &&
OptimizeStraightLines == other.OptimizeStraightLines;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((Config) obj);
}
public override int GetHashCode()
{
var hashCode = (int) Fields;
hashCode = (hashCode*397) ^ Parts;
hashCode = (hashCode*397) ^ UsePointPositionsToCalcTangents.GetHashCode();
hashCode = (hashCode*397) ^ OptimizeStraightLines.GetHashCode();
return hashCode;
}
/// <summary>Fire Updated event</summary>
public void FireUpdate()
{
if (Update != null) Update(this, null);
}
}
/// <summary>information for one single section (between 2 points) of the curve</summary>
public class SectionInfo
{
/// <summary>distance from section start to curve start</summary>
public float DistanceFromStartToOrigin;
/// <summary>distance from section end to curve start </summary>
public float DistanceFromEndToOrigin;
//all the points in this section
protected internal readonly List<SectionPointInfo> points = new List<SectionPointInfo>();
/// <summary>From location, used by calculation</summary>
public Vector3 OriginalFrom;
/// <summary>To location, used by calculation</summary>
public Vector3 OriginalTo;
/// <summary>From control type, used by calculation</summary>
public BGCurvePoint.ControlTypeEnum OriginalFromControlType;
/// <summary>To control type, used by calculation</summary>
public BGCurvePoint.ControlTypeEnum OriginalToControlType;
/// <summary>From control position, used by calculation</summary>
public Vector3 OriginalFromControl;
/// <summary>To control position, used by calculation</summary>
public Vector3 OriginalToControl;
// we need these 2 following fields, cause this section can get skipped while calculation, but adjacent sections may change,
// and this will affect adjacent points tangents
// so we need these "original tangents" to calculate final tangents in case this section will get skipped
/// <summary>First point tangent, calculated for the first time</summary>
public Vector3 OriginalFirstPointTangent;
/// <summary>Last point tangent, calculated for the first time</summary>
public Vector3 OriginalLastPointTangent;
/// <summary>Approximation points in the section</summary>
public List<SectionPointInfo> Points
{
get { return points; }
}
/// <summary>Approximation points count</summary>
public int PointsCount
{
get { return points.Count; }
}
/// <summary>Section's total distance</summary>
public float Distance
{
get { return DistanceFromEndToOrigin - DistanceFromStartToOrigin; }
}
public override string ToString()
{
return "Section distance=(" + Distance + ")";
}
/// <summary>Get section's point by index</summary>
public SectionPointInfo this[int i]
{
get { return points[i]; }
set { points[i] = value; }
}
/// <summary>Reset section's init data and returns true, if (re)calculation is needed</summary>
protected internal bool Reset(BGCurvePointI fromPoint, BGCurvePointI toPoint, int pointsCount, bool skipCheck)
{
var newFrom = fromPoint.PositionWorld;
var newTo = toPoint.PositionWorld;
var newFromControl = fromPoint.ControlSecondWorld;
var newToControl = toPoint.ControlFirstWorld;
const float epsilon = 0.000001f;
if (
!skipCheck
&& points.Count == pointsCount
&& OriginalFromControlType == fromPoint.ControlType
&& OriginalToControlType == toPoint.ControlType
&& Vector3.SqrMagnitude(new Vector3(OriginalFrom.x - newFrom.x, OriginalFrom.y - newFrom.y, OriginalFrom.z - newFrom.z)) < epsilon
&& Vector3.SqrMagnitude(new Vector3(OriginalTo.x - newTo.x, OriginalTo.y - newTo.y, OriginalTo.z - newTo.z)) < epsilon
&& Vector3.SqrMagnitude(new Vector3(OriginalFromControl.x - newFromControl.x, OriginalFromControl.y - newFromControl.y, OriginalFromControl.z - newFromControl.z)) < epsilon
&& Vector3.SqrMagnitude(new Vector3(OriginalToControl.x - newToControl.x, OriginalToControl.y - newToControl.y, OriginalToControl.z - newToControl.z)) < epsilon
)
return false;
OriginalFrom = newFrom;
OriginalTo = newTo;
OriginalFromControlType = fromPoint.ControlType;
OriginalToControlType = toPoint.ControlType;
OriginalFromControl = newFromControl;
OriginalToControl = newToControl;
return true;
}
//copy pasted binary search algorithm
public int FindPointIndexByDistance(float distanceWithinSection)
{
var pointsCountMinusOne = points.Count - 1;
// ----- critical section start (copy paste)
int low = 0, mid = 0, high = points.Count, i = 0;
while (low < high)
{
mid = (low + high) >> 1;
var item = points[mid];
//success
if (!(distanceWithinSection < item.DistanceToSectionStart) && (mid == pointsCountMinusOne || points[mid + 1].DistanceToSectionStart >= distanceWithinSection)) break;
if (distanceWithinSection < item.DistanceToSectionStart) high = mid;
else low = mid + 1;
//just in case
if (i++ > 100) throw new UnityException("Something wrong: more than 100 iterations inside BinarySearch");
}
// ----- critical section end
return mid;
}
/// <summary>Calculates posiiton and distance by distance within this section. Note, it does not apply any checking for performance's sake </summary>
public void CalcByDistance(float distanceWithinSection, out Vector3 position, out Vector3 tangent, bool calculatePosition, bool calculateTangent)
{
position = Vector3.zero;
tangent = Vector3.zero;
if (points.Count == 2)
{
//linear
var first = points[0];
if (Math.Abs(Distance) < BGCurve.Epsilon)
{
if (calculatePosition) position = first.Position;
if (calculateTangent) tangent = first.Tangent;
}
else
{
var ratio = distanceWithinSection/Distance;
var second = points[1];
if (calculatePosition) position = Vector3.Lerp(first.Position, second.Position, ratio);
if (calculateTangent) tangent = Vector3.Lerp(first.Tangent, second.Tangent, ratio);
}
}
else
{
var pointIndex = FindPointIndexByDistance(distanceWithinSection);
var targetPoint = points[pointIndex];
//the very last point within section
if (pointIndex == points.Count - 1)
{
if (calculatePosition) position = targetPoint.Position;
if (calculateTangent) tangent = targetPoint.Tangent;
}
else
{
var nextPoint = points[pointIndex + 1];
//this is the distance between 2 points
var distanceBetweenTwoPoints = (nextPoint.DistanceToSectionStart - targetPoint.DistanceToSectionStart);
//this is smaller distance between 1st point and target distance. distanceWithinSection= the distance from section start to target distance
var distanceWithinTwoPoints = (distanceWithinSection - targetPoint.DistanceToSectionStart);
//zero division check
var ratio = Math.Abs(distanceBetweenTwoPoints) < BGCurve.Epsilon ? 0 : distanceWithinTwoPoints/distanceBetweenTwoPoints;
//lerp target's field between 1st and 2nd points using a ratio, which is based on the distance between them
if (calculatePosition) position = Vector3.Lerp(targetPoint.Position, nextPoint.Position, ratio);
if (calculateTangent) tangent = Vector3.Lerp(targetPoint.Tangent, nextPoint.Tangent, ratio);
}
}
}
}
/// <summary>information for one point within a section (BGCurveBaseMath.Section)</summary>
public class SectionPointInfo
{
/// <summary>point's world position </summary>
public Vector3 Position;
/// <summary>distance from the start of the section to this point</summary>
public float DistanceToSectionStart;
/// <summary>point's world tangent</summary>
public Vector3 Tangent;
//get field's value (position or tangent)
internal Vector3 GetField(Field field)
{
Vector3 result;
switch (field)
{
case Field.Position:
result = Position;
break;
case Field.Tangent:
result = Tangent;
break;
default:
throw new UnityException("Unknown field=" + field);
}
return result;
}
//lerp field's value (position or tangent) between two points by ratio
internal Vector3 LerpTo(Field field, SectionPointInfo to, float ratio)
{
return Vector3.Lerp(GetField(field), to.GetField(field), ratio);
}
public override string ToString()
{
return "Point at (" + Position + ")";
}
}
#endregion
}
#region Helper extensions for Field and Fields enums
//===============================================================================================
// Extensions
//===============================================================================================
//helper extension class for Field enum
public static class FieldExtensions
{
/// <summary> if given fieldEnum is contained in the mask </summary>
public static bool In(this BGCurveBaseMath.Field field, int mask)
{
return (field.Val() & mask) != 0;
}
/// <summary> cast to int </summary>
public static int Val(this BGCurveBaseMath.Field field)
{
return (int) field;
}
}
public static class FieldsExtensions
{
/// <summary> cast to int </summary>
public static int Val(this BGCurveBaseMath.Fields fields)
{
return (int) fields;
}
}
#endregion
}