How about using piecewise cubics for this... Does not really matter if BEZIER SPLINE or whatever. You just need to connect the patches with proper point call sequence which you clearly did not do. Here is my example using my Interpolation cubics with proper call sequence:
Gray is the turtle graphics original Hilbert curve and Aqua is the interpolated cubic curve for the same points ...
Was curious so I wanted to implement this but Took me a while to figure out and implement 2D Hilbert curves (I used turtle graphics) as I never used them before. Here OpenGL VCL C++ source code for this:
//---------------------------------------------------------------------------
double ha=0.0; AnsiString hs=""; // turtle graphics
List<double> pnt; // 2D point list
//---------------------------------------------------------------------------
void turtle_mirror(AnsiString &s) // swap l,r
{
int i,l; char c;
for (l=s.Length(),i=1;i<=l;i++)
{
c=s[i];
if (c=='l') s[i]='r';
if (c=='r') s[i]='l';
}
}
//---------------------------------------------------------------------------
void turtle_hilbert(AnsiString &s,double &a,int n) // compute hilbert curve turtle string s , line segment size a for n iterations
{
int i,l; char c;
AnsiString s0;
if (s=="") { l=1; s="frfrf"; } // init hilbert curve assuming starting bottom left turned up
for (i=0;i<n;i++)
{
s0=s; // generator
if (int(i&1)==0) // even pass
{
turtle_mirror(s0); s ="r"+s0+"rf";
turtle_mirror(s0); s+=s0+"lfl"+s0;
turtle_mirror(s0); s+="fr"+s0;
}
else{ // odd pass
turtle_mirror(s0); s ="r"+s0+"f";
turtle_mirror(s0); s+=s0+"fl"+s0;
turtle_mirror(s0); s+="rfr"+s0;
}
l=l+l+1; // adjust scale
}
a=1.0/double(l);
}
//---------------------------------------------------------------------------
void turtle_draw(double x,double y,double dx,double dy,const AnsiString &s)
{
int i,l; char c;
double q;
l=s.Length();
glBegin(GL_LINE_STRIP);
glVertex2d(x,y);
for (i=1;i<=l;i++)
{
c=s[i];
if (c=='f') { x+=dx; y+=dy; glVertex2d(x,y); }
if (c=='l') { q=dx; dx=-dy; dy= q; }
if (c=='r') { q=dx; dx= dy; dy=-q; }
}
glEnd();
}
//---------------------------------------------------------------------------
void turtle_compute(List<double> &xy,double x,double y,double dx,double dy,const AnsiString &s)
{
int i,l; char c;
double q;
l=s.Length();
xy.num=0; // clear list
xy.add(x); // add point
xy.add(y);
for (i=1;i<=l;i++)
{
c=s[i];
if (c=='f') { x+=dx; y+=dy; xy.add(x); xy.add(y); }
if (c=='l') { q=dx; dx=-dy; dy= q; }
if (c=='r') { q=dx; dx= dy; dy=-q; }
}
glEnd();
}
//---------------------------------------------------------------------------
void gl_draw()
{
//_redraw=false;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
GLint id;
glUseProgram(prog_id);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glDisable(GL_DEPTH_TEST);
glDisable(GL_TEXTURE_2D);
// hilber curve covering <-1,+1> range
if (hs=="")
{
turtle_hilbert(hs,ha,3); // init turtle string
turtle_compute(pnt,-0.9,-0.9,0.0,1.8*ha,hs); // init point list for curve fit
}
// render hs,ha as turtle graphics
glColor3f(0.4,0.4,0.4);
turtle_draw(-0.9,-0.9,0.0,1.8*ha,hs);
// render pnt[] as interpolation cubics
int i,j;
double d1,d2,t,tt,ttt,*p0,*p1,*p2,*p3,a0[2],a1[2],a2[2],a3[2],p[2];
glColor3f(0.2,0.7,1.0);
glBegin(GL_LINE_STRIP);
for (i=-2;i<pnt.num;i+=2) // process whole curve
{ // here create the call sequence (select control points)
j=i-2; if (j<0) j=0; if (j>=pnt.num) j=pnt.num-2; p0=pnt.dat+j;
j=i ; if (j<0) j=0; if (j>=pnt.num) j=pnt.num-2; p1=pnt.dat+j;
j=i+2; if (j<0) j=0; if (j>=pnt.num) j=pnt.num-2; p2=pnt.dat+j;
j=i+4; if (j<0) j=0; if (j>=pnt.num) j=pnt.num-2; p3=pnt.dat+j;
for (j=0;j<2;j++) // compute curve parameters
{
d1=0.5*(p2[j]-p0[j]);
d2=0.5*(p3[j]-p1[j]);
a0[j]=p1[j];
a1[j]=d1;
a2[j]=(3.0*(p2[j]-p1[j]))-(2.0*d1)-d2;
a3[j]=d1+d2+(2.0*(-p2[j]+p1[j]));
}
for (t=0.0;t<=1.0;t+=0.05) // single curve patch/segment
{
tt=t*t;
ttt=tt*t;
for (j=0;j<2;j++) p[j]=a0[j]+(a1[j]*t)+(a2[j]*tt)+(a3[j]*ttt);
glVertex2dv(p);
}
}
glEnd();
glFlush();
SwapBuffers(hdc);
}
//---------------------------------------------------------------------------
I do not think the curves would self-intersect and for all the n
I tried they did not because the rounding is confined very near to the edges and increasing recursion will scale it too so the gap remains.
I used AnsiString
type from VCL which is string (accessed from index 1
!) capable of string arithmetics like adding string etc ...
I also use mine dynamic list template so:
List<double> xxx;
is the same as double xxx[];
xxx.add(5);
adds 5
to end of the list
xxx[7]
access array element (safe)
xxx.dat[7]
access array element (unsafe but fast direct access)
xxx.num
is the actual used size of the array
xxx.reset()
clears the array and set xxx.num=0
xxx.allocate(100)
preallocate space for 100
items
for the curve points you can use any dynamic list structure you got at your disposal instead.
The rendering is done by OpenGL 1.0 (old style api) so porting that should be easy ...
Function:
void turtle_hilbert(AnsiString &s,double &a,int n);
will generate turtle graphics string s
representing the n
-th Hilbert curve iteration. The a
is just scaling (line length) so the entire curve fit into unit square <0,1>
.
For more info see related: