Compute the control points for an arbitrary bezier curve

Just starting out? Need help? Post your questions and find answers here.
Seymour Clufley
Addict
Addict
Posts: 1266
Joined: Wed Feb 28, 2007 9:13 am
Location: London

Compute the control points for an arbitrary bezier curve

Post by Seymour Clufley »

The goal is: given the begin and end points of a cubic bezier curve as well as two points that it has to pass through along the way, compute the control points necessary.
I am trying to translate this code to PB, which seems to be able to do exactly this task. However, the result isn't working.

Here is the original function:

Code: Select all

// Assume we need to calculate the control
// points between (x1,y1) and (x2,y2).
// Then x0,y0 - the previous vertex,
//      x3,y3 - the next one.

double xc1 = (x0 + x1) / 2.0;
double yc1 = (y0 + y1) / 2.0;
double xc2 = (x1 + x2) / 2.0;
double yc2 = (y1 + y2) / 2.0;
double xc3 = (x2 + x3) / 2.0;
double yc3 = (y2 + y3) / 2.0;

double len1 = sqrt((x1-x0) * (x1-x0) + (y1-y0) * (y1-y0));
double len2 = sqrt((x2-x1) * (x2-x1) + (y2-y1) * (y2-y1));
double len3 = sqrt((x3-x2) * (x3-x2) + (y3-y2) * (y3-y2));

double k1 = len1 / (len1 + len2);
double k2 = len2 / (len2 + len3);

double xm1 = xc1 + (xc2 - xc1) * k1;
double ym1 = yc1 + (yc2 - yc1) * k1;

double xm2 = xc2 + (xc3 - xc2) * k2;
double ym2 = yc2 + (yc3 - yc2) * k2;

// Resulting control points. Here smooth_value is mentioned
// above coefficient K whose value should be in range [0...1].
ctrl1_x = xm1 + (xc2 - xm1) * smooth_value + x1 - xm1;
ctrl1_y = ym1 + (yc2 - ym1) * smooth_value + y1 - ym1;

ctrl2_x = xm2 + (xc2 - xm2) * smooth_value + x2 - xm2;
ctrl2_y = ym2 + (yc2 - ym2) * smooth_value + y2 - ym2;
Here is my complete PB code, including a translation of the above function:

Code: Select all

