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.
 
 
 
 
 
 

421 lines
16 KiB

using System;
using UnityEngine;
namespace BansheeGz.BGSpline.Curve
{
/// <summary>
/// Adaptive curve subdivision math. It split the spline into uneven parts, based on the curvature.
/// Strictly speaking, it still uses uniform splitting, but it can omit and add points when needed, resulting in better approximation and less points.
/// </summary>
public class BGCurveAdaptiveMath : BGCurveBaseMath
{
//------------------------------------------------------------------------
//min tolerance value
public const float MinTolerance = 0.1f;
//max tolerance value
public const float MaxTolerance = 0.9999750f;
//distance tolerance (to avoid spamming points at one single location)
public const float DistanceTolerance = 0.01f;
//max recursion level
private const int RecursionLimit = 24;
//force section to recalculate
private bool ignoreSectionChangedCheckOverride;
//normalized tolerance
private float toleranceRatio;
//normalized tolerance squared
private float toleranceRatioSquared;
//tolerance
private float tolerance;
/// <summary>Create new math with config options </summary>
public BGCurveAdaptiveMath(BGCurve curve, ConfigAdaptive config)
: base(curve, config)
{
}
/// <summary>Init math with new config and recalculate data </summary>
public override void Init(Config config)
{
var configAdaptive = (ConfigAdaptive) config;
tolerance = Mathf.Clamp(configAdaptive.Tolerance, MinTolerance, MaxTolerance);
//pow 4
tolerance *= tolerance;
tolerance *= tolerance;
//normalized
toleranceRatio = 1/(1 - tolerance);
toleranceRatioSquared = toleranceRatio*toleranceRatio;
//if old tolerance is different- we force section recalculation
ignoreSectionChangedCheckOverride = this.config == null || Math.Abs(((ConfigAdaptive) this.config).Tolerance - tolerance) > BGCurve.Epsilon;
//recalculate
base.Init(config);
}
//Reset pooled section with new data and recalculates if needed
protected override bool Reset(SectionInfo section, BGCurvePointI @from, BGCurvePointI to, int pointsCount)
{
return section.Reset(@from, to, section.PointsCount, ignoreSectionChangedCheck || ignoreSectionChangedCheckOverride);
}
protected override bool IsUseDistanceToAdjustTangents(SectionInfo section, SectionInfo prevSection)
{
return true;
}
//approximate split section
//this method contains some intentional copy/paste
protected override void CalculateSplitSection(SectionInfo section, BGCurvePointI @from, BGCurvePointI to)
{
//if set to true, tangent formula will be used to calc tangents
var calcTangents = cacheTangent && !config.UsePointPositionsToCalcTangents;
var points = section.points;
var count = points.Count;
//move all existing points to the pool (the number of points depends on the curvature)
for (var i = 0; i < count; i++) poolPointInfos.Add(points[i]);
points.Clear();
//original points and control types
var p0 = section.OriginalFrom;
var p1 = section.OriginalFromControl;
var p2 = section.OriginalToControl;
var p3 = section.OriginalTo;
//type of the curve (0-absent,3-both Bezier, 1 or 2 - one of the points has Bezier controls)
var type = (section.OriginalFromControlType != BGCurvePoint.ControlTypeEnum.Absent ? 2 : 0) + (section.OriginalToControlType != BGCurvePoint.ControlTypeEnum.Absent ? 1 : 0);
//===================================== first point
SectionPointInfo firstPoint;
var poolCursor = poolPointInfos.Count - 1;
if (poolCursor >= 0)
{
firstPoint = poolPointInfos[poolCursor];
poolPointInfos.RemoveAt(poolCursor);
}
else firstPoint = new SectionPointInfo();
firstPoint.Position = p0;
firstPoint.DistanceToSectionStart = 0;
points.Add(firstPoint);
//==================================== split recursively
switch (type)
{
case 3:
RecursiveCubicSplit(section, p0.x, p0.y, p0.z, p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, p3.x, p3.y, p3.z, 0, calcTangents, 0, 1);
break;
case 2:
RecursiveQuadraticSplit(section, p0.x, p0.y, p0.z, p1.x, p1.y, p1.z, p3.x, p3.y, p3.z, 0, false, calcTangents, 0, 1);
break;
case 1:
RecursiveQuadraticSplit(section, p0.x, p0.y, p0.z, p2.x, p2.y, p2.z, p3.x, p3.y, p3.z, 0, true, calcTangents, 0, 1);
break;
}
//===================================== last point
SectionPointInfo lastPoint;
poolCursor = poolPointInfos.Count - 1;
if (poolCursor >= 0)
{
lastPoint = poolPointInfos[poolCursor];
poolPointInfos.RemoveAt(poolCursor);
}
else lastPoint = new SectionPointInfo();
lastPoint.Position = p3;
points.Add(lastPoint);
//calculate distances (and optionally tangents)
//if set to true, points positions will be used to calculate tangents
calcTangents = cacheTangent && config.UsePointPositionsToCalcTangents;
var prevPoint = points[0];
for (var i = 1; i < points.Count; i++)
{
var point = points[i];
var pos = point.Position;
var prevPos = prevPoint.Position;
var dX = (double) pos.x - prevPos.x;
var dY = (double) pos.y - prevPos.y;
var dZ = (double) pos.z - prevPos.z;
//distance
point.DistanceToSectionStart = prevPoint.DistanceToSectionStart + ((float) Math.Sqrt(dX*dX + dY*dY + dZ*dZ));
//tangents
if (calcTangents) point.Tangent = Vector3.Normalize(pos - prevPos);
prevPoint = point;
}
//set first and last points tangents (cause they was omitted during recursive split)
if (cacheTangent)
{
if (config.UsePointPositionsToCalcTangents)
{
firstPoint.Tangent = (points[1].Position - firstPoint.Position).normalized;
lastPoint.Tangent = points[points.Count - 2].Tangent;
}
else
{
switch (type)
{
case 0:
firstPoint.Tangent = lastPoint.Tangent = (lastPoint.Position - firstPoint.Position).normalized;
break;
case 1:
firstPoint.Tangent = Vector3.Normalize(section.OriginalToControl - section.OriginalFrom);
lastPoint.Tangent = Vector3.Normalize(section.OriginalTo - section.OriginalToControl);
break;
case 2:
firstPoint.Tangent = Vector3.Normalize(section.OriginalFromControl - section.OriginalFrom);
lastPoint.Tangent = Vector3.Normalize(section.OriginalTo - section.OriginalFromControl);
break;
case 3:
firstPoint.Tangent = Vector3.Normalize(section.OriginalFromControl - section.OriginalFrom);
lastPoint.Tangent = Vector3.Normalize(section.OriginalTo - section.OriginalToControl);
break;
}
}
}
}
//------------------------------------------------------------------------ Quadratic
//this method contains some intentional copy/paste
private void RecursiveQuadraticSplit(SectionInfo section, double x0, double y0, double z0, double x1, double y1, double z1, double x2, double y2, double z2, int level, bool useSecond,
bool calcTangents, double fromT, double toT)
{
if (level > RecursionLimit) return;
// is curve flat
// http://www.malinc.se/m/DeCasteljauAndBezier.php
// sqr(b) + sqr(c) < sqr(a) * tolerance
var dx01 = x0 - x1;
var dy01 = y0 - y1;
var dz01 = z0 - z1;
var dx12 = x1 - x2;
var dy12 = y1 - y2;
var dz12 = z1 - z2;
var dx02 = x0 - x2;
var dy02 = y0 - y2;
var dz02 = z0 - z2;
var a = dx02*dx02 + dy02*dy02 + dz02*dz02;
var b = dx01*dx01 + dy01*dy01 + dz01*dz01;
var c = dx12*dx12 + dy12*dy12 + dz12*dz12;
var temp = a*toleranceRatioSquared - b - c;
if (4*b*c < temp*temp || a + b + c < DistanceTolerance) return;
//at this point we know, the curve is not flat
// split (Casteljau algorithm)
var x01 = (x0 + x1)*.5;
var y01 = (y0 + y1)*.5;
var z01 = (z0 + z1)*.5;
var x12 = (x1 + x2)*.5;
var y12 = (y1 + y2)*.5;
var z12 = (z1 + z2)*.5;
var x012 = (x01 + x12)*.5;
var y012 = (y01 + y12)*.5;
var z012 = (z01 + z12)*.5;
var t = calcTangents ? (fromT + toT)*.5 : .0;
var pos = new Vector3((float) x012, (float) y012, (float) z012);
//apply snapping
if (curve.SnapType == BGCurve.SnapTypeEnum.Curve) curve.ApplySnapping(ref pos);
//split first section
RecursiveQuadraticSplit(section, x0, y0, z0, x01, y01, z01, x012, y012, z012, level + 1, useSecond, calcTangents, fromT, t);
//add point
SectionPointInfo pointInfo;
var poolCursor = poolPointInfos.Count - 1;
if (poolCursor >= 0)
{
pointInfo = poolPointInfos[poolCursor];
poolPointInfos.RemoveAt(poolCursor);
}
else pointInfo = new SectionPointInfo();
pointInfo.Position = pos;
section.points.Add(pointInfo);
//tangents
if (calcTangents)
{
var control = useSecond ? section.OriginalToControl : section.OriginalFromControl;
//idea.. optimize it
pointInfo.Tangent = Vector3.Normalize(2*(1 - (float) t)*(control - section.OriginalFrom) + 2*(float) t*(section.OriginalTo - control));
}
//split second section
RecursiveQuadraticSplit(section, x012, y012, z012, x12, y12, z12, x2, y2, z2, level + 1, useSecond, calcTangents, t, toT);
}
//------------------------------------------------------------------------ Cubic
//this method contains some intentional copy/paste
private void RecursiveCubicSplit(SectionInfo section, double x0, double y0, double z0, double x1, double y1, double z1, double x2, double y2, double z2, double x3, double y3, double z3,
int level, bool calcTangents, double fromT, double toT)
{
if (level > RecursionLimit) return;
// is curve flat
// http://www.malinc.se/m/DeCasteljauAndBezier.php
// sqr(b) + sqr(c) + sqr(d) < sqr(a) * tolerance
var dx01 = x0 - x1;
var dy01 = y0 - y1;
var dz01 = z0 - z1;
var dx12 = x1 - x2;
var dy12 = y1 - y2;
var dz12 = z1 - z2;
var dx23 = x2 - x3;
var dy23 = y2 - y3;
var dz23 = z2 - z3;
var dx03 = x0 - x3;
var dy03 = y0 - y3;
var dz03 = z0 - z3;
var a = dx03*dx03 + dy03*dy03 + dz03*dz03;
var b = dx01*dx01 + dy01*dy01 + dz01*dz01;
var c = dx12*dx12 + dy12*dy12 + dz12*dz12;
var d = dx23*dx23 + dy23*dy23 + dz23*dz23;
//idea.. optimize sqrt. Sqrt is expensive call, and 3 Sqrt calls is super expensive to say the least
if (Math.Sqrt(b*c) + Math.Sqrt(c*d) + Math.Sqrt(b*d) < (a*toleranceRatioSquared - b - c - d)*.5 || a + b + c + d < DistanceTolerance) return;
//at this point we know, the curve is not flat
//split (Casteljau algorithm)
var x01 = (x0 + x1)*.5;
var y01 = (y0 + y1)*.5;
var z01 = (z0 + z1)*.5;
var x12 = (x1 + x2)*.5;
var y12 = (y1 + y2)*.5;
var z12 = (z1 + z2)*.5;
var x23 = (x2 + x3)*.5;
var y23 = (y2 + y3)*.5;
var z23 = (z2 + z3)*.5;
var x012 = (x01 + x12)*.5;
var y012 = (y01 + y12)*.5;
var z012 = (z01 + z12)*.5;
var x123 = (x12 + x23)*.5;
var y123 = (y12 + y23)*.5;
var z123 = (z12 + z23)*.5;
var x0123 = (x012 + x123)*.5;
var y0123 = (y012 + y123)*.5;
var z0123 = (z012 + z123)*.5;
var t = calcTangents ? (fromT + toT)*.5 : .0;
var pos = new Vector3((float) x0123, (float) y0123, (float) z0123);
if (curve.SnapType == BGCurve.SnapTypeEnum.Curve) curve.ApplySnapping(ref pos);
//split first section
RecursiveCubicSplit(section, x0, y0, z0, x01, y01, z01, x012, y012, z012, x0123, y0123, z0123, level + 1, calcTangents, fromT, t);
//add point
SectionPointInfo pointInfo;
var poolCursor = poolPointInfos.Count - 1;
if (poolCursor >= 0)
{
pointInfo = poolPointInfos[poolCursor];
poolPointInfos.RemoveAt(poolCursor);
}
else pointInfo = new SectionPointInfo();
pointInfo.Position = pos;
section.points.Add(pointInfo);
//tangent
if (calcTangents)
{
var tr = 1 - t;
//idea.. optimize it
pointInfo.Tangent = Vector3.Normalize(3*((float) (tr*tr))*(section.OriginalFromControl - section.OriginalFrom) +
6*(float) (tr*t)*(section.OriginalToControl - section.OriginalFromControl) +
3*(float) (t*t)*(section.OriginalTo - section.OriginalToControl));
}
//split second section
RecursiveCubicSplit(section, x0123, y0123, z0123, x123, y123, z123, x23, y23, z23, x3, y3, z3, level + 1, calcTangents, t, toT);
}
/// <summary>Configuration options for Adaptive Math </summary>
public class ConfigAdaptive : Config
{
/// <summary>
/// This parameter is used to calculate if curve's flat and no more splitting is required
/// Note: The final tolerance parameter, used by Math, is based on this value, but differs
/// </summary>
public float Tolerance = .2f;
/// <summary>Construct new config</summary>
public ConfigAdaptive(Fields fields) : base(fields)
{
}
}
public override string ToString()
{
return "Adaptive Math for curve (" + Curve + "), sections=" + SectionsCount;
}
}
/*
Roger Willcocks flat criteria for quadratic (something wrong here)
var ax = 2*x1 - x2 - x0;
var ay = 2*y1 - y2 - y0;
var az = 2*z1 - z2 - z0;
ax *= ax;
ay *= ay;
az *= az;
if (ax + ay + az < tolerance) return;
*/
/*
Roger Willcocks flat criteria for cubic (does not work well at all)- maybe a bbug or something- have no idea
var ax = 3*x1 - 2*x0 - x3;
var ay = 3*y1 - 2*y0 - y3;
var az = 3*z1 - 2*z0 - z3;
ax *= ax;
ay *= ay;
az *= az;
var bx = 3*x2 - x0 - 2*x3;
var by = 3*y2 - y0 - 2*y3;
var bz = 3*z2 - z0 - 2*z3;
bx *= bx;
by *= by;
bz *= bz;
if (Math.Max(ax, bx) + Math.Max(ay, by) + Math.Max(az, bz) < tolerance) return;
*/
/*
// For Willckock's criteria: 16 and exponenta originate from the flatness calculation
tolerance *= tolerance*16;
*/
}