Comments and answers for "Subdivide Bezier Curves"
http://answers.unity.com/questions/1599711/subdivide-bezier-curves.html
The latest comments and answers for the question "Subdivide Bezier Curves"Answer by dan_wipf
http://answers.unity.com/answers/1601108/view.html
So the final code works now with connected beziers. thanks a lot, to @Bunny83 *(Moved from Comment to Answer, due to. readability)*
<br>
**Structs:**
public struct SimpsonVec{
[SerializeField] public Vector3 A;
[SerializeField] public Vector3 B;
[SerializeField] public Vector3 C;
[SerializeField] public Vector3 D;
}
public struct BezierPoints
{
[SerializeField] public List<Vector3> bp_vector3;
[SerializeField] public List<Vector3> bp_lastSpawn;
}
<br>
**Code for a Vector3[] Array Output:**
public Vector3[] GetAllPoints(float gap,float acc){
SimpsonVector = SV_SETUP_ALL();
BezierPoints bp = new BezierPoints();
bp.bp_vector3 = new List<Vector3>();
bp.bp_lastSpawn = new List<Vector3>();
for(int i = 0; i<points.Length / 3;i++){
Vector3 ls = new Vector3();
if(i == 0){
ls = Bezier.GetPoint(SimpsonVector[0].A,SimpsonVector[0].B,SimpsonVector[0].C,SimpsonVector[0].D,0);
}if (i > 0){
ls = bp.bp_lastSpawn[i-1];
}
BezierPoints bp_temp = GetSegmentPoints(gap,acc,i,ls);
bp.bp_lastSpawn.Add(bp_temp.bp_lastSpawn[0]);
bp.bp_vector3.AddRange(bp_temp.bp_vector3);
SimpsonVector_TEMP = SimpsonVector;
}
return bp.bp_vector3.ToArray();
}
BezierPoints GetSegmentPoints (float gap,float acc,int index, Vector3 ls)
{
SimpsonVec sv = SimpsonVector[index];
Vector3 last_spawn = ls;
BezierPoints bp = new BezierPoints();
bp.bp_vector3 = new List<Vector3>();
bp.bp_lastSpawn = new List<Vector3>();
float step = 0.1f;
float t = step;
float lastT = new float();
while (t >= 0 && t <= 1f)
{
while (t < 1f && Vector3.Distance(Bezier.GetPoint(sv.A,sv.B,sv.C,sv.D,t), last_spawn) < gap){
t += step;}
step /= acc;
while (t > lastT && Vector3.Distance(Bezier.GetPoint(sv.A,sv.B,sv.C,sv.D,t), last_spawn) > gap){
t -= step;}
step /= acc;
if (t > 1f || t < lastT){
break;}
if(step < 0.000001f){
last_spawn = Bezier.GetPoint(sv.A,sv.B,sv.C,sv.D,t);
bp.bp_vector3.Add(last_spawn + transform.position);
lastT = t;
step = 0.1f;
}
}
bp.bp_lastSpawn.Add(last_spawn);
return bp;
}
<br>
**Helper Methods**
public SimpsonVec SV_Setup(int index){
SimpsonVec sv;
sv.A = points[index];
sv.B = points[index+1];
sv.C = points[index+2];
sv.D = points[index+3];
return sv;
}
public SimpsonVec[] SV_SETUP_ALL(){
SimpsonVec[] sv = new SimpsonVec[points.Length / 3];
for(int i = 0; i<points.Length / 3;i++){
sv[i] = SV_Setup(i*3);
}
return sv;
}
public Vector3 GetPoint (Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) {
t = Mathf.Clamp01(t);
float OneMinusT = 1f - t;
return
OneMinusT * OneMinusT * OneMinusT * p0 +
3f * OneMinusT * OneMinusT * t * p1 +
3f * OneMinusT * t * t * p2 +
t * t * t * p3;
}Tue, 12 Feb 2019 15:08:29 GMTdan_wipfComment by Bunny83 on Bunny83's comment
http://answers.unity.com/comments/1601071/view.html
Well, it depends on what is more important to you. If you just need a position along that curve but have exact distance sections, you can simply force that distance by not using the calculated position, but create a normalized direction from the last point to the current and scale it to your desired length. This may knock the position slightly off the curve, but the distance is exact. If the precision is already relatively high, you won't notice that slight off-the-curve position. This would most likely be the better approach than cranking up the precision.
<br>
Uhm, the "lastT" shouldn't stay -1 since it's set to the last "t" value when a new point is choosen. The main point of that variable was to ensure we don't get stuck inside the while loop. However i see the problem now ^^. When we set lastT = t the top while loop condition would be false. I added the check in the break condition later which should be enough. So yes, the check can be safely removed from the outer loop. I'll edit my comment.Tue, 12 Feb 2019 14:15:10 GMTBunny83Comment by dan_wipf on dan_wipf's comment
http://answers.unity.com/comments/1600967/view.html
just wondering if i’d reach out to the max capeable of a float(i’ve read it’s accurate up to 7 dezimal digits) would it be then the exact value as gap?
<br>
or stick to doubles?Tue, 12 Feb 2019 05:59:33 GMTdan_wipfComment by dan_wipf on dan_wipf's comment
http://answers.unity.com/comments/1600963/view.html
thanks a lot! It didn't took a lot to get the untested code working =)
...
float lastT = new float();
Vector3 trial = last_spawn;
while (t >= 0 && t <= 1f)
{
...
<br>
(fyi => lastT was always -1, so removed from the first while Statement, and voila it worked for me)
<br>
thanks again. you're legend
![BezierTest][1]
[1]: /storage/temp/133014-beziergif.gifTue, 12 Feb 2019 05:52:20 GMTdan_wipfComment by Bunny83 on Bunny83's comment
http://answers.unity.com/comments/1600769/view.html
I've quickly made an image which should show the idea visually(click to enlarge)
<a href="https://www.dropbox.com/s/0z4yzsmoril0ezc/nudgeIn_large.png?raw=1"><img src="https://www.dropbox.com/s/ebvmpegjr4vz7dd/nudgeIn.png?raw=1"></a>
The blue line is the actual location with the exact distance we want. The green line is the current t valueMon, 11 Feb 2019 18:23:06 GMTBunny83Comment by Bunny83 on Bunny83's comment
http://answers.unity.com/comments/1600760/view.html
Your code is quite a bit different from what i wrote ^^. The point of my code was to minimize the required steps and gradually increase the accuracy. So you're just searching for an approximation with rather large steps. But then you decrease the step size and go backwards until you undershoot your desired gap. Then again decrease the stepsize and go forward again, etc...
<br>
This way for the first iteration when using a stepsize of 0.1 for example you get your first approximation after at most 10 steps, then you would use 0.01 and just go back a few steps (again at most 10 steps). You repeat this until you reach your desired accuracy. This should only take a few steps.
<br>
I did not test it, but it should be something like this:
public Vector3[] GetPoints (float gap,float accuracy)
{
SimpsonVec sv = SV_Setup(0);
Vector3 last_spawn = Bezier.GetPoint(sv.A,sv.B,sv.C,sv.D,0);
List<Vector3> allPoints = new List<Vector3>();
allPoints.Add(last_spawn);
float step = accuracy;
float t = step;
float lastT = -1;
Vector3 trial = last_spawn;
while (t >= 0 && t < 1f)
{
while (t < 1f && Vector3.Distance(Bezier.GetPoint(sv.A,sv.B,sv.C,sv.D,t), last_spawn) < gap)
t += step;
step /= 10;
while (t > lastT && Vector3.Distance(Bezier.GetPoint(sv.A,sv.B,sv.C,sv.D,t), last_spawn) > gap)
t -= step;
step /= 10;
if (t > 1f || t < lastT)
break;
else if(step < 0.000001f){
last_spawn = Bezier.GetPoint(sv.A,sv.B,sv.C,sv.D,t);
allPoints.Add(last_spawn);
lastT = t;
step = accuracy;
}
}
return allPoints.ToArray();
}
There are a few exit conditions just to make sure we don't get stuck in some strange up and down nudging. The outer loop exits as soon as "t" goes out of bounds after one iteration. We also stop if the newly found "t" is smaller than the last one since we don't want to move backwards. After we found a point with our desired accuracy, we store it and reset the step size to the initial value for the next point. Your initial "accuracy" depends on how many points you expect between your curve start and end. If there are going to be about 10 points the initial accuracy should be smaller than "1/10". Probably half of that (1/20). Though it could be smaller but it would increase the required stepsMon, 11 Feb 2019 17:33:02 GMTBunny83Comment by dan_wipf on dan_wipf's comment
http://answers.unity.com/comments/1600755/view.html
i thought i could limit the calculations to when i release the mouse after adjusting the bezier handle, but event.current. type == eventtype.mousedrag/up doesnt get registerMon, 11 Feb 2019 17:01:10 GMTdan_wipfComment by dan_wipf on dan_wipf's comment
http://answers.unity.com/comments/1600637/view.html
well I've tried out your code and for a moment I've got the next step in the curve.
I did now an implementation of what you suggested to achieve, but it's a heavy operation which bend's my system to it knees, (depends on how accurate I want to get the operation running)
public Vector3[] GetPoints (float gap,float accuracy){
SimpsonVec sv = SV_Setup(0);
Vector3 last_spawn = Bezier.GetPoint(sv.A,sv.B,sv.C,sv.D,0);
List<Vector3> allPoints = new List<Vector3>();
allPoints.Add(last_spawn);
for(float t = accuracy;t <= 1.0f; t +=accuracy){
Vector3 trial = Bezier.GetPoint(sv.A,sv.B,sv.C,sv.D,t);
if(Vector3.Distance(trial,last_spawn) >= gap){
last_spawn = trial;
allPoints.Add(trial);
}
}
return allPoints.ToArray();
}
<br>
List of accuracy:
0.001f = smooth working accurate (between 0.1 and 0.2 difference).
0.0001f = still smooth working accurate (between 0.01 and 0.02 difference).
0.00001f = buggy working accurate (between 0.001 and 0.002 difference).
0.000001f = up to 20 seconds waiting for respond, accurate (almost zero difference)
<br>
Runs in an editor script. Basically I want to get the most accurate result, but I need the best Performance.Mon, 11 Feb 2019 09:42:34 GMTdan_wipfComment by Bunny83 on Bunny83's comment
http://answers.unity.com/comments/1600505/view.html
"C()" simply stands for your curve function. So it's just the bezier function and the parameter is the normalized "t" value (0 to 1). "t" is just the normalized position along the curve. So you would start with a value of "0". Note that the code i've shown is just the code to calculate the "next point" on the curve.
<br>
I just realised i had already posted something similar [over here][1], though the question was a bit different. He wanted to get the closest position on the curve from a given point.
[1]: http://answers.unity.com/answers/374368/view.htmlSun, 10 Feb 2019 20:05:48 GMTBunny83Comment by dan_wipf on dan_wipf's answer
http://answers.unity.com/comments/1600466/view.html
thanks for the explenation, makes now more sence to me.
just didn’t quite figure out what C and t stands for, could you give me a hint?Sun, 10 Feb 2019 17:08:48 GMTdan_wipfAnswer by Bunny83
http://answers.unity.com/answers/1600307/view.html
Ok, based on your new image and explanation I think we finally understand what's your goal. However it's not that easy to get it working for every kind of cubic bezier curve. The main issue is that certain points can have more than one point that is on the perimeter.
<br>
Bezier curves aren't linear in it's nature which makes it quite difficult to get some uniform pattern analytically. The best approach is to do some sort Newton's method to approach the desired point. So you would just nudge forward along the curve and measure the distance between your last point and the current position. Once distance is too large you would decrease the step size and nudge back until you are again below the target distance. Repeat to get as close as you want to your target distance.
<br>
One problem is that if your curve has a small sharp turn and your step size is too large that you jump over a possible solution and pick the next intersection instead. If that's not an issue this is probably the best solution. Since the "density" of the curve changes as you walk along the curve it's best to measure the relative distance after each step to adjust the step size accordingly.
<br>
Basically something like this:
float targetLength;
// determine worldspace length of a step of 0.001 units
float L = (C(0.001f) - C(0)).magnitude;
// normalized step size to achieve about 1/10 of our target length.
float step = ((targetLength / 10) / L) * 0.001f;
Vector3 last = C(t);
t += step;
while (true)
{
while ((C(t) - last).sqrMagnitude < targetLength *targetLength && t < 1f)
t += step;
step /= 10f;
while ((C(t) - last).sqrMagnitude > targetLength *targetLength && t > 0)
t -= step;
step /= 10f;
if (Mathf.Abs((C(t) - last).sqrMagnitude - targetLength *targetLength) < 0.0001f || t<0 || t>1)
break;
}
Vector3 next = C(t);
This is untested and should be seen as a pseudo script example.Sun, 10 Feb 2019 02:57:45 GMTBunny83Comment by dan_wipf on dan_wipf's comment
http://answers.unity.com/comments/1600214/view.html
Here's a drawing of what I want to achieve:
![Bezier Explenation][1]
[1]: /storage/temp/132853-bezier-explenation.jpgSat, 09 Feb 2019 22:18:40 GMTdan_wipfComment by dan_wipf on dan_wipf's comment
http://answers.unity.com/comments/1600149/view.html
@Bunny83 well yes you both are right about the distances, they’re indeed correct if you take the length from the bezier.
<br>
but in my case i just want the points on the bezier but the distance must be a straight line between the points. because i want to spawn posts on the points and between connected a fixed length paling(distance)Sat, 09 Feb 2019 17:56:41 GMTdan_wipfComment by Bunny83 on Bunny83's comment
http://answers.unity.com/comments/1600043/view.html
Actually the distance is correct. The distances are meant to be along the curve. The curve segment at the sharp turn has approximately the same length as the others. You just calculated the direct connection between the points. Since it's a curve it's in its nature to have different direct distances between two points on the curve depending on where you put them.
<br>
Some curves may need more "approximationSteps" than others. Those which sharp turns or extreme unpropotional segments need more than a "curve" which isn't "curved" that much.
<br>
If you don't understood what i meant, just imagine you double the subdivisions (so you just get another intermediate point on the curve between the current points). That means you get a point at the tip of the sharp curve. The distance betwen that tip and the two neighbors are approximately as long as all the other segments.
<br>
So his example works as intendet. Maybe you have something else in mind, but you have to be more clear in that case what you actually need. Can you take a picture of your curve and manually draw the marks where you want them to be?Sat, 09 Feb 2019 13:29:58 GMTBunny83Comment by dan_wipf on dan_wipf's answer
http://answers.unity.com/comments/1599981/view.html
@coeusueoc well I think basically it's the solution you provided! and it's approximately enough. the only thing I'm worried about is if the curve turn's extremely sharp. the distance between the SubdividedParts will be off the expected result.
----------
you can see in the Picture below, Distance 2 is 7.53.., and not 18.64.. like the other's
is there a way to avoid this? like setting restriction on how much the Bezier can be curved?
Thanks a lot dan
----------
![Result's][1]
[1]: /storage/temp/132809-offresult.jpgSat, 09 Feb 2019 07:08:52 GMTdan_wipfComment by dan_wipf on dan_wipf's answer
http://answers.unity.com/comments/1599885/view.html
thanks alot for your edit! i’ll have a look to it! :)Fri, 08 Feb 2019 22:18:00 GMTdan_wipfComment by dan_wipf on dan_wipf's answer
http://answers.unity.com/comments/1599821/view.html
Thank you for your example Code, this didn't do the Trick for me!.
----------
well I've read this article, but couldn't use it.
might I explain what I really want to achieve I want to divide the bezier into parts. (Vector3 positions) those part have an exactly length, now if I do GetPoint() I'll have to put in the 4 ControllPoints of the Bezier + value t (range 0 to 1) if I'd do a for loop like this:
float segmentLength = 5;
int validPositions = Mathf.FloorToInt(LengthOfBezier / segmentLength);
float currentPos = 0;
for(int i = 0; i< validPositions; i++){
float currentLength = currentPos / LengthOfBezier;
Vector3 WorldPos = GetPoint(A,B,C,D,currentLength);
currentPos += segmentLength;
}
this would give me 10 Vector3 WorldPosition on the Bezier. But if the Bezier is curved, the Distances between those WorldPosition is no more for Example 5.Fri, 08 Feb 2019 18:51:28 GMTdan_wipfAnswer by BastianUrbach
http://answers.unity.com/answers/1599787/view.html
It's explained in [this article][1].
Here is some code for cubic Beziers:
void Subdivide(
Vector3 a0, Vector3 a1, Vector3 a2, Vector3 a3, float t,
out Vector3[] firstPart, out Vector3[] secondPart
) {
var b0 = Vector3.Lerp(a0, a1, t); // Same as evaluating a Bezier
var b1 = Vector3.Lerp(a1, a2, t);
var b2 = Vector3.Lerp(a2, a3, t);
var c0 = Vector3.Lerp(b0, b1, t);
var c1 = Vector3.Lerp(b1, b2, t);
var d0 = Vector3.Lerp(c0, c1, t); // This would be the interpolated point
firstPart = new Vector3[] { a0, b0, c0, d0 }; // first point of each step
secondPart = new Vector3[] { a3, b2, c1, d0 }; // last point of each step
}
<br>
**EDIT:**
Ah, now I understand what you meant (I'll leave the previous answer because it might be helpful for someone else). Unfortunately I think this might be quite a difficult problem. It's surely possible but it probably involves some nasty integrals. Depending on your requirements, an approximate solution might be enough. Here is one, it's a bit ugly but it kinda works. It takes any curve (could be Bezier), the number of subdivisions you want to perform and a value that determines how accurate the approximation will be. It returns an array of values such that evaluating the curve with those values yields evenly spaced points.<br><br>
float[] Subdivide(System.Func<float, Vector3> curve, int divisionCount, int approximationSteps) {
// Estimate distances along the curve in regular intervals of t
// by approximating the curve with a polyline
float stepSize = 1f / approximationSteps;
float[] distances = new float[approximationSteps + 1];
Vector3 last = curve(0);
Vector3 current;
for (int i = 1; i < distances.Length; i++) {
current = curve(i * stepSize);
distances[i] = distances[i - 1] + Vector3.Distance(last, current);
last = current;
}
// Walk through distance array to find the t for each subdivision
float sectionLength = distances[distances.Length - 1] / (divisionCount + 1);
float[] divisions = new float[divisionCount];
int tInt = 0;
float tFrac;
for (int i = 0; i < divisionCount; i++) {
float target = (i + 1) * sectionLength; // Where the next subdivision should be
while (distances[tInt] < target) tInt++; // Skip until target is exceeded
tFrac = Mathf.InverseLerp(distances[tInt - 1], distances[tInt], target);
divisions[i] = (tInt - 1 + tFrac) / approximationSteps;
}
return divisions;
}
<br>
Here is how it could be used:
var divisions = Subdivide(t => Bezier(a, b, c, d, t), 10, 100);
foreach (var t in divisions) {
var p = Bezier(a, b, c, d, t);
Gizmos.DrawLine(p + Vector3.down * 0.1f, p + Vector3.up * 0.1f);
}
<br>
If you just need it to look right, this should be accurate enough:
![subdivided bezier curve][2]
[1]: https://pages.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/bezier-sub.html
[2]: /storage/temp/132784-beziersubdivide.pngFri, 08 Feb 2019 17:14:05 GMTBastianUrbach