1
0
mirror of https://github.com/Cardidi/dotween-upm-fork.git synced 2025-12-20 09:16:02 +08:00

PathType.CubicBezier implemented with all DOTween's features (more tests coming)

This commit is contained in:
Demigiant 2019-02-28 15:36:25 +01:00
parent 4d55885166
commit acacc85b29
26 changed files with 210 additions and 59 deletions

View File

@ -996,6 +996,9 @@
<member name="F:DG.Tweening.PathType.CatmullRom">
<summary>Curved path (which uses Catmull-Rom curves)</summary>
</member>
<member name="F:DG.Tweening.PathType.CubicBezier">
<summary><code>EXPERIMENTAL: </code>Curved path (which uses Cubic Bezier curves, where each point requires two extra control points)</summary>
</member>
<member name="T:DG.Tweening.Plugins.Core.PathCore.ControlPoint">
<summary>
Path control point

View File

@ -1,6 +1,6 @@
fileFormatVersion: 2
guid: d3e15b806a8368742ba6f10e794d7b76
timeCreated: 1551216472
timeCreated: 1551364502
licenseType: Pro
TextureImporter:
fileIDToRecycleName: {}

View File

@ -0,0 +1,67 @@
using System.Collections;
using System.Collections.Generic;
using DG.Tweening;
using UnityEngine;
public class PathsBezier : BrainBase
{
public Transform target;
public float duration = 3;
public bool closedPath = false;
public Ease easeType = Ease.Linear;
public bool loop = true;
public LoopType loopType = LoopType.Restart;
public int wpsToUse = 0;
public Vector3[] wps0 = new[] {
new Vector3(1, 1, 0), // wp
new Vector3(0, 0.75f, 0),
new Vector3(0.25f, 1, 0),
new Vector3(2, 0, 0), // wp
new Vector3(1.75f, 1, 0),
new Vector3(2, 0.75f, 0),
new Vector3(1, -1, 0), // wp
new Vector3(2, -0.75f, 0),
new Vector3(1.75f, -1, 0),
};
public Vector3[] wps1 = new[] {
new Vector3(1, 1, 0), // wp
new Vector3(0, 0.75f, 0),
new Vector3(0.25f, 1, 0),
new Vector3(2, 0, 0), // wp
new Vector3(1.75f, 1, 0),
new Vector3(2, 0.75f, 0),
new Vector3(1, -1, 0), // wp
new Vector3(2, -0.75f, 0),
new Vector3(1.75f, -1, 0),
new Vector3(0, 0, 0), // wp
new Vector3(0.25f, -1, 0),
new Vector3(0f, -0.75f, 0)
};
public Vector3[] wps2 = new[] {
new Vector3(0, 0, 0), // wp
new Vector3(0, 0.75f, 0),
new Vector3(0.25f, 1, 0),
new Vector3(1, 1, 0), // wp
new Vector3(0, 0.75f, 0),
new Vector3(0.25f, 1, 0),
new Vector3(2, 0, 0), // wp
new Vector3(1.75f, 1, 0),
new Vector3(2, 0.75f, 0),
new Vector3(1, -1, 0), // wp
new Vector3(2, -0.75f, 0),
new Vector3(1.75f, -1, 0),
new Vector3(0, 0, 0), // wp
new Vector3(0.25f, -1, 0),
new Vector3(0f, -0.75f, 0)
};
IEnumerator Start()
{
yield return new WaitForSeconds(0.5f);
Debug.Log("Creating CubicBezier path");
Vector3[] wps = wpsToUse == 0 ? wps0 : wpsToUse == 1 ? wps1 : wps2;
target.DOPath(wps, duration, PathType.CubicBezier).SetOptions(closedPath).SetLookAt(0.001f)
.SetLoops(loop ? -1 : 1, loopType).SetEase(easeType);
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 4b14f70d99c4aea40be7cc18724a9246
timeCreated: 1551356786
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,4 @@
fileFormatVersion: 2
guid: 474761fd35fdb8a44b5b72b55c6c4aa7
DefaultImporter:
userData:

View File

@ -32,7 +32,7 @@ namespace DG.Tweening
public class DOTween
{
/// <summary>DOTween's version</summary>
public static readonly string Version = "1.2.201"; // Last version before modules: 1.1.755
public static readonly string Version = "1.2.210"; // Last version before modules: 1.1.755
///////////////////////////////////////////////
// Options ////////////////////////////////////

View File

@ -15,7 +15,7 @@ namespace DG.Tweening
Linear,
/// <summary>Curved path (which uses Catmull-Rom curves)</summary>
CatmullRom,
// /// <summary>Curved path (which uses Cubic Bezier curves, where each point requires two extra control points)</summary>
// CubicBezier // Under development
/// <summary><code>EXPERIMENTAL: </code>Curved path (which uses Cubic Bezier curves, where each point requires two extra control points)</summary>
CubicBezier
}
}

View File

@ -27,5 +27,10 @@ namespace DG.Tweening.Plugins.Core.PathCore
{
return new ControlPoint(cp.a + v, cp.b + v);
}
public override string ToString()
{
return "[" + a.ToString() + " | " + b.ToString() + "]";
}
}
}

