#if BURST_PRESENT
using Unity.Burst;
#endif
using Unity.Collections;
using Unity.Mathematics;
namespace UnityEngine.XR.Interaction.Toolkit.Utilities.Curves
{
///
/// A static class that provides utility methods for working with curves.
/// All functions are compiled with the Burst compiler if the Burst package is present.
///
#if BURST_PRESENT
[BurstCompile]
#endif
static class CurveUtility
{
///
/// Pre-multiplied 8x applied to the Unity mathematics float epsilon value, used for float approximate equality comparisons.
///
const float k_EightEpsilon = math.EPSILON * 8f;
///
/// Samples a point on a quadratic Bezier curve defined by three control points and a parameter t.
///
/// The first control point.
/// The second control point.
/// The third control point.
/// The parameter t, ranging from 0 to 1.
/// The output point on the curve.
#if BURST_PRESENT
[BurstCompile]
#endif
public static void SampleQuadraticBezierPoint(in float3 p0, in float3 p1, in float3 p2, float t, out float3 point)
{
var u = 1f - t; // (1 - t)
var uu = u * u; // (1 - t)²
var tt = t * t; // t²
// (1 - t)²P₀ + 2(1 - t)tP₁ + t²P₂ where 0 ≤ t ≤ 1
// u²P₀ + 2utP₁ + t²P₂
point = (uu * p0) +
(2f * u * t * p1) +
(tt * p2);
}
///
/// Samples a point on a cubic Bezier curve defined by four control points and a parameter t.
///
/// The first control point.
/// The second control point.
/// The third control point.
/// The fourth control point.
/// The parameter t, ranging from 0 to 1.
/// The output point on the curve.
#if BURST_PRESENT
[BurstCompile]
#endif
public static void SampleCubicBezierPoint(in float3 p0, in float3 p1, in float3 p2, in float3 p3, float t, out float3 point)
{
var u = 1f - t; // (1 - t)
var uu = u * u; // (1 - t)²
var uuu = uu * u; // (1 - t)³
var tt = t * t; // t²
var ttt = tt * t; // t³
// (1 - t)³P₀ + 3(1 - t)²tP₁ + 3(1 - t)t²P₂ + t³P₃ where 0 ≤ t ≤ 1
// u³P₀ + 3u²tP₁ + 3ut²P₂ + t³P₃
point = (uuu * p0) +
(3f * uu * t * p1) +
(3f * u * tt * p2) +
(ttt * p3);
}
///
/// Elevates a quadratic Bezier curve to a cubic Bezier curve by adding an extra control point.
///
/// The first control point of the quadratic curve.
/// The second control point of the quadratic curve.
/// The third control point of the quadratic curve.
/// The first control point of the cubic curve. (output)
/// The second control point of the cubic curve. (output)
/// The third control point of the cubic curve. (output)
/// The fourth control point of the cubic curve. (output)
#if BURST_PRESENT
[BurstCompile]
#endif
public static void ElevateQuadraticToCubicBezier(in float3 p0, in float3 p1, in float3 p2, out float3 c0, out float3 c1, out float3 c2, out float3 c3)
{
// A Bezier curve of one degree can be reproduced by one of higher degree.
// Convert quadratic Bezier curve with control points P₀, P₁, P₂
// into a cubic Bezier curve with control points C₀, C₁, C₂, C₃.
// The end points remain the same.
c0 = p0;
c1 = p0 + (2f / 3f) * (p1 - p0);
c2 = p2 + (2f / 3f) * (p1 - p2);
c3 = p2;
}
///
/// Generates a cubic Bezier curve from a given line segment and a curve ratio.
///
/// The number of points to generate for the curve.
/// The ratio of the line length to use as the distance from the midpoint to the control point.
/// The starting point of the line segment.
/// The normalized forward direction vector of the line segment.
/// The ending point of the line segment.
/// A reference to a native array of that will store the generated curve points.
#if UNITY_2022_2_OR_NEWER && BURST_PRESENT
[BurstCompile]
#endif
public static void GenerateCubicBezierCurve(int numTargetPoints, float curveRatio, in float3 lineOrigin, in float3 lineDirection, in float3 endPoint, ref NativeArray targetPoints)
{
var lineLength = math.length(endPoint - lineOrigin);
var adjustedMidPoint = lineOrigin + lineDirection * lineLength * curveRatio;
ElevateQuadraticToCubicBezier(lineOrigin, adjustedMidPoint, endPoint,
out var p0, out var p1, out var p2, out var p3);
// Set first point
targetPoints[0] = lineOrigin;
var interval = 1f / (numTargetPoints - 1);
for (var i = 1; i < numTargetPoints; ++i)
{
var percent = i * interval;
SampleCubicBezierPoint(p0, p1, p2, p3, percent, out var newPoint);
targetPoints[i] = newPoint;
}
}
///
/// Generates a cubic Bezier curve from a given curve segment and a curve ratio.
///
/// The number of points to generate for the curve.
/// The ratio of the curve length to use as the distance from the midpoint to the control point.
/// Fixed offset to omit from the start of the curve.
/// Fixed offset to omit from the end of the curve.
/// The starting point of the curve segment.
/// The normalized forward direction vector of the curve segment.
/// The ending point of the curve segment.
/// A reference to a native array of that will store the generated curve points.
/// Minimum line length that will be computed before the function returns false.
/// True if the cubic Bezier curve was successfully generated, false otherwise.
#if UNITY_2022_2_OR_NEWER && BURST_PRESENT
[BurstCompile]
#endif
public static bool TryGenerateCubicBezierCurve(int numTargetPoints, float curveRatio, in float3 curveOrigin, in float3 curveDirection, in float3 endPoint, ref NativeArray targetPoints, float minLineLength = 0.005f, float startOffset = 0f, float endOffset = 0f)
{
var lineLength = math.length(endPoint - curveOrigin);
var combinedOffset = startOffset + endOffset;
// The offsets exceed the length of the line
if (combinedOffset > lineLength || lineLength - combinedOffset < minLineLength)
return false;
float3 adjustedMidPoint;
if (curveRatio > 0)
adjustedMidPoint = curveOrigin + curveDirection * lineLength * curveRatio;
else
adjustedMidPoint = math.lerp(curveOrigin, endPoint, 0.5f);
return TryGenerateCubicBezierCurveCore(numTargetPoints, curveOrigin, adjustedMidPoint, endPoint, ref targetPoints, minLineLength, startOffset, endOffset);
}
///
/// Generates a cubic Bezier curve from a given curve segment and a curve ratio.
///
/// The number of points to generate for the curve.
/// Fixed offset to omit from the start of the curve.
/// Fixed offset to omit from the end of the curve.
/// The starting point of the curve segment.
/// The midpoint the cubic bezier curve.
/// The ending point of the curve segment.
/// A reference to a native array of that will store the generated curve points.
/// Minimum line length that will be computed before the function returns false.
/// True if the cubic Bezier curve was successfully generated, false otherwise.
#if UNITY_2022_2_OR_NEWER && BURST_PRESENT
[BurstCompile]
#endif
public static bool TryGenerateCubicBezierCurve(int numTargetPoints, in float3 curveOrigin, in float3 midPoint, in float3 endPoint, ref NativeArray targetPoints, float minLineLength = 0.005f, float startOffset = 0f, float endOffset = 0f)
{
var lineLength = math.length(endPoint - curveOrigin);
var combinedOffset = startOffset + endOffset;
// The offsets exceed the length of the line
if (combinedOffset > lineLength || lineLength - combinedOffset < minLineLength)
return false;
return TryGenerateCubicBezierCurveCore(numTargetPoints, curveOrigin, midPoint, endPoint, ref targetPoints, minLineLength, startOffset, endOffset);
}
static bool TryGenerateCubicBezierCurveCore(int numTargetPoints, in float3 curveOrigin, in float3 midPoint, in float3 endPoint, ref NativeArray targetPoints, float minLineLength = 0.005f, float startOffset = 0f, float endOffset = 0f)
{
ElevateQuadraticToCubicBezier(curveOrigin, midPoint, endPoint,
out var p0, out var p1, out var p2, out var p3);
bool hasStartOffset = startOffset > 0;
bool hasEndOffset = endOffset > 0;
float tStart = 0f;
float tEnd = 1f;
if (hasStartOffset || hasEndOffset)
{
var combinedOffset = startOffset + endOffset;
float totalCurveLength = ApproximateCubicBezierLength(p0, p1, p2, p3, math.max(numTargetPoints / 2, 4));
if (combinedOffset > totalCurveLength || totalCurveLength - (combinedOffset) < minLineLength)
return false;
if (hasStartOffset)
tStart = startOffset / totalCurveLength;
if (hasEndOffset)
tEnd = (totalCurveLength - endOffset) / totalCurveLength;
}
var interval = (tEnd - tStart) / (numTargetPoints - 1);
for (var i = 0; i < numTargetPoints; ++i)
{
var percent = tStart + i * interval;
SampleCubicBezierPoint(p0, p1, p2, p3, percent, out var newPoint);
targetPoints[i] = newPoint;
}
return true;
}
///
/// Calculates an approximate length of a cubic Bezier curve.
///
/// The starting point of the Bezier curve.
/// The first control point influencing the curve's shape.
/// The second control point influencing the curve's shape.
/// The ending point of the Bezier curve.
/// The number of subdivisions used for approximation. Higher values increase accuracy.
/// The approximate length of the cubic Bezier curve.
///
/// This method approximates the length of a cubic Bezier curve by subdividing it into smaller segments
/// and summing the lengths of these linear segments. The accuracy of the approximation increases
/// with the number of subdivisions. The method uses linear interpolation to find points along the curve at regular intervals.
///
#if BURST_PRESENT
[BurstCompile]
#endif
public static float ApproximateCubicBezierLength(in float3 p0, in float3 p1, in float3 p2, in float3 p3, int subdivisions)
{
float length = 0f;
float3 previousPoint = p0;
for (int i = 1; i <= subdivisions; i++)
{
float t = (float)i / subdivisions;
SampleCubicBezierPoint(p0, p1, p2, p3, t, out float3 currentPoint);
length += math.distance(currentPoint, previousPoint);
previousPoint = currentPoint;
}
return length;
}
///
/// Calculates the position of a projectile at a given time using constant acceleration formula.
///
/// The initial position vector of the projectile.
/// The initial velocity vector of the projectile.
/// The constant acceleration vector of the projectile, typically (0, -9.8, 0).
/// The time at which to calculate the position.
/// The output point on the curve.
#if BURST_PRESENT
[BurstCompile]
#endif
public static void SampleProjectilePoint(in float3 initialPosition, in float3 initialVelocity, in float3 constantAcceleration, float time, out float3 point)
{
// Position of object in constant acceleration is:
// x(t) = x₀ + v₀t + 0.5at²
// where x₀ is the position at time 0,
// v₀ is the velocity vector at time 0,
// a is the constant acceleration vector
point = initialPosition + initialVelocity * time + constantAcceleration * (0.5f * time * time);
}
///
/// Calculates the time of flight for a projectile launched at a given angle and initial velocity.
///
/// The magnitude of the initial velocity vector.
/// The constant acceleration due to gravity (typically 9.8).
/// The launch angle in radians.
/// The initial height of the projectile.
/// An additional time to add to the flight time.
/// The output parameter for the calculated flight time.
#if BURST_PRESENT
[BurstCompile]
#endif
public static void CalculateProjectileFlightTime(float velocityMagnitude, float gravityAcceleration, float angleRad, float height, float extraFlightTime, out float flightTime)
{
// Vertical velocity component Vy = v₀sinθ
// When initial height = 0,
// Time of flight = 2(initial velocity)(sine of launch angle) / (acceleration) = 2v₀sinθ/g
// When initial height > 0,
// Time of flight = [Vy + √(Vy² + 2gh)] / g
// The additional flight time property is added.
var vy = velocityMagnitude * angleRad;
if (height < 0f)
flightTime = 0f;
// Does the same math roughly as Mathf.Approximately(height, 0f)
else if (math.abs(height) < k_EightEpsilon)
flightTime = 2f * vy / gravityAcceleration;
else
flightTime = (vy + math.sqrt(vy * vy + 2f * gravityAcceleration * height)) / gravityAcceleration;
flightTime = math.max(flightTime + extraFlightTime, 0f);
}
}
}