Advertisement

Bezier curve control point

Started by August 15, 2022 02:47 PM
36 comments, last by JoeJ 2 years, 4 months ago

Yeah that looks right. I see you messed up the order of points before, so calculated middle CPs went to the next segment instead the current.

heh65532 said:
I think leaving part of those ends missing could be bug in the Bezier library.

Unlikely, if it's a proper library. But curious this problem shows only now, when everything else seems right.
What's your code to tessellate the splines?

JoeJ said:

Yeah that looks right. I see you messed up the order of points before, so calculated middle CPs went to the next segment instead the current.

heh65532 said:
I think leaving part of those ends missing could be bug in the Bezier library.

Unlikely, if it's a proper library. But curious this problem shows only now, when everything else seems right.
What's your code to tessellate the splines?

I'm not sure what you mean by splines but I use convex hull library. And I noticed the problem with the ends missing ever since I started working with the Bezier curves

Advertisement

I thought you would use a library for splines (curves defined from small set of control points).

Personally i've tried this, which is nice, but has only cubic splines.
However, i saw such library has no magically fast solutions to calculate spline lengths, intersections, or tessellation using regular intervals either, so i kept using my own stuff.

Here's some example code to tessellate a spline:

				sVec3 p0;
				for (int i=0; i<=tess; i++) // tess of 5 would give us 4 line segments
				{
					float t = float(i) / float(tess); // gives a number going from 0 to 1
					sVec3 p1 = Spline::CatmullRom3D (t, controlPoints[0], controlPoints[1], controlPoints[2], controlPoints[3]); // calculate the point on the curve as parametrized by t
					if (i) RenderLine(p0, p1, r,g,b); // draw a straight line segment fromt eh previous to the current point
					p0 = p1;
				}

You do something similar i guess, and that's where i expect some bug.

Maybe, for some reason you get NaNs at the start and end, which you might easily figure out by stepping through this code section with the debugger.
Usually there should be no NaNs with splines, as there are no divisions. But i guess it's just some silly little thing like that, easy to fix.

JoeJ said:

I thought you would use a library for splines (curves defined from small set of control points).

Personally i've tried this, which is nice, but has only cubic splines.
However, i saw such library has no magically fast solutions to calculate spline lengths, intersections, or tessellation using regular intervals either, so i kept using my own stuff.

Here's some example code to tessellate a spline:

				sVec3 p0;
				for (int i=0; i<=tess; i++) // tess of 5 would give us 4 line segments
				{
					float t = float(i) / float(tess); // gives a number going from 0 to 1
					sVec3 p1 = Spline::CatmullRom3D (t, controlPoints[0], controlPoints[1], controlPoints[2], controlPoints[3]); // calculate the point on the curve as parametrized by t
					if (i) RenderLine(p0, p1, r,g,b); // draw a straight line segment fromt eh previous to the current point
					p0 = p1;
				}

You do something similar i guess, and that's where i expect some bug.

Maybe, for some reason you get NaNs at the start and end, which you might easily figure out by stepping through this code section with the debugger.
Usually there should be no NaNs with splines, as there are no divisions. But i guess it's just some silly little thing like that, easy to fix.

Thanks for the link but I use C#

I'll try fixing the curve ends but I noticed that with some values the curves disappear entirely and I don't know if that's due to limitation in bezier curve it self or in the library?

Posting this problem to the same thread…

there's still gap at the start

The code is quite simple, but can't figure what's wrong:

           Pen bpe1 = new Pen(Color.Blue, 5);
            Pen bpe2 = new Pen(Color.Black, 5);

            Vector2 spv = new Vector2(50, 50);
            Vector2 mpv = new Vector2(100, 50);
            Vector2 epv = new Vector2(80, 150);

            Bezier beez = new Bezier(spv, mpv, epv);

            e.Graphics.DrawEllipse(bpe2, spv.X - 2, spv.Y - 2, 4, 4);
            e.Graphics.DrawEllipse(bpe2, epv.X - 2, epv.Y - 2, 4, 4);

            IEnumerable<IPathShape> bpoints1 = beez.ToArcs();


            float efe = 1f;


            IPathShape pPoint = null; //  bpoints1.Last();
            foreach (IPathShape bp in bpoints1)
            {

                if (pPoint != null)
                {
                    Vector2 pos = bp.Position(efe);
                    Vector2 posPrev = pPoint.Position(efe);
                    
                    e.Graphics.DrawLine(bpe1, pos.X, pos.Y, posPrev.X, posPrev.Y);
                }

                pPoint = bp;
            }

heh65532 said:
there's still gap at the start

One way to fix it might be to set the starting point to the control point:

IPathShape pPoint = spv;//null; //  bpoints1.Last();
foreach (IPathShape bp in bpoints1)

Though, you work with arc segments (IPathShape), not points. So you might need to change this to using just points;

But following the intended use of the library as i guess it, this would be the ‘correct’ way:

//IPathShape pPoint = null; //  not needed, the arc segments already have it i guess
            foreach (IPathShape bp in bpoints1)
            {

                    Vector2 pos = bp.Position(0.f); // start point of the arc
                    Vector2 posPrev = bp.Position(1.f); // end point of the arc
                    
                    e.Graphics.DrawLine(bpe1, pos.X, pos.Y, posPrev.X, posPrev.Y);
            }