View File

@ -4,35 +4,84 @@
// This work is subject to the terms at http://dotween.demigiant.com/license.php
using System;
using System.Collections.Generic;
using UnityEngine;
namespace DG.Tweening.Plugins.Core.PathCore
{
internal class CubicBezierDecoder : ABSPathDecoder
{
// wps must be in multiple of 3 (each waypoint has 2 control points), in this order:
// Used for temporary operations
static readonly ControlPoint[] _PartialControlPs = new ControlPoint[1];
static readonly Vector3[] _PartialWps = new Vector3[2];
#region Methods
// Finalize path and separate wps into stripped wps (no control points) and control points
// wps must be in multiple of 3 (each waypoint has 2 control points) plus one starting waypoint without control points, in this order:
// - waypoint
// - IN control point
// - OUT control point
// NOTE: Control points have length of wps - 1 (first wp has no control point)
internal override void FinalizePath(Path p, Vector3[] wps, bool isClosedPath)
{
if (isClosedPath && !p.addedExtraEndWp) isClosedPath = false;
// Normally there's an extra wp without control points for the starting wp added by DOTween,
// but if isClosedPath consider an extra wp without control points at the end
int wpsLen = wps.Length;
if (wpsLen < 6 || wpsLen % 3 != 0) {
Debug.LogError("CubicBezier paths must contain waypoints in multiple of 3 (1: waypoint, 2: IN control point, 3: OUT control point)");
int diff = p.addedExtraStartWp ? 1 : 0;
if (p.addedExtraEndWp) diff++;
if (wpsLen < 3 + diff || (wpsLen - diff) % 3 != 0) {
// Report multiple of 3s error even if we're checking for multiple of 3 + starting point,
// because starting point is assigned by DOTween and not by user
Debug.LogError(
"CubicBezier paths must contain waypoints in multiple of 3 excluding the starting point added automatically by DOTween" +
" (1: waypoint, 2: IN control point, 3: OUT control point — the minimum amount of waypoints for a single curve is 3)"
);
return;
}
int wpsOnlyLen = wpsLen / 3;
// Store control points
p.controlPoints = new ControlPoint[wpsOnlyLen];
// // DEBUG
// for (int i = 0; i < wps.Length; ++i) {
// Debug.Log("WP " + i + " ► " + wps[i]);
// }
// Debug.Log("--------------------------------------");
// // DEBUG END
int wpsOnlyLen = diff + (wpsLen - diff) / 3;
// Store control points and stripped version of wps
Vector3[] strippedWps = new Vector3[wpsOnlyLen];
p.controlPoints = new ControlPoint[wpsOnlyLen - 1]; // Exclude control points for first wp
strippedWps[0] = wps[0];
int strippedWpIndex = 1;
int cpIndex = 0;
for (int i = 0; i < wpsLen; i+=3) {
p.controlPoints[cpIndex] = new ControlPoint(wps[i+1], wps[i+2]);
for (int i = 3 + (p.addedExtraStartWp ? 0 : 2); i < wpsLen; i+=3) {
strippedWps[strippedWpIndex] = wps[i-2];
strippedWpIndex++;
p.controlPoints[cpIndex] = new ControlPoint(wps[i-1], wps[i]);
cpIndex++;
}
p.wps = strippedWps; // Reassign stripped wps to path's wps
// // DEBUG
// for (int i = 0; i < strippedWps.Length; ++i) {
// Debug.Log("WP " + i + " ► " + strippedWps[i]);
// }
// for (int i = 0; i < p.controlPoints.Length; ++i) {
// Debug.Log("CP " + i + " ► " + p.controlPoints[i]);
// }
// // DEBUG END
// Manage closed path
if (isClosedPath) {
// TODO Bezier Closed Path
// Add control points for closed path
Vector3 wpEnd = p.wps[p.wps.Length - 2];
Vector3 wpStart = p.wps[0];
Vector3 cEnd = p.controlPoints[p.controlPoints.Length - 2].b;
Vector3 cStart = p.controlPoints[0].a;
float maxMagnitude = (wpStart - wpEnd).magnitude;
p.controlPoints[p.controlPoints.Length - 1] = new ControlPoint(
wpEnd + Vector3.ClampMagnitude(wpEnd - cEnd, maxMagnitude),
wpStart + Vector3.ClampMagnitude(wpStart - cStart, maxMagnitude)
);
}
// Store total subdivisions
p.subdivisions = wpsOnlyLen * p.subdivisionsXSegment;
@ -40,48 +89,36 @@ namespace DG.Tweening.Plugins.Core.PathCore
SetTimeToLengthTables(p, p.subdivisions);
// Store waypoints lengths
SetWaypointsLengths(p, p.subdivisionsXSegment);
// // Add starting and ending control points (uses only one vector per control point)
// wpsLen = wps.Length;
// if (p.controlPoints == null || p.controlPoints.Length != 2) p.controlPoints = new ControlPoint[2];
// if (isClosedPath) {
// p.controlPoints[0] = new ControlPoint(wps[wpsLen - 2], Vector3.zero);
// p.controlPoints[1] = new ControlPoint(wps[1], Vector3.zero);
// } else {
// p.controlPoints[0] = new ControlPoint(wps[1], Vector3.zero);
// Vector3 lastP = wps[wpsLen - 1];
// Vector3 diffV = lastP - wps[wpsLen - 2];
// p.controlPoints[1] = new ControlPoint(lastP + diffV, Vector3.zero);
// }
// // Store total subdivisions
// p.subdivisions = wpsLen * p.subdivisionsXSegment;
// // Store time to len tables
// SetTimeToLengthTables(p, p.subdivisions);
// // Store waypoints lengths
// SetWaypointsLengths(p, p.subdivisionsXSegment);
}
// controlPoints as a separate parameter so we can pass custom ones from SetWaypointsLengths
// Immense thanks to Vivek Tank's Gamasutra post about Bezier curves whose code I used for this:
// https://www.gamasutra.com/blogs/VivekTank/20180806/323709/How_to_work_with_Bezier_Curve_in_Games_with_Unity.php
internal override Vector3 GetPoint(float perc, Vector3[] wps, Path p, ControlPoint[] controlPoints)
{
int numSections = wps.Length - 1; // Considering also control points
int numSections = wps.Length - 1;
int tSec = (int)Math.Floor(perc * numSections);
int currPt = numSections - 1;
if (currPt > tSec) currPt = tSec;
float u = perc * numSections - currPt;
float t = perc * numSections - currPt;
Vector3 a = currPt == 0 ? controlPoints[0].a : wps[currPt - 1];
Vector3 b = wps[currPt];
Vector3 c = wps[currPt + 1];
Vector3 d = currPt + 2 > wps.Length - 1 ? controlPoints[1].a : wps[currPt + 2];
Vector3 p0 = wps[currPt];
Vector3 p1 = controlPoints[currPt].a;
Vector3 p2 = controlPoints[currPt].b;
Vector3 p3 = wps[currPt + 1];
return .5f * (
(-a + 3f * b - 3f * c + d) * (u * u * u)
+ (2f * a - 5f * b + 4f * c - d) * (u * u)
+ (-a + c) * u
+ 2f * b
);
float u = 1 - t;
float tt = t * t;
float uu = u * u;
float uuu = uu * u;
float ttt = tt * t;
Vector3 result = uuu * p0
+ 3 * uu * t * p1
+ 3 * u * tt * p2
+ ttt * p3;
return result;
}
internal void SetTimeToLengthTables(Path p, int subdivisions)
@ -113,21 +150,20 @@ namespace DG.Tweening.Plugins.Core.PathCore
int count = p.wps.Length;
float[] wpLengths = new float[count];
wpLengths[0] = 0;
ControlPoint[] partialControlPs = new ControlPoint[2];
Vector3[] partialWps = new Vector3[2];
for (int i = 1; i < count; ++i) {
// Create partial path
partialControlPs[0].a = i == 1 ? p.controlPoints[0].a : p.wps[i - 2];
partialWps[0] = p.wps[i - 1];
partialWps[1] = p.wps[i];
partialControlPs[1].a = i == count - 1 ? p.controlPoints[1].a : p.wps[i + 1];
// _PartialControlPs[0].a = i == 1 ? p.controlPoints[0].a : p.wps[i - 2];
_PartialControlPs[0] = p.controlPoints[i - 1];
_PartialWps[0] = p.wps[i - 1];
_PartialWps[1] = p.wps[i];
// _PartialControlPs[1].a = i == count - 1 ? p.controlPoints[1].a : p.wps[i + 1];
// Calculate length of partial path
float partialLen = 0;
float incr = 1f / subdivisions;
Vector3 prevP = GetPoint(0, partialWps, p, partialControlPs);
Vector3 prevP = GetPoint(0, _PartialWps, p, _PartialControlPs);
for (int c = 1; c < subdivisions + 1; ++c) {
float perc = incr * c;
Vector3 currP = GetPoint(perc, partialWps, p, partialControlPs);
Vector3 currP = GetPoint(perc, _PartialWps, p, _PartialControlPs);
partialLen += Vector3.Distance(currP, prevP);
prevP = currP;
}
@ -137,5 +173,7 @@ namespace DG.Tweening.Plugins.Core.PathCore
// Assign
p.wpLengths = wpLengths;
}
#endregion
}
}

View File

@ -19,12 +19,13 @@ namespace DG.Tweening.Plugins.Core.PathCore
// Static decoders stored to avoid creating new ones each time
static CatmullRomDecoder _catmullRomDecoder;
static LinearDecoder _linearDecoder;
static CubicBezierDecoder _cubicBezierDecoder;
public float[] wpLengths; // Unit length of each waypoint (public so it can be accessed at runtime by external scripts)
[SerializeField] internal PathType type;
[SerializeField] internal int subdivisionsXSegment; // Subdivisions x each segment
[SerializeField] internal int subdivisions; // Stored by PathPlugin > total subdivisions for whole path (derived automatically from subdivisionsXSegment)
[SerializeField] internal Vector3[] wps; // Waypoints (modified by PathPlugin when setting relative end value and change value) - also modified by DOTweenPathInspector
[SerializeField] internal Vector3[] wps; // Waypoints (modified by PathPlugin when setting relative end/change value or by CubicBezierDecoder) - also modified by DOTweenPathInspector
[SerializeField] internal ControlPoint[] controlPoints; // Control points used by non-linear paths
[SerializeField] internal float length; // Unit length of the path
[SerializeField] internal bool isFinalized; // TRUE when the path has been finalized (either by starting the tween or if the path was created by the Path Editor)
@ -32,6 +33,7 @@ namespace DG.Tweening.Plugins.Core.PathCore
[SerializeField] internal float[] timesTable; // Connected to lengthsTable, used for constant speed calculations
[SerializeField] internal float[] lengthsTable; // Connected to timesTable, used for constant speed calculations
internal int linearWPIndex = -1; // Waypoint towards which we're moving (only stored for linear paths, when calling GetPoint)
internal bool addedExtraStartWp, addedExtraEndWp;
Path _incrementalClone; // Last incremental clone. Stored in case of incremental loops, to avoid recreating a new path every time
int _incrementalIndex = 0;
@ -268,6 +270,10 @@ namespace DG.Tweening.Plugins.Core.PathCore
if (_linearDecoder == null) _linearDecoder = new LinearDecoder();
_decoder = _linearDecoder;
break;
case PathType.CubicBezier:
if (_cubicBezierDecoder == null) _cubicBezierDecoder = new CubicBezierDecoder();
_decoder = _cubicBezierDecoder;
break;
default: // Catmull-Rom
if (_catmullRomDecoder == null) _catmullRomDecoder = new CatmullRomDecoder();
_decoder = _catmullRomDecoder;

View File

@ -34,6 +34,7 @@ namespace DG.Tweening.Plugins.Options
internal Quaternion startupRot; // Used to reset orientation when rewinding
internal float startupZRot; // Used to store Z value in case of lock Z, in order to rotate things differently
internal bool addedExtraStartWp, addedExtraEndWp;
public void Reset()
{

View File

@ -68,15 +68,25 @@ namespace DG.Tweening.Plugins
int additionalWps = 0;
bool hasAdditionalStartingP = false, hasAdditionalEndingP = false;
// Create final wps and add eventual starting/ending waypoints
// if (path.wps[0] != currVal) {
// Create final wps and add eventual starting/ending waypoints.
if (!Utils.Vector3AreApproximatelyEqual(path.wps[0], currVal)) {
hasAdditionalStartingP = true;
additionalWps += 1;
}
if (t.plugOptions.isClosedPath && path.wps[unmodifiedWpsLen - 1] != currVal) {
hasAdditionalEndingP = true;
additionalWps += 1;
if (t.plugOptions.isClosedPath) {
Vector3 endWp = path.wps[unmodifiedWpsLen - 1];
if (path.type == PathType.CubicBezier) {
if (unmodifiedWpsLen < 3) {
Debug.LogError(
"CubicBezier paths must contain waypoints in multiple of 3 excluding the starting point added automatically by DOTween" +
" (1: waypoint, 2: IN control point, 3: OUT control point — the minimum amount of waypoints for a single curve is 3)"
);
} else endWp = path.wps[unmodifiedWpsLen - 3];
}
if (endWp != currVal) {
hasAdditionalEndingP = true;
additionalWps += 1;
}
}
int wpsLen = unmodifiedWpsLen + additionalWps;
Vector3[] wps = new Vector3[wpsLen];
@ -87,6 +97,8 @@ namespace DG.Tweening.Plugins
path.wps = wps;
// Finalize path
path.addedExtraStartWp = hasAdditionalStartingP;
path.addedExtraEndWp = hasAdditionalEndingP;
path.FinalizePath(t.plugOptions.isClosedPath, t.plugOptions.lockPositionAxis, currVal);
t.plugOptions.startupRot = trans.rotation;

View File

@ -996,6 +996,9 @@
<member name="F:DG.Tweening.PathType.CatmullRom">
<summary>Curved path (which uses Catmull-Rom curves)</summary>
</member>
<member name="F:DG.Tweening.PathType.CubicBezier">
<summary><code>EXPERIMENTAL: </code>Curved path (which uses Cubic Bezier curves, where each point requires two extra control points)</summary>
</member>
<member name="T:DG.Tweening.Plugins.Core.PathCore.ControlPoint">
<summary>
Path control point

Binary file not shown.