Procedure RI(img.i,title.s="",bgclr.i=#Gray,timelimit.i=0)
	
	iw.d = ImageWidth(img)
	ih.d = ImageHeight(img)
	
	simg = CreateImage(#PB_Any,iw,ih,32,bgclr)
	StartDrawing(ImageOutput(simg))
	DrawAlphaImage(ImageID(img),0,0)
	StopDrawing()
	
	If title="" : title="Report Image" : EndIf
	win = OpenWindow(#PB_Any,0,0,iw,ih,title,#PB_Window_BorderLess|#PB_Window_ScreenCentered)
	imgad = ImageGadget(#PB_Any,0,0,iw,ih,ImageID(simg))
	escapekey = 1
	spacekey = 2
	returnkey = 3
	AddKeyboardShortcut(win,#PB_Shortcut_Escape,escapekey)
	AddKeyboardShortcut(win,#PB_Shortcut_Space,spacekey)
	AddKeyboardShortcut(win,#PB_Shortcut_Return,returnkey)
	SetWindowPos_(WindowID(win),#HWND_TOPMOST,0,0,0,0,#SWP_NOMOVE|#SWP_NOSIZE)    ; gets it on top to stay 
	killtime = ElapsedMilliseconds()+timelimit
	Repeat
		we = WindowEvent()
		If we
			If we=#PB_Event_Menu
				Break
			EndIf
		Else
			Delay(10)
		EndIf
		If timelimt>0 And ElapsedMilliseconds()>killtime : Break : EndIf
	ForEver
	CloseWindow(win)
	
	FreeImage(simg)
	
EndProcedure



Structure PointD
  x.d
  y.d
EndStructure


Procedure.b ComputeCubicBezierControlPoints(*anchor1.PointD,*passthrough1.PointD,*passthrough2.PointD,*anchor2.PointD,*controlpoint1.PointD,*controlpoint2.PointD)
  
  ; calculate the control points between (anchor1) and (anchor2) so that the curve will pass through (passthrough1) and (passthrough2)
  
  xc1.d = (*anchor1\x + *passthrough1\x) / 2.0
  yc1.d = (*anchor1\y + *passthrough1\y) / 2.0
  xc2.d = (*passthrough1\x + *passthrough2\x) / 2.0
  yc2.d = (*passthrough1\y + *passthrough2\y) / 2.0
  xc3.d = (*passthrough2\x + *anchor2\x) / 2.0
  yc3.d = (*passthrough2\y + *anchor2\y) / 2.0
  
  len1.d = Sqr( (*passthrough1\x-*anchor1\x) * (*passthrough1\x-*anchor1\x) + (*passthrough1\y-*anchor1\y) * (*passthrough1\y-*anchor1\y) )
  len2.d = Sqr( (*passthrough2\x-*passthrough1\x) * (*passthrough2\x-*passthrough1\x) + (*passthrough2\y-*passthrough1\y) * (*passthrough2\y-*passthrough1\y) )
  len3.d = Sqr( (*anchor2\x-*passthrough2\x) * (*anchor2\x-*passthrough2\x) + (*anchor2\y-*passthrough2\y) * (*anchor2\y-*passthrough2\y) )
  
  k1.d = len1 / (len1 + len2)
  k2.d = len2 / (len2 + len3)
  
  xm1.d = xc1 + (xc2 - xc1) * k1
  ym1.d = yc1 + (yc2 - yc1) * k1
  
  xm2.d = xc2 + (xc3 - xc2) * k2
  ym2.d = yc2 + (yc3 - yc2) * k2
  
  
  ; Resulting control points. Here smooth_value is mentioned above coefficient K whose value should be in range [0...1]
  smooth_value.d = 0.1
  
  *controlpoint1\x = xm1 + (xc2 - xm1) * smooth_value + *passthrough1\x - xm1
  *controlpoint1\y = ym1 + (yc2 - ym1) * smooth_value + *passthrough1\y - ym1
  Debug "Control Point 1: "+StrD(*controlpoint1\x,0)+","+StrD(*controlpoint1\y,0)
  
  *controlpoint2\x = xm2 + (xc2 - xm2) * smooth_value + *passthrough2\x - xm2
  *controlpoint2\y = ym2 + (yc2 - ym2) * smooth_value + *passthrough2\y - ym2
  Debug "Control Point 2: "+StrD(*controlpoint2\x,0)+","+StrD(*controlpoint2\y,0)
  
EndProcedure



Procedure.b RasterizeCubicBezier(*anchor1.PointD,*control1.PointD,*control2.PointD,*anchor2.PointD,NUM_STEPS.i,Array rstpnt.PointD(1))
  
  dx1.d = *control1\x - *anchor1\x
  dy1.d = *control1\y - *anchor1\y
  dx2.d = *control2\x - *control1\x
  dy2.d = *control2\y - *control1\y
  dx3.d = *anchor2\x - *control2\x
  dy3.d = *anchor2\y - *control2\y
  
  subdiv_step.d  = 1.0 / (NUM_STEPS + 1)
  subdiv_step2.d = subdiv_step*subdiv_step
  subdiv_step3.d = subdiv_step*subdiv_step*subdiv_step
  
  pre1.d = 3.0 * subdiv_step
  pre2.d = 3.0 * subdiv_step2
  pre4.d = 6.0 * subdiv_step2
  pre5.d = 6.0 * subdiv_step3
  
  tmp1x.d = *anchor1\x - *control1\x * 2.0 + *control2\x
  tmp1y.d = *anchor1\y - *control1\y * 2.0 + *control2\y
  
  tmp2x.d = (*control1\x - *control2\x)*3.0 - *anchor1\x + *anchor2\x
  tmp2y.d = (*control1\y - *control2\y)*3.0 - *anchor1\y + *anchor2\y
  
  fx.d = *anchor1\x
  fy.d = *anchor1\y
  
  dfx.d = (*control1\x - *anchor1\x)*pre1 + tmp1x*pre2 + tmp2x*subdiv_step3
  dfy.d = (*control1\y - *anchor1\y)*pre1 + tmp1y*pre2 + tmp2y*subdiv_step3
  
  ddfx.d = tmp1x*pre4 + tmp2x*pre5
  ddfy.d = tmp1y*pre4 + tmp2y*pre5
  
  dddfx.d = tmp2x*pre5
  dddfy.d = tmp2y*pre5
  
  ReDim rstpnt(NUM_STEPS+2)
  For p = 1 To NUM_STEPS
    fx + dfx
    fy + dfy
    dfx + ddfx
    dfy + ddfy
    ddfx + dddfx
    ddfy + dddfy
    rstpnt(p)\x = fx
    rstpnt(p)\y = fy
  Next p
  p+1
  rstpnt(p)\x = *anchor2\x
  rstpnt(p)\y = *anchor2\y
  
EndProcedure



iw.d = 1200
ih.d = 800

anchor1.PointD
passthrough1.PointD
passthrough2.PointD
anchor2.PointD
anchor1\x = 0
anchor1\y = ih
passthrough1\x = iw * 0.25
passthrough1\y = ih * 0.975
passthrough2\x = iw * 0.85
passthrough2\y = ih * 0.65
anchor2\x = iw
anchor2\y = 0

ComputeCubicBezierControlPoints(@anchor1,@passthrough1,@passthrough2,@anchor2,@ctrl1.PointD,@ctrl2.PointD)

rpnts = 1000
Dim rpnt.PointD(1)
RasterizeCubicBezier(@anchor1,@ctrl1,@ctrl2,@anchor2,rpnts,rpnt())

img = CreateImage(#PB_Any,iw,ih)
StartDrawing(ImageOutput(img))
For a = 1 To rpnts
  x = rpnt(a)\x
  y = rpnt(a)\y
  Circle(x,y,1,#White)
Next a
Circle(anchor1\x,anchor1\y,10,#Green)
Circle(passthrough1\x,passthrough1\y,10,#Blue)
Circle(passthrough2\x,passthrough2\y,10,#Blue)
Circle(ctrl1\x,ctrl1\y,20,#Yellow)
Circle(ctrl2\x,ctrl2\y,20,#Yellow)
Circle(anchor2\x,anchor2\y,10,#Red)
StopDrawing()
RI(img)
Unfortunately, the bezier curve that results from this code does not pass through either of the arbitrary passthrough points! I don't know what is going wrong. If anyone could help with this, I would be most grateful. The man who wrote the original function explains how it works in detail on the page.

Thank you for your time.
JACK WEBB: "Coding in C is like sculpting a statue using only sandpaper. You can do it, but the result wouldn't be any better. So why bother? Just use the right tools and get the job done."