If this works, you could refine the detail of the curve this way, just for a test:

            foreach (IPathShape bp in bpoints1)
            {

                    Vector2 pos0 = bp.Position(0.f);
                    Vector2 pos1 = bp.Position(0.5f);
                    Vector2 pos2 = bp.Position(1.f);
                    
                    e.Graphics.DrawLine(bpe1, pos0.X, pos0.Y, pos1.X, pos1.Y);
                    e.Graphics.DrawLine(bpe1, pos1.X, pos1.Y, pos2.X, pos2.Y);
            }

All that is based on my assumption that the parameter of the Position() function gives us a point on the arc segment.

If so, the idea of the library is to approximate a spline with arc segments.
That's better than just straight lines, but it also is slower than just tessellating the spline itself at a higher resolution.
Calculating points on an arc requires trig functions, while evaluating a spline does not, so that feels a bit pointless to me.
It also means you waste performance on calculating arcs although you draw just straight lines.

Ideally, your spline library also has a point list as output, not just a list of arcs.

Advertisement

@joej Thanks for the code your solution does in fact work!

It also means you waste performance on calculating arcs although you draw just straight lines.

only call I know to work is the ToArcs()

wish there was documentation for this thing somewhere ?

Another problem…

this code is causing a random hangup:

 int max = 20;
                    foreach (IPathShape bp in bpoints)
                    {
                            Vector2 pos = bp.Position(0f);
                            Vector2 posPrev = bp.Position(1f);

                            e.Graphics.DrawLine(bpe, posPrev.X, posPrev.Y, pos.X, pos.Y);

                        max--;
                        if (max <= 0) break; // Trying to break if infinite (not working)
                    }

heh65532 said:
only call I know to work is the ToArcs() wish there was documentation for this thing somewhere ?

In case API restrictions gives you problems it's easy to do this spline stuff yourself.

Here's what i have for quadratic bezier (it's 1D, but just replace float s with 2D vectors):

inline void EvaluateBezier3 (float &pos, float &tang, float cp1, float cp2, float cp3, float t)
	{
		float u = 1.0f - t;
		
		float vv = u*u;
		float vu = u*t*2;
		float uu = t*t;
	
		pos =  vv * cp1;
		pos += vu * cp2;
		pos += uu * cp3;

		// tangent... 
		tang =  1-2*t, cp2;
		tang +=     t, cp3;
		tang +=    -u, cp1;
	}

Not sure if i use this anywhere, so i hope it's not buggy.

Here's cubic bezier:

inline void EvaluateBezier4 (sVec3 &pos, sVec3 &tang, sVec3 cp1, sVec3 cp2, sVec3 cp3, sVec3 cp4, float t)
	{
		float u = 1.0f - t;		
	
		float vvv = u*u*u;
		float vvu = u*u*t*3;
		float vuu = u*t*t*3;
		float uuu = t*t*t;
	
		pos =  vvv * cp1;
		pos += vvu * cp2;
		pos += vuu * cp3;
		pos += uuu * cp4;

		// tangent... 
		vvv = -3*u*u; // opt
		vvu = -6*u*t + 3*u*u; 
		vuu =  6*u*t - 3*t*t; 
		uuu =  3*t*t;

		tang =  vvv * cp1;
		tang += vvu * cp2;
		tang += vuu * cp3;
		tang += uuu * cp4;
	}

And Catmull Rom:

inline sVec3 CatmullRom3D (const float t, const sVec3 k0, const sVec3 k1, const sVec3 k2, const sVec3 k3) 
	{

 		const float t2 = t * t;
		const float t3 = t2 * t;

		const float w0 = (2.f * t3 - 3.f * t2 + 1.f);
        const float w1 = (t3 - 2.f * t2 + t) * -.5f;
        const float w2 = (-2.f * t3 + 3.f * t2);
        const float w3 = (t3 - t2) * .5f;

		return	(w0-w3) * k1 + (w2-w1) * k2 + w1 * k0 + w3 * k3;		

	}

	inline sVec3 CatmullRom3DTangent (const float t, const sVec3 k0, const sVec3 k1, const sVec3 k2, const sVec3 k3) 
	{
		const float t2 = t * t;

 		const float w0 = ( 6.f * t2 - 6.f * t);
        const float w1 = ( 3.f * t2 - 4.f * t + 1.f) * -.5f;
        const float w2 = (-6.f * t2 + 6.f * t);
        const float w3 = ( 3.f * t2 - 2.f * t) * .5f;
        
 		return	(w0-w3) * k1 + (w2-w1) * k2 + w1 * k0 + w3 * k3;
    }

My proposal of automated interior control points setup for cubic bezier gives the exact same result as using Catmull Rom, i'm pretty sure.
So if you end up at that, being happy with the shapes, you could just use Catmull Rom where no interior control points are needed at all.

The tangent is very useful, as it gives the velocity on any point of the curve. It would help to orient an object following a spline path for example.

Strange only looping the bezier results hangs up sometimes. Without loop there's no hang but I wonder why is this, shouldn't looping be just reading and nothing else?

This hangs up sometimes:

foreach (IPathShape bp in bpoints)
                        {
                        }

This topic is closed to new replies.

Advertisement