Hey, I made this small demo to test out an idea for my game. Thought I would share it. I plan on posting the code on the wiki once I add some more flexibility to it http://bronx.zionmax.com/unity/bezier/
Hi, the link given there has expired so i request u to pls upload the latest and make it accessible. thanks in advance
If it can help... Code (csharp): class Bezier extends System.Object { var p0 : Vector3; var p1 : Vector3; var p2 : Vector3; var p3 : Vector3; var ti : float = 0.0; private var b0 : Vector3 = Vector3.zero; private var b1 : Vector3 = Vector3.zero; private var b2 : Vector3 = Vector3.zero; private var b3 : Vector3 = Vector3.zero; private var Ax : float; private var Ay : float; private var Az : float; private var Bx : float; private var By : float; private var Bz : float; private var Cx : float; private var Cy : float; private var Cz : float; // Init function v0 = 1st point, v1 = handle of the 1st point , v2 = handle of the 2nd point, v3 = 2nd point // handle1 = v0 + v1 // handle2 = v3 + v2 function Bezier(v0 : Vector3, v1 : Vector3, v2 : Vector3, v3 : Vector3) { this.p0 = v0; this.p1 = v1; this.p2 = v2; this.p3 = v3; } // 0.0 >= t <= 1.0 function GetPointAtTime(t : float) : Vector3 { this.CheckConstant(); var t2 : float = t * t; var t3 : float = t * t * t; var x : float = this.Ax * t3 + this.Bx * t2 + this.Cx * t + p0.x; var y : float = this.Ay * t3 + this.By * t2 + this.Cy * t + p0.y; var z : float = this.Az * t3 + this.Bz * t2 + this.Cz * t + p0.z; return(Vector3(x,y,z)); } private function SetConstant() { this.Cx = 3 * ((this.p0.x + this.p1.x) - this.p0.x); this.Bx = 3 * ((this.p3.x + this.p2.x) - (this.p0.x + this.p1.x)) - this.Cx; this.Ax = this.p3.x - this.p0.x - this.Cx - this.Bx; this.Cy = 3 * ((this.p0.y + this.p1.y) - this.p0.y); this.By = 3 * ((this.p3.y + this.p2.y) - (this.p0.y + this.p1.y)) - this.Cy; this.Ay = this.p3.y - this.p0.y - this.Cy - this.By; this.Cz = 3 * ((this.p0.z + this.p1.z) - this.p0.z); this.Bz = 3 * ((this.p3.z + this.p2.z) - (this.p0.z + this.p1.z)) - this.Cz; this.Az = this.p3.z - this.p0.z - this.Cz - this.Bz; } // Check if p0, p1, p2 or p3 have changed private function CheckConstant() { if (this.p0 != this.b0 || this.p1 != this.b1 || this.p2 != this.b2 || this.p3 != this.b3) { this.SetConstant(); this.b0 = this.p0; this.b1 = this.p1; this.b2 = this.p2; this.b3 = this.p3; } } } Usage: Code (csharp): var myBezier : Bezier; private var t : float = 0.0; function Start() { myBezier = new Bezier(Vector3(-5,0,0), Random.insideUnitSphere * 2.0, Random.insideUnitSphere * 2.0, Vector3(5,0,0)); } function Update () { var vec : Vector3 = myBezier.GetPointAtTime(t); transform.position = vec; t+= 0.001; if (t > 1.0) t = 0.0; }
hi i like to thank u again for that help. I hav been able to complete that task of a rope very fin. Its an cool and proper Rope that i was able to make. thanks a lot...
So I was looking for a unity bezier curve implementation and found this thread! Loran, your code awesome in every aspect, except it is not C#. So I ported it for myself and for the lazy: Bezier main class in Bezier.cs: Code (csharp): using UnityEngine; [System.Serializable] public class Bezier : System.Object { public Vector3 p0; public Vector3 p1; public Vector3 p2; public Vector3 p3; public float ti = 0f; private Vector3 b0 = Vector3.zero; private Vector3 b1 = Vector3.zero; private Vector3 b2 = Vector3.zero; private Vector3 b3 = Vector3.zero; private float Ax; private float Ay; private float Az; private float Bx; private float By; private float Bz; private float Cx; private float Cy; private float Cz; // Init function v0 = 1st point, v1 = handle of the 1st point , v2 = handle of the 2nd point, v3 = 2nd point // handle1 = v0 + v1 // handle2 = v3 + v2 public Bezier( Vector3 v0, Vector3 v1, Vector3 v2, Vector3 v3 ) { this.p0 = v0; this.p1 = v1; this.p2 = v2; this.p3 = v3; } // 0.0 >= t <= 1.0 public Vector3 GetPointAtTime( float t ) { this.CheckConstant(); float t2 = t * t; float t3 = t * t * t; float x = this.Ax * t3 + this.Bx * t2 + this.Cx * t + p0.x; float y = this.Ay * t3 + this.By * t2 + this.Cy * t + p0.y; float z = this.Az * t3 + this.Bz * t2 + this.Cz * t + p0.z; return new Vector3( x, y, z ); } private void SetConstant() { this.Cx = 3f * ( ( this.p0.x + this.p1.x ) - this.p0.x ); this.Bx = 3f * ( ( this.p3.x + this.p2.x ) - ( this.p0.x + this.p1.x ) ) - this.Cx; this.Ax = this.p3.x - this.p0.x - this.Cx - this.Bx; this.Cy = 3f * ( ( this.p0.y + this.p1.y ) - this.p0.y ); this.By = 3f * ( ( this.p3.y + this.p2.y ) - ( this.p0.y + this.p1.y ) ) - this.Cy; this.Ay = this.p3.y - this.p0.y - this.Cy - this.By; this.Cz = 3f * ( ( this.p0.z + this.p1.z ) - this.p0.z ); this.Bz = 3f * ( ( this.p3.z + this.p2.z ) - ( this.p0.z + this.p1.z ) ) - this.Cz; this.Az = this.p3.z - this.p0.z - this.Cz - this.Bz; } // Check if p0, p1, p2 or p3 have changed private void CheckConstant() { if( this.p0 != this.b0 || this.p1 != this.b1 || this.p2 != this.b2 || this.p3 != this.b3 ) { this.SetConstant(); this.b0 = this.p0; this.b1 = this.p1; this.b2 = this.p2; this.b3 = this.p3; } } } And an example usage in MyBezier.cs: Code (csharp): using UnityEngine; public class MyBezier : MonoBehaviour { public Bezier myBezier; private float t = 0f; void Start() { myBezier = new Bezier( new Vector3( -5f, 0f, 0f ), Random.insideUnitSphere * 2f, Random.insideUnitSphere * 2f, new Vector3( 5f, 0f, 0f ) ); } void Update() { Vector3 vec = myBezier.GetPointAtTime( t ); transform.position = vec; t += 0.001f; if( t > 1f ) t = 0f; } } Thank you very much for this.
the lazy asks (after a month lol) what values should I give the 1st and 2nd handle or the bezier curve? thank you.
Well, that depends on the shape you want the curve to be... If you've ever used Illustrator and modified curves using those little handlebar things, that is a bezier curve, and the ends of the handlebars are the P1 P2. The farther those middle points are from a straight line, the more drastic the curvature. I also mean no disrespect to Loran and Fivedollar, but I do have a really bare-bones implementation of the Bezier function, for anyone who wants to take it way down to basics. Code (csharp): function CalculateBezierPoint(t : float, p0 : Vector3, p1 : Vector3 , p2 : Vector3 , p3 : Vector3) { var u : float = 1.0 - t; var tt : float = t * t; var uu : float = u * u; var uuu : float = uu * u; var ttt : float = tt * t; var p : Vector3 = uuu * p0; //first term p += 3 * uu * t * p1; //second term p += 3 * u * tt * p2; //third term p += ttt * p3; //fourth term return p; } Feed it 't' as a 0-1 float representing the point along the curve that you want to get, and the other four points, and it will spit back the point on the curve at that t value.
No, I have never used Illustrator I opened up Blender and created a bezier curve but there was two points on each of the handle bars. The diagram I found in Wikipedia sort of explains it though Thank you for your bare-bones version Will try using that
I'll give it a shot... A Bezier Curve is a curved line that is defined by four points. The first and last points are the ends of the curve, and the two points in the middle distort and define the curvature. Here's a picture that might help explain it. In this picture, you can see three Bezier Curves of different shapes. The P0-P3 points' coordinates are called out, the gray lines are just to help point them out. To use my function above, define the curve you want in 3D space, enter in those four points, and a time value 't'. 't' represents the amount along the curve you want to generate the point for. 0 will return P0, 1 will return P3, .5 will give you the point in the center of the curve. The intended use is to use this multiple times in a loop, and either use those points to move or draw something, or store those point in an array for later use...
You're welcome, I'm glad it is useful! And one thing I forgot to mention is that even though my example info there is only 2-D, this function works in 3-D. I'm using it right now for flight paths for missiles... I start with the beginning and end known, then add an offset to both those points to move P1 P2 up in the +Y direction, run a loop with as many points as I need for precision, and fill an array for my path. I also add a small random number to P1 P2, so the missiles don't always fly in a plane between the gun and the target. I make sure that I move them both in the same direction, by the same amount, and the effect is basically as if the flight path rotated to the side a few degrees. If I applied different numbers, or they were in opposite directions, or the offsets were extreme, the curve could be a really funky 3-D S-curve. You can get some pretty interesting curves, even only with four points... Edit: added And if you want to think about the next level, you can chain multiple bezier curves together to make more complicated paths. If you make sure that your P2 from the first curve, the shared P3/P0, and P1 of the second curve are all on the same line (in 3D), the join will be smooth. If they are not co-linear, there will be a 'corner' at the join. Which I suppose could be a desired outcome in some cases...
I don't know ,what the function "GetPointAtTime" and "SetContent" do. I'm a chinese , English is very poor, sorry . Eventually thanks a lot.
SetConstant is an internal function called by CheckContent... GetPointAtTime is what you use to get a point on the curve after you have defined it. I assume 0.0f will give you the start of the curve, 1.0f will give you the other end, and numbers in between will give you those points. You can run a loop and store a whole array of points, or you can just get them one at a time. I think that function is a little too complicated; check out my simpler version lower down, I even have pictures to explain how it works... Hope this makes sense...
I know what you say, but I don't know where the formula come from! That's to say, why "this.Cx = 3 * ((this.p0.x + this.p1.x) - this.p0.x);" atc. It's the Bezier formula? Thank you all right.
Yes, that's the math that's figuring the curve points. If you look at my function, you can see the simplest function I could boil it all down to. Code (csharp): function CalculateBezierPoint(t : float, p0 : Vector3, p1 : Vector3 , p2 : Vector3 , p3 : Vector3) { var u : float = 1.0 - t; var tt : float = t * t; var uu : float = u * u; var uuu : float = uu * u; var ttt : float = tt * t; var p : Vector3 = uuu * p0; //first term p += 3 * uu * t * p1; //second term p += 3 * u * tt * p2; //third term p += ttt * p3; //fourth term return p; You call the function with five arguments: the four points of your bezier curve (the first and last are the ends, but the middle two might not actually be ON the curve...), and a float between 0 and 1 that is the percentage along the curve you want to get the point for. So 0.5f would get you the midpoint of your curve... If you are confused about how the bezier curves work, look at the picture I posted above... Oh, and I did not create this math... I can understand what it is doing, but I don't really understand why it works
Can anyone who know about , the heading of an object along a curve in unity 3D? If possible than help pls.
Well, the way I handle it is I generate all the points for the curve, say an array of 100 points... Then I move the object from point to point, and rotate it to LookAt the next point on the curve. If you have a smooth curve, you won't need a lot of points; if you have a curve that curves sharply, you'll need more points. You'll need to handle what happens when you get to the end, I usually don't actually use the very last point, I stop one short.
no its give some jittering problem , i have a solution but not perfect it's close to the perfect, due to 2D , mean two dimensional , your are saying about 3D point of view..... mean , i apply a point*time = vector; than i pass the vector to , rotational operation , but it's giving a problem.
As you move through the points, just have the object rotate to LookAt the point you are moving toward. So, if you are at point 0, the object is facing point 1, when you move to point 1, have it facing point 2, etc...
yes but giving problem , i did this also., but facing but i give the limit of one axis be zero , than unbehavior result. giving. but i convert through this formulae but problem , anyway i post algo of this cocos2D, can you convert this in rotation , in quad bezcurve. I post here...... ************************* float qx = (powf(1-t,2)*xa + 2*(1-t)*t*xb+powf(t,2)*xc); float qy = (powf(1-t,2)*ya + 2*(1-t)*t*yb+powf(t,2)*yc); double deltaX = x-qx; double deltaY = y-qy; double degrees = (-180/M_PI)*ccpToAngle(CGPointMake(deltaX,deltaY)); ************** SOMETHING LIKE THIS , JUST ANGLE transform.Rotate(angle,0,0);//my required result... it's giving a solution as i want , but some problem , try to solved in perfect , Requirement is here in 2D , not in 3D view, rotation along X axis only and movement in y-z axis only...
here main thing in 3 line code , how you modify , it's depends on you and more developers, if possible than post here . **************************************** var relative : Vector3 = transform.InverseTransformPoint(2*curve.Interp(inc rement)); var angle : float = Mathf.Atan2(relative.x, relative.y) * Mathf.Rad2Deg; transform.Rotate (angle, 0, 0);
That's not how I would do it at all; I'm not quite sure what is going on with your code, but you don't need to do all that math. Here's how I set up my path following: I create an array of Vector3s using my code above, looping through, providing t values from 0-1. Call this array BezierPoints. I move the object to BezierPoints[0], and use transform.LookAt(BezierPoints[1]) Then move it to BezierPoints[1], and use transform.LookAt(BezierPoints[2]). loop through and viola.
How do I give the heading for the object if I have a simple bezier with just 4 points? The LookAt will work if you have a lot of points and have the object LookAt the next one(like mentioned above by Tasarran), but what about heading with just 4 points? Edit: I got it to work. Since Tasarran's function returns a Vector3 value which depends on 't' between 1 and 0, I have the object LookAt the returned value where 't' is "ahead" of the object, i.e. the function receives not t, but t + 0.2 (I have used the value 0.2 but any value can be used, the smaller the value the smoother the result). That gives a smooth heading while the object travels along the bezier. I hope this helps anyone who's stuck on the object heading.
I found this thread very helpful when added bezier curve support to my animation engine LeanTween. However a lot of the scripts listed here don't give you the option to integrate over the curve at a constant rate (it will move quickly into the curve and slow down and other strange behavior). I was able to create my own script where you can integrate over the path at a constant rate, you can either use it by downloading my free animation engine LeanTween, or I have also included it here: PHP: class LTBezier{ public var length:float; private var a:Vector3; private var aa:Vector3; private var bb:Vector3; private var cc:Vector3; private var len:float; private var arcLengths:float[]; public function LTBezier(a:Vector3, b:Vector3, c:Vector3, d:Vector3, precision:float){ this.a = a; aa = (-a + 3*(b-c) + d); bb = 3*(a+c) - 6*b; cc = 3*(b-a); this.len = 1.0 / precision; arcLengths = new float[this.len + 1]; arcLengths[0] = 0; var ov:Vector3 = a; var v:Vector3; var clen:float = 0.0; for(var i:int = 1; i <= this.len; i++) { v = bezierPoint(i * precision); clen += (ov - v).magnitude; this.arcLengths[i] = clen; ov = v; } this.length = clen; } private function map(u:float):float { var targetLength:float = u * this.arcLengths[this.len]; var low:int = 0; var high:int = this.len; var index:int = 0; while (low < high) { index = low + (((high - low) / 2) | 0); if (this.arcLengths[index] < targetLength) { low = index + 1; } else { high = index; } } if(this.arcLengths[index] > targetLength) index--; if(index<0) index = 0; return (index + (targetLength - arcLengths[index]) / (arcLengths[index + 1] - arcLengths[index])) / this.len; } private function bezierPoint(t:float):Vector3{ return ((aa* t + (bb))* t + cc)* t + a; } public function point(t:float):Vector3{ return bezierPoint( map(t) ); }}class LTBezierPath{ public var pts:Vector3[]; public var length:float; public var orientToPath:boolean; private var beziers:LTBezier[]; private var lengthRatio:float[]; public function LTBezierPath( pts_:Vector3[] ){ pts = pts_; var k:int = 0; beziers = new LTBezier[ pts.Length / 4 ]; lengthRatio = new float[ beziers.Length ]; for(var i:int=0; i < pts.Length; i+=4){ beziers[k] = new LTBezier(pts[i+0],pts[i+2],pts[i+1],pts[i+3],0.05); length += beziers[k].length; k++; } // Debug.Log("beziers.Length:"+beziers.Length + " beziers:"+beziers); for(i = 0; i < beziers.Length; i++){ lengthRatio[i] = beziers[i].length / length; } } public function point( ratio:float ):Vector3{ var added:float = 0.0; for(var i:int = 0; i < lengthRatio.Length; i++){ added += lengthRatio[i]; if(added >= ratio) return beziers[i].point( (ratio-(added-lengthRatio[i])) / lengthRatio[i] ); } return beziers[lengthRatio.Length-1].point( 1.0 );; } public function place( transform:Transform, ratio:float ){ place( transform, ratio, Vector3.up ); } public function place( transform:Transform, ratio:float, worldUp:Vector3 ){ transform.position = point( ratio ); ratio += 0.001; if(ratio<=1.0) transform.LookAt( point( ratio ), worldUp ); }} And here is an example of using the class to iterate over a path at constant rate: PHP: public var editorPath:LeanTweenPath;private var lt1:GameObject;private var lt2:GameObject;private var ltPath1:LTBezierPath;private var ltPath2:LTBezierPath;private var iter1:float = 0.25;private var iter2:float = 0.5;function Start () { lt1 = GameObject.Find("LeanTweenAvatar1"); lt2 = GameObject.Find("LeanTweenAvatar2"); ltPath1 = new LTBezierPath(editorPath.path); ltPath2 = new LTBezierPath(editorPath.path);}function Update () { ltPath1.place( lt1.transform, iter1); lt2.transform.position = ltPath2.point( iter2 ); iter1 += Time.deltaTime*0.1; if(iter1>1.0) iter1 = 0.0; iter2 += Time.deltaTime*0.1; if(iter2>1.0) iter2 = 0.0;} For creating paths it is easiest to download the path editor I have made: LeanTween Editor Or you can pass in values manually in the format: new LTBezierPath( startPoint1, controlPoint1, controlPoint2, endPoint2, endPoint2, controlPoint3, controlPoint4, endPoint4, .... );
Hi everyone, this is awesome. I dont know any maths,but for now ive been able to understand and apply it. But im getting stuck in duplicating objects along the curve. A "superbasic" approach to calculate the number of objects to duplicate, was to get the distance between p3 and p0 and divide it by the localScale.x of the duplicate object(is an uniform box). Then create the objects in a for loop, setting "t" time of the curve by dividing the index number of the child object by the total amount of objects to duplicate(to get a 0-1 range) . Jeje,sorry about this childish math explanation and to want to understand everything without knowing anything. So... Code (csharp): float distance = Vector3.Distance(endDummy.position - startDummy.position); distance = Mathf.Round(distance/startDummy.localScale.x) - 1; for(int n = 0 ; n < distance;n++){ //p1 and p2 are "random" for now Vector3 _pos = CalculateBezierPoint(n/distance,startDummy.position,startDummy.position + (startDummy.position.normalized *5),endDummy.position + (endDummy.position.normalized * 5),endDummy.position); Transform tmpChild = Instantiate(dummy,_pos,Quaternion.identity) as Transform; } I know this is, again, a childish approach, but if anyone could explain or put me in the right direction,apart from learning maths , it will be very helpful. I think there is too much maths behind this,but.. who knows. Something like array and curve modifiers in Blender. Thanks in advance.
Anyone struggling to understand how beziers work, or how they can be calculated, should look at the wikipedia page for bezier curves: http://en.wikipedia.org/wiki/Bézier_curve in particular scroll down to the animated gifts which show the De Casteljau algorithm quite simply. It really is basically just a simple subdivision algorithm, finding the mid-point between two sets of coordinates representing the control points and simply linearly interpolating along the lines between the control points. It can be implemented quite efficiently in code, too, and much easier to understand (for me) than than all those squares and cubes etc, even for beziers with more than 4 controls. You don't need to understand complicated formulas or other such math weirdness to understand how the basic algorithm works.
Hi, I modified the script to make a convenient souple tool. However, there's a problem with the "GetPointAtTime" and "GetTangentAtTime" formulas (in screen bellow, in red : real unity handles.drawbezier math, in yellow : real math). If somebody has an idea, he is welcome. After I plan to add a rotation handle to rotate handles arround a node. View attachment 85829 BezierComplex.cs // object extension to manage the bezier elements (to use it with JS, place make it compile first (place it in a "Standard Assets folder") ) Code (csharp): // If you wanna use that script with JS, it needs to be comiled before JS script and so placed into a "Standard Assets" folder //http://forum.unity3d.com/threads/5082-Bezier-Curve using UnityEngine; using System.Collections; [System.Serializable] // allow storage of variables + display in the inspector to get them back when launching unity. public class BezierComplex : System.Object { public Vector3[] nodes = new Vector3[0]; public Vector3[] handlesA = new Vector3[0]; // handle before point public Vector3[] handlesB = new Vector3[0]; // hanldesA[0] hanldesB[max] are unsued // Bezier constant are stored to calculate progression faster //private Vector3[] A = new Vector3[0]; //private Vector3[] B = new Vector3[0]; //private Vector3[] C = new Vector3[0]; public BezierComplex() { } // t = 0 =---= 1; public Vector3 GetPointAtTime(float t) { if (t < 0 || t > 1) return Vector3.zero; if (nodes.Length < 2) return Vector3.zero; int seg = GetSegmentAtTime(ref t); // the function will also modify t (ref) if (t == 0) return nodes[seg]; return CalculatePosition(t, nodes[seg], handlesB[seg], handlesA[seg+1], nodes[seg+1]); //return CalculatePosition(t, nodes[seg], A[seg], B[seg], C[seg]); } public int GetSegmentAtTime(ref float t) { // get segment and retrieve the delta value to t int nbSegment = nodes.Length - 1; float tRect = t * nbSegment; int seg = (int) tRect; // cast to int to get segment tRect -= seg; // 0-1 for that segment t = tRect; return seg; } private Vector3 CalculatePosition(float t, Vector3 p0, Vector3 h0, Vector3 h1, Vector3 p1) { float tt = t*t; float temp = 1-t; float c = 3*temp*tt; temp *= temp; float b = 3*temp*t; temp *= temp; float a = temp; return a * p0 + b * h0 + c * h1 + tt*t * p1; // (1-t)^3 p0 + 3(1-t)^2 t p1 + 3(1-t) t^2 p2 + t^3 p3 } /* private Vector3 CalculatePosition(float t, Vector3 p0, Vector3 A, Vector3 B, Vector3 C) { float t2 = t * t; float t3 = t2 * t; float x = A.x * t3 + B.x * t2 + C.x * t + p0.x; float y = A.y * t3 + B.y * t2 + C.y * t + p0.y; float z = A.z * t3 + B.z * t2 + C.z * t + p0.z; return new Vector3(x, y, z); }//*/ public Vector3 GetTangentAtTime(float t) { if (t < 0 || t > 1) return Vector3.zero; if (nodes.Length < 2) return Vector3.zero; int seg = GetSegmentAtTime(ref t); // the function will also modify t (ref) if (t == 0) { if (seg == nodes.Length-1) return nodes[seg] - handlesA[seg]; return handlesB[seg] - nodes[seg]; } return CalculatePosition(t, nodes[seg], handlesB[seg], handlesA[seg+1], nodes[seg+1]); } private Vector3 CalculateTangent(float t, Vector3 p0, Vector3 h0, Vector3 h1, Vector3 p1) { float a = 1-t; float b = a*6*t; a = a*a*3; float c = t*t*3; return - a * p0 // derivative formula from GetBezierPoint formula + a * h0 - b * h0 - c * h1 + b * h1 + c * p1; // - 3(1-t)^2 *P0 // + 3(1-t)^2 *P1 // - 6t(1-t) * P1 // - 3t^2 * P2 // + 6t(1-t) * P2 // + 3t^2 * P3 } /* // If all bezier is instantaneously created through function public void SetAllConstants() { for (int i = 0; i < A.Length; i++) SetConstant(i); } private void SetConstant(int i) { // i base node of bezier if (i >= A.Length) return; // A.Length = nodes.Length - 1 CalculateConstant(i, nodes[i], handlesB[i], nodes[i+1], handlesA[i+1]); } private void CalculateConstant(int i, Vector3 p0, Vector3 h0, Vector3 p1, Vector3 h1) { C[i].x = 3f * ( ( p0.x + h0.x ) - p0.x ); B[i].x = 3f * ( ( p1.x + h1.x ) - ( p0.x + h0.x ) ) - C[i].x; A[i].x = p1.x - p0.x - C[i].x - B[i].x; C[i].y = 3f * ( ( p0.y + h0.y ) - p0.y ); B[i].y = 3f * ( ( p1.y + h1.y ) - ( p0.y + h0.y ) ) - C[i].y; A[i].y = p1.y - p0.y - C[i].y - B[i].y; C[i].z = 3f * ( ( p0.z + h0.z ) - p0.z ); B[i].z = 3f * ( ( p1.z + h1.z ) - ( p0.z + h0.z ) ) - C[i].z; A[i].z = p1.z - p0.z - C[i].z - B[i].z; } //*/ public Vector3[,] GetBezierInHandlesFormat() { // format : (start, end, handleStart, handleEnd) int l = GetNumberOfSegment(); if (l == 0) return null; Vector3[,] b = new Vector3[l, 4]; for (int i = 0; i < l; i++) { b[i, 0] = nodes[i]; b[i, 1] = nodes[i + 1]; b[i, 2] = handlesB[i]; b[i, 3] = handlesA[i + 1]; } return b; } /* Use example Vector3[,] h = thisBezier.GetBezierInHandlesFormat(); //Vector3[segments, 4(p0,h0,p1,h1)]; for (int i = 0; i < l; i++) { Handles.DrawBezier(h[i,0], h[i,1], h[i,2], h[i,3], Color.green, null, 2); } //*/ public void DeleteAllNodes() { nodes = new Vector3[0]; handlesA = new Vector3[0]; handlesB = new Vector3[0]; //A = new Vector3[0]; //B = new Vector3[0]; //C = new Vector3[0]; } public void DeleteNode(int i) { if (i < 0 || i >= nodes.Length) Debug.LogError("You are trying to DELETE a node which does not exist. Max node index : " + (nodes.Length - 1) + " . Index wanted : " + i); ArrayList n = new ArrayList(nodes); ArrayList ha = new ArrayList(handlesA); ArrayList hb = new ArrayList(handlesB); //ArrayList a = new ArrayList(A); //ArrayList b = new ArrayList(B); //ArrayList c = new ArrayList(C); n.RemoveAt(i); ha.RemoveAt(i); hb.RemoveAt(i); //a.RemoveAt(i); //b.RemoveAt(i); //c.RemoveAt(i); nodes = (Vector3 []) n.ToArray(typeof(Vector3)); handlesA = (Vector3 []) ha.ToArray(typeof(Vector3)); handlesB = (Vector3 []) hb.ToArray(typeof(Vector3)); //A = (Vector3 []) a.ToArray(typeof(Vector3)); //B = (Vector3 []) b.ToArray(typeof(Vector3)); //C = (Vector3 []) c.ToArray(typeof(Vector3)); } public void InsertNode(int i, Vector3 p, Vector3 h1, Vector3 h2) { if (i < 0 || i > nodes.Length) Debug.LogError("You are trying to delete a node which does not exist. Max node index (with added node) : " + nodes.Length + " . Index wanted : " + i); /* string str = ""; for (int k = 0; k < nodes.Length; k++) { str += System.Math.Round(nodes[k].x, 0) + ", "; } Debug.Log(str); //*/ ArrayList n = new ArrayList(nodes); ArrayList ha = new ArrayList(handlesA); ArrayList hb = new ArrayList(handlesB); //ArrayList a = new ArrayList(A); //ArrayList b = new ArrayList(B); //ArrayList c = new ArrayList(C); if (i == nodes.Length) { n.Add(p); ha.Add(h1); hb.Add(h2); //a.Add(Vector3.zero); //b.Add(Vector3.zero); //c.Add(Vector3.zero); } else { n.Insert(i, p); ha.Insert(i, h1); hb.Insert(i, h2); //a.Insert(i, Vector3.zero); //b.Insert(i, Vector3.zero); //c.Insert(i, Vector3.zero); } nodes = (Vector3 []) n.ToArray(typeof(Vector3)); handlesA = (Vector3 []) ha.ToArray(typeof(Vector3)); handlesB = (Vector3 []) hb.ToArray(typeof(Vector3)); //A = (Vector3 []) a.ToArray(typeof(Vector3)); //B = (Vector3 []) b.ToArray(typeof(Vector3)); //C = (Vector3 []) c.ToArray(typeof(Vector3)); //if (i != nodes.Length - 1) SetConstant(i); //if (i != 0) SetConstant(i - 1); /* for (int k = 0; k < nodes.Length; k++) { str += System.Math.Round(nodes[k].x, 2) + ", "; } Debug.Log(str); //*/ } public void MoveNode(Vector3 p, int i) { if (i < 0 || i >= nodes.Length) Debug.LogError("You are trying to MOVE a node which does not exist. Max node index : " + (nodes.Length - 1) + " . Index wanted : " + i); var delta = p - nodes[i]; handlesA[i] += delta; handlesB[i] += delta; nodes[i] = p; //if (i != nodes.Length - 1) SetConstant(i); //if (i != 0) SetConstant(i - 1); } public void MoveHandle(Vector3 p, int i, bool handleA) { if (i < 0 || i >= nodes.Length) Debug.LogError("You are trying to MOVE an handle which does not exist. Max node index : " + (nodes.Length - 1) + " . Index wanted : " + i); if (handleA) handlesA[i] = p; else handlesB[i] = p; //if (i != nodes.Length - 1) SetConstant(i); //if (i != 0) SetConstant(i - 1); } public int GetNumberOfSegment() { int n = nodes.Length - 1; if (n < 0) n = 0; return n; } } PolyBezier.cs // The script that the othe scripts will interact with. Attached it to an empty gameobject Code (csharp): using UnityEngine; using System.Collections; public class PolyBezier : MonoBehaviour { public BezierComplex bez = new BezierComplex(); public float t = 0; } PolyBezierEditor.cs // The script that allow you to manage the point through "in editor" gui. Code (csharp): using UnityEditor; using UnityEngine; using System.Collections; using System; using System.Reflection; // Thanks to Dantus from Edelweiss Interactive for this trick public class DefaultHandles { public static bool Hidden { get { Type type = typeof(Tools); FieldInfo field = type.GetField("s_Hidden", BindingFlags.NonPublic | BindingFlags.Static); return ((bool)field.GetValue(null)); } set { Type type = typeof(Tools); FieldInfo field = type.GetField("s_Hidden", BindingFlags.NonPublic | BindingFlags.Static); field.SetValue(null, value); } } } [CustomEditor(typeof(PolyBezier))] public class PolyBezierEditor : Editor { private enum cpte {Node, HandleA, HandleB}; // current point type enum private cpte curPointType = cpte.Node; int curPointIndex = -1; bool hideDefaultHandle = true; void OnEnable() { DefaultHandles.Hidden = hideDefaultHandle; } void OnDisable() { DefaultHandles.Hidden = false;} void MoveNode(PolyBezier polyBezier, Vector3 localPos, int i) { polyBezier.bez.MoveNode(localPos, i); EditorUtility.SetDirty(target); } void MoveHandle(PolyBezier polyBezier, Vector3 localPos, int i, bool handleA){ polyBezier.bez.MoveHandle(localPos, i, handleA); EditorUtility.SetDirty(target); } void RotateHandles(PolyBezier polyBezier, Quaternion qDelta, int i){ Vector3 p = polyBezier.bez.nodes[i]; Vector3 ha = polyBezier.bez.handlesA[i]; Vector3 hb = polyBezier.bez.handlesB[i]; polyBezier.bez.handlesA[i] = p + qDelta * (ha-p); polyBezier.bez.handlesB[i] = p + qDelta * (hb-p); EditorUtility.SetDirty(target); } void DeleteNode(PolyBezier polyBezier, int index) { int nl = polyBezier.bez.nodes.Length; if (index < 0 || index >= nl) return; polyBezier.bez.DeleteNode(index); if (nl == 0) { curPointIndex = -1; RectifyCurrentPointEnum(polyBezier); } else { if (index != 0) { curPointIndex -= 1; RectifyCurrentPointEnum(polyBezier); } } EditorUtility.SetDirty(target); } void AddNode(PolyBezier polyBezier, int position) { // before = position // after = position + 1 int nl = polyBezier.bez.nodes.Length; Vector3 p; Vector3 h; Vector3 p2; Vector3[] nodes = polyBezier.bez.nodes; if (nl == 0) { p = Vector3.zero; h = Vector3.right * 10; polyBezier.bez.InsertNode(0, p, p+h, p-h); } else if (nl == 1) { if (position == 0) { p = Vector3.zero; h = Vector3.right * 10; polyBezier.bez.InsertNode(0, p, p+h, p-h); } else { p = nodes[0] * 2; p2 = p - nodes[0]; polyBezier.bez.InsertNode( 1, p, polyBezier.bez.handlesA[0] + p2, polyBezier.bez.handlesB[0] + p2); } } else { if (position == -1 ) return; //position = nl ; // last if (position == 0) { p = nodes[position]; p2 = p - nodes[position + 1]; polyBezier.bez.InsertNode( position, p + p2 * 2/4, p + p2 * 3/4, // ext p + p2 * 1/4 ); // int } else if ( position == nl) { p = nodes[position - 1]; p2 = p - nodes[position - 2]; polyBezier.bez.InsertNode( position, p + p2 * 2/4, p + p2 * 1/4, // int p + p2 * 3/4 ); // ext } else { p = 0.5f * (nodes[position - 1] + nodes[position]); // between point before and after Vector3 pMid = 0.5f * (p - polyBezier.bez.nodes[position - 1]); // this + p = between p and point after, p - this = between point polyBezier.bez.InsertNode( position, p, p - pMid, p + pMid); } } EditorUtility.SetDirty(target); } void ResetHandles(PolyBezier polyBezier, int i) { Vector3 p = polyBezier.bez.nodes[i]; polyBezier.bez.handlesA[i] = p - Vector3.right * 4; polyBezier.bez.handlesB[i] = p + Vector3.right * 4; EditorUtility.SetDirty(target); } void ClearAll(PolyBezier polyBezier) { polyBezier.bez.DeleteAllNodes(); } void insideSceneGUI(PolyBezier polyBezier) { Rect size = new Rect(0, 0, 300, 160 + 10); float sizeButton = 30; Handles.BeginGUI(); // Handles.BeginGUI(new Rect(Screen.width - size.width - 10, Screen.height - size.height - 10, size.width, size.height)); GUI.BeginGroup(new Rect(Screen.width - size.width - 10, Screen.height - size.height - 50, size.width, size.height)); GUI.Box(size, "PolyBezier Tool Bar"); Rect rc = new Rect(0, 15, size.width, sizeButton); GUI.contentColor = Color.black; GUI.Label(rc, "Clic on Circles to select a point"); GUI.contentColor = Color.white; rc.y += 15; if (curPointIndex != -1) { GUI.contentColor = Color.black; GUI.Label(rc, "Current Point " + curPointIndex); GUI.contentColor = Color.white; rc.y -= 25; // back up rc.x = size.width / 5 * 4; rc.width = size.width / 5; if (GUI.Button(rc, "Deselect")) { curPointIndex = -1; RectifyCurrentPointEnum(polyBezier); } rc.y += 25; // back down rc.x = 0; rc.y += 15; rc.width = size.width / 3; if (GUI.Button(rc, "Insert Before")) { AddNode(polyBezier, curPointIndex); } rc.x += rc.width; if (GUI.Button(rc, "Delete")) DeleteNode(polyBezier, curPointIndex); rc.x += rc.width; if (GUI.Button(rc, "Insert After")) { AddNode(polyBezier, curPointIndex + 1); curPointIndex++; RectifyCurrentPointEnum(polyBezier); } rc.x = 0; rc.y += 10; rc.y += sizeButton; if (GUI.Button(rc, "Reset Handles")) { ResetHandles(polyBezier, curPointIndex); } } else { if (polyBezier.bez.nodes.Length == 0) { if (GUI.Button(rc, "Insert")) AddNode(polyBezier, 0); rc.y += sizeButton; } else { rc.width = size.width / 2; if (GUI.Button(rc, "Insert First")) { AddNode(polyBezier, 0); curPointIndex = 0; RectifyCurrentPointEnum(polyBezier); } rc.x += rc.width; if (GUI.Button(rc, "Insert Last")) { AddNode(polyBezier, polyBezier.bez.nodes.Length); curPointIndex = polyBezier.bez.nodes.Length - 1; RectifyCurrentPointEnum(polyBezier); } rc.y += sizeButton; } } if (polyBezier.bez.nodes.Length > 0) { //rc.y += sizeButton; rc.width = size.width / 2; rc.x = 0; rc.y += sizeButton+10; if (GUI.Button(rc, "Clear All")) { ClearAll(polyBezier); curPointIndex = -1; RectifyCurrentPointEnum(polyBezier); } } rc.x += rc.width; if (hideDefaultHandle) { if (GUI.Button(rc, "Show Main Transform")) { hideDefaultHandle = false; DefaultHandles.Hidden = hideDefaultHandle; } } else { if (GUI.Button(rc, "Hide Main Transform")) { hideDefaultHandle = true; DefaultHandles.Hidden = hideDefaultHandle; } } GUI.EndGroup(); Handles.EndGUI(); } void OnSceneGUI () { PolyBezier polyBezier = (PolyBezier) target; DisplayPointsAndLines(polyBezier); DisplayBezier(polyBezier); DisplayProgression(polyBezier); SceneView.RepaintAll(); // repaint to display progression } void DisplayBezier(PolyBezier polyBezier) { int l = polyBezier.bez.GetNumberOfSegment(); // segments if (l == 0) return; Transform tr = polyBezier.transform; Vector3[,] h = polyBezier.bez.GetBezierInHandlesFormat(); //Vector3[segments, 4(p0,h0,p1,h1)]; for (int i = 0; i < l; i++) { for (int j = 0; j < 4; j++) h[i, j] = tr.TransformPoint(h[i, j]); Handles.DrawBezier(h[i,0], h[i,1], h[i,2], h[i,3], Color.red, null, 2); } //* Handles.color = Color.yellow; float tt = 0; float step = 0.02f; Vector3 pb; Vector3 pa; for (float t = step; t <= 1; t += step) { pb = polyBezier.bez.GetPointAtTime(tt); pb = tr.TransformPoint(pb); pa = polyBezier.bez.GetPointAtTime(t); pa = tr.TransformPoint(pa); Handles.DrawLine(pb, pa); tt = t; } //*/ } //private Quaternion qRec = Quaternion.identity; private Vector3 nodeRec = Vector3.zero; private Vector3 hanARec = Vector3.zero; private Vector3 hanBRec = Vector3.zero; void DisplayPointsAndLines(PolyBezier polyBezier) { int someHashCode = GetHashCode(); Transform tr = polyBezier.transform; int nl = polyBezier.bez.nodes.Length; Vector3[] worldNodes = new Vector3[nl]; Vector3[] worldHandlesA = new Vector3[nl]; Vector3[] worldHandlesB = new Vector3[nl]; for (int i = 0; i < nl; i++) { Vector3 node = tr.TransformPoint(polyBezier.bez.nodes[i]); Vector3 hanA = tr.TransformPoint(polyBezier.bez.handlesA[i]); Vector3 hanB = tr.TransformPoint(polyBezier.bez.handlesB[i]); //Handles.color = Color.yellow; //Handles.DrawLine(hanA, node); //Handles.DrawLine(node, hanB); //if (i != nl-1) Handles.DrawLine(hanB, tr.TransformPoint(polyBezier.bez.handlesA[i+1])); Handles.color = Color.white; Handles.Label(node, " " + i); float size; // Display Node and check if it has the focus // size = HandleUtility.GetHandleSize(node); SetControlIDState(someHashCode); Handles.ScaleValueHandle(0, node, Quaternion.identity, size, Handles.SphereCap, 0); // display cap if (GetControlIDState(someHashCode)) { //Debug.Log(i); curPointIndex = i; curPointType = cpte.Node; RectifyCurrentPointEnum(polyBezier); } Handles.color = Color.blue; // Display First Handle and check if it has the focus // if (i != 0) { size = HandleUtility.GetHandleSize(hanA); SetControlIDState(someHashCode); Handles.ScaleValueHandle(0, hanA, Quaternion.identity, size, Handles.SphereCap, 0); if (GetControlIDState(someHashCode)) { //Debug.Log(i); curPointIndex = i; curPointType = cpte.HandleA; RectifyCurrentPointEnum(polyBezier); } Handles.DrawLine(hanA, node); } // Display Last Handle and check if it has the focus // if (i != nl-1) { size = HandleUtility.GetHandleSize(hanB); SetControlIDState(someHashCode); Handles.ScaleValueHandle(0, hanB, Quaternion.identity, size, Handles.SphereCap, 0); if (GetControlIDState(someHashCode)) { //Debug.Log(i); curPointIndex = i; curPointType = cpte.HandleB; RectifyCurrentPointEnum(polyBezier); } Handles.DrawLine(hanB, node); } // Move points for selected object // if (curPointIndex == i) { /* Quaternion q = Handles.RotationHandle(Quaternion.identity, node); if (q != qRec) // do not call EditorUtility.Set Dirty() if nothing is moved { q = q * Quaternion.Inverse(qRec); qRec = q; //RotateHandles(polyBezier, q, curPointIndex); // display rotation gizmo } //*/ if (curPointType == cpte.Node) { node = Handles.PositionHandle(node, Quaternion.identity); // display position gizmo if (node != nodeRec) // do not call EditorUtility.Set Dirty() if nothing is moved { MoveNode(polyBezier, tr.InverseTransformPoint(node), i); nodeRec = node; } } else if (curPointType == cpte.HandleA) { hanA = Handles.PositionHandle(hanA, Quaternion.identity); if (hanA != hanARec) // do not call EditorUtility.Set Dirty() if nothing is moved { MoveHandle(polyBezier, tr.InverseTransformPoint(hanA), i, true); hanARec = hanA; } } else { hanB = Handles.PositionHandle(hanB, Quaternion.identity); if (hanB != hanBRec) // do not call EditorUtility.Set Dirty() if nothing is moved { MoveHandle(polyBezier, tr.InverseTransformPoint(hanB), i, false); hanBRec = hanB; } } } // Store nodes to display lines // worldNodes[i] = node; worldHandlesA[i] = hanA; worldHandlesB[i] = hanB; } insideSceneGUI(polyBezier); //Handles.color = Color.red; //Handles.DrawPolyLine(worldNodes); } int controlIDBeforeHandle; bool isEventUsedBeforeHandle; private void SetControlIDState(int hashCode) { // Get the needed data before the handle (selected SphereCap ID) controlIDBeforeHandle = GUIUtility.GetControlID(hashCode, FocusType.Passive); isEventUsedBeforeHandle = (Event.current.type == EventType.used); } private bool GetControlIDState(int hashCode) { // Get the needed data after the handle int controlIDAfterHandle = GUIUtility.GetControlID(hashCode, FocusType.Passive); bool isEventUsedByHandle = !isEventUsedBeforeHandle (Event.current.type == EventType.used); if ( (controlIDBeforeHandle < GUIUtility.hotControl GUIUtility.hotControl < controlIDAfterHandle) || isEventUsedByHandle) return true; return false; } void DisplayProgression(PolyBezier polyBezier) { float t = polyBezier.t; t = Mathf.Clamp01(t); polyBezier.t = t; if (t == 0) { float tAuto = (Time.realtimeSinceStartup/polyBezier.bez.nodes.Length) % 1; t = tAuto; } Transform tr = polyBezier.transform; Vector3 p = polyBezier.bez.GetPointAtTime(t); p = tr.TransformPoint(p); Vector3 tan = polyBezier.bez.GetTangentAtTime(t); tan = tr.TransformDirection(tan); Handles.color = Color.green; Handles.DrawWireDisc(p, tan, 1); Handles.DrawLine(p, p + tan * 1); float tRect = t; int segment = polyBezier.bez.GetSegmentAtTime(ref tRect); Handles.Label(p, " " + Math.Round(t,2) + "\n" + "segment : " + segment + "\n" + "local t : " + Math.Round(tRect,2)); } override public void OnInspectorGUI() { base.OnInspectorGUI(); } private void RectifyCurrentPointEnum(PolyBezier polyBezier) { if (curPointIndex == -1) curPointType = cpte.Node; if (curPointIndex == 0 curPointType == cpte.HandleA) curPointType = cpte.HandleB; if (curPointIndex == polyBezier.bez.nodes.Length - 1 curPointType == cpte.HandleB) curPointType = cpte.HandleA; } }
For those asking about orienting the transform to point in the direction of the curve, there is no need to generate a whole load of points. Just evaluate the first derivative of the curve. If you can't perform the differentiation yourself, you can cheat and look-up the equation on the Wikipedia page.
To fix the GetPointAtTime() function that MapBuilder has provided change the CalculatePosition() function to: Code (CSharp): private Vector3 CalculatePosition(float t, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3) { float u = 1.0f - t; float tt = t * t; float uu = u * u; float uuu = uu * u; float ttt = tt * t; Vector3 p = uuu * p0; //first term p += 3 * uu * t * p1; //second term p += 3 * u * tt * p2; //third term p += ttt * p3; //fourth term return p; } To fix the GetTangentAtTime() make sure the GetTangentAtTime() returns CalculateTangent() and not CalculatePosition(). To look in direction of spline I use: Code (CSharp): // Look at direction of spline Vector3 tangentWorld = mover.transform.position + tangentAlongSpline; mover.transform.LookAt(tangentWorld, mover.transform.up); Hope that helps
Hey everyone. I know this is an old thread, but I have a question regarding Bezier curves i thought would fit well here. Lets say I have a pre-existing Bezier Curve and I want to calculate what the positions of the handles (p1 and p3) would be. Is this possible? Thanks for your time.
you already would know the positions of the start and end handles because those are the same as the first and last bezier points. I think based on reversing the math you should be able to figure out what the other handles were.
If anyone has the time I would love some help reversing this math. I've given it a good deal of work but I'm not great with these types of equations.
Nice article. But what about when I want to get Y coordinate of point lying on bezier curve when I know the X coordinate of this point? Is this possible?
Well, yes, it's possible, just use algebra, and solve the equation for X instead. However, that's a complex equation, and it might just be easier to do a loop that iterates along the curve until the X of the point it calculates is X or close to X and viola.
Your images are so good, help to understand. By the way, in Unity, we can test the Bezier Curves by using Handle like this: Handles.DrawBezier(p0, p1, new Vector3(1, 0, 3), new Vector3(4,0,1), Color.red, null, 2f);
Of course, we can get the t value by solving the equation. In Unity, The Bezier curve is: It's also depending on the kind of Bezier curves you deployed by your self. For visualization: (from wikipedia) Linear Bezier curve: Quadratic Bezier curve: Cubic Bezier curve: Quartic Bezier curve: