1

I am interesting in drawing spiral curves on a surface which look like that: pair of pants

The equation of it is:

((1.0-z)*(((x-1.0)*(x-1.0))+y*y-(1.0/3.0))*(((x+1.0)*(x+1.0))+(y*y)-(1.0/3.0)))+(z*((x*x)+(y*y)-(1.0/3.0))) == 0.0

where

x=<-1.0-sqrt(1/3),+1.0+sqrt(1/3)>
y=<-sqrt(1/3),+sqrt(1/3)>
z=< 0.0,1.0>

I found the equation on this website:

https://www.quora.com/What-is-the-mathematical-expression-which-when-plotted-looks-like-a-pair-of-pants

Then I saw that post in StackMath but I don't understand the answer...:

https://math.stackexchange.com/questions/3267636/drawing-a-pair-of-pants-using-python?answertab=active#tab-top

If someone have any idea of a process to draw spiral curves on that kind of surface, it would greatly help me. Do not hesitate to ask me more about the problem. Thank you.

Spektre
  • 49,595
  • 11
  • 110
  • 380
Bucgram
  • 13
  • 5
  • Which part of the answer you do not understand? It would help us to answer your question without repeating stuff that you already understand. – Gilles-Philippe Paillé Jul 11 '19 at 12:46
  • it is the numerical resolution I did not understand... I know how to solve linear equation but in that case it is non linear. – Bucgram Jul 11 '19 at 12:57
  • In this case, the simplest way would be to use forward Euler method. You first need to find a point `(x0, y0, z0)` on the surface. Then, use the equation `(dF/dy-..., -dF/dx-...,1/10)` in the second edit to calculate a direction vector `d`. Then, choose a timestep `dt`, and calculate a second point `(x1,y1,z1) = d * dt`. Continue stepping like this and you will obtain a curve on a surface. However, forward Euler method tends to slowly diverge from the surface. Use more advanced method to improve accuracy, e.g., Runge-Kutta, or simply reproject each point on the surface after each iteration. – Gilles-Philippe Paillé Jul 11 '19 at 13:10
  • ok i will do that ! I will try to show you what I obtain :) – Bucgram Jul 11 '19 at 13:27
  • Sure! But I realize I made a mistake in my comment, the step should be `(x1,y1,z1)=(x0,y0,z0)+d*dt`. – Gilles-Philippe Paillé Jul 11 '19 at 13:31
  • how the spiral should look like? you got 3 ends of the shape so 2 joined spirals or one going from one end to another ignoring the third? or what? There are more ways how to solve this not just analytical ... for example you could texture the object by creating a mapping function `(u,v) -> (x,y,z)` and then spiral is just line in the UV surface space (texture). Another option is create slices ... compute the cross-section of shape and any plane parallel to the base plane the shape stands on where the distance of plane to base is `u` and `v` can be any topology aligned to one of the plane axis – Spektre Jul 12 '19 at 08:41
  • @Bucgram I rewrite your equation as it was not compilable due wrong unicode characters ... and also added the ranges ... – Spektre Jul 12 '19 at 09:05
  • @Spektre I saw a looooot of scientific paper where they said they use cyllindrical parametrization but I have exactly 0 idea on how to use them honestly ^^. This is why I tried to get around the problem and go to the more mathematical solution. – Bucgram Jul 12 '19 at 13:29

1 Answers1

1

analytical approach is not the only way how to attack this (an not suited for this site anyway) so there are quite a few other options. But you did not specify how the spiral should look like as there are 3 ends of the shape and spiral have just 2...

For simplicity I assuming spiral envelope (like you wrap a rope around the shape).

So how to attack this. The usual approach is to create mapping 2D <--> 3D between some rectangular 2D area (texture) and your surface. Usually exploiting topology of the shape like cylindircal/spherical coordinates etc.

Another option is to cut your shape into individual slices and handle each slice as 2D ... Which simplifies things a lot.

To apply a spiral onto some curved surface you either project it or find intersection between shape and direction to actual point on spiral defined by parametric equation.

Here C++ example using OpenGL visualization:

//---------------------------------------------------------------------------
List<int> slice;        // start index of each z slice in pnt
List<double> pnt;       // GL_POINTS surface points
List<int> lin;          // LINE_STRIP spiral point indexes
//---------------------------------------------------------------------------
void obj_init()
    {
    const double ry=sqrt(1.0/3.0);
    const double rx=1.0+ry;
    double a,x,y,z,d,N=7.0,da,dx,dy,dz,r,rr,_zero;
    int i,j,i0,i1,ii;
    // get points of analytical surface
    pnt.num=0;
    slice.num=0;
    d=0.02; dz=0.25*d;_zero=1e-2;
    for (z=0.0;z<=1.0;z+=dz,slice.add(pnt.num))
     for (x=-rx;x<=rx;x+=d)
      for (y=-ry;y<=ry;y+=d)
       if (fabs(((1.0-z)*(((x-1.0)*(x-1.0))+y*y-(1.0/3.0))*(((x+1.0)*(x+1.0))+(y*y)-(1.0/3.0)))+(z*((x*x)+(y*y)-(1.0/3.0))))<=_zero)
        {
        pnt.add(x);
        pnt.add(y);
        pnt.add(4.0*z-2.0);
        }
    // spiral line
    lin.num=0;
    da=5.0*M_PI/180.0;
    d=pnt[slice[1]+2]-pnt[slice[0]+2];
    for (a=0.0;a<=2.0*M_PI*N;a+=da)     // go through whole spiral
        {
        z=a/(2.0*M_PI*N);               // z = f(angle,screws)
        z=4.0*z-2.0;

        j=((z+2.0)/d);
        i0=j-1; if (i0<0) i0=0;     if (i0>=slice.num) break;
        i1=j+1; if (i1<0) continue; if (i1>=slice.num) i1=slice.num-1;
        i0=slice.dat[i0];
        i1=slice.dat[i1];

        dx=cos(a);                      // direction vector to the spiral point
        dy=sin(a);
        dz=z;

        for (rr=0.0,i=i0;i<i1;i+=3)         // find point with biggest distance from center and close to a,dx,dy
            {
            x=pnt.dat[i+0];
            y=pnt.dat[i+1];
            r=(x*dx)+(y*dy)+(z*dz);     // dot( (dx,dy,dz) , (x,y,z) )
            if (r>rr){ii=i; rr=r; }     // better point found
            }
        if (ii>=0) lin.add(ii);         // add spiral point to lin[]
        }
    }
//---------------------------------------------------------------------------
void obj_draw()
    {
    int i,j;
    glColor3f(0.3,0.3,0.3);
    glBegin(GL_POINTS);
    for (i=0;i<pnt.num;i+=3) glVertex3dv(pnt.dat+i);
    glEnd();
    glColor3f(0.2,0.9,0.2);
    glBegin(GL_LINE_STRIP);
    for (i=0;i<lin.num;i+=3) glVertex3dv(pnt.dat+lin.dat[i]);
    glEnd();
    }
//---------------------------------------------------------------------------

and preview:

preview

What I did is simple:

  1. obtain surface points pnt[]

    simply test all (x,y,z) point in some BBOX volume and if your equation result for the point is close to zero I add it to the point list pnt[]. Thanks to the fact I use nested for loops with z axis being the outer loop the points are already sorted by z coordinate and the slice[] list holds the start indexes of points for each z slice so no need to slow searching in the whole list as I can go from z to slice index directly.

  2. compute parametric spiral

    using cylinder parametric equation I can compute x,y,z for any t=<0,1> If I know radius (1.0) and number of screws (N). As z is already the parameter of the shape I can use it instead of t ...

  3. computing intersection between spiral and shape

    simply loop through whole spiral. For each of its points find point that has the same direction from spiral/shape central axis and is further away... This can be done by simple dot product between the direction vectors. So simply using z coordinate of the actual tested point on spiral compute slice of the shape and test all its points remembering the max of the dot product. As the central axis is z no superpositions are needed ... The found point indexes are stored in lin[] for later use...

As you can see on the preview the spiral is a bit jagged like. This is due to non linear distribution of points. If you create a topology of the points on each slice and connect by it all the neighbor slices you can then interpolate any surface point eliminating this problem. After this you can use much less points with still better result and also render the faces instead of just wire-frame.

Another option is just to smooth the points of spiral using averaging FIR filter

Beware in the code I rescaled the z axis (z' = 4*z - 2) so it matches the plot in your link ...

[Edit1] path aligned spiral

You can create a curve/polyline or whatever describing the center of the spiral/helix. I was too lazy to construct such control points so I used the shape center points instead (computed as 0.5*(min+max) of x,y coordinate per each slice but just for a half of shape (x<0.0) for parts where the "pants" are 2 tubes... per slice. The rest I just interpolated by quadratic curve ...

From this just process all the centers of the spiral/helix, compute local TBN matrix (tangent,normal,binormal) where tangent is also tangent of the centers path and one of the axis is aligned to Y axis as there is no change (y=0) so the spiral angle will be aligned to the same axis and not twisting. From that just compute spiral angle (the center arclength from path start is the parameter) and apply the cylindrical coordinates on the t,b,n basis vectors instead of directly on x,y,z coordinates.

After that just cast ray from center into the helix direction and when hit intersection with the surface render/store the point ...

Here preview of the result:

path aligned spiral

And updated C++/VCL/GL code:

//---------------------------------------------------------------------------
List<int> slice;        // start index of each z slice in pnt
List<double> pnt;       // GL_POINTS surface points
List<double> path;      // LINE_STRIP curved helix center points
List<double> spir;      // LINE_STRIP spiral points
//---------------------------------------------------------------------------
void obj_init()
    {
    const double ry=sqrt(1.0/3.0);
    const double rx=1.0+ry;
    double a,x,y,z,zz,d,N=12.0,da,dx,dy,dz,ex,ey,ez,r,rr,_zero;
    int i,j,i0,i1,ii;

    // [get points of analytical surface]
    pnt.num=0;
    slice.num=0;
    d=0.02; dz=0.25*d;_zero=1e-2;
    for (z=0.0;z<=1.0;z+=dz,slice.add(pnt.num))
     for (x=-rx;x<=rx;x+=d)
      for (y=-ry;y<=ry;y+=d)
        if (fabs(((1.0-z)*(((x-1.0)*(x-1.0))+y*y-(1.0/3.0))*(((x+1.0)*(x+1.0))+(y*y)-(1.0/3.0)))+(z*((x*x)+(y*y)-(1.0/3.0))))<=_zero)
            {
            pnt.add(x);
            pnt.add(y);
            pnt.add(4.0*z-2.0);
            }

    // [helix center path] as center point of half-slice
    path.num=0;
    for (i1=0,j=0;j<slice.num;j++)      // all slices
        {
        i0=i1; i1=slice.dat[j];
        dx=+9; ex=-9;
        dy=+9; ey=-9;
        dz=+9; ez=-9;
        for (i=i0;i<i1;i+=3)            // single slice
            {
            x=pnt.dat[i+0];
            y=pnt.dat[i+1];
            z=pnt.dat[i+2];
            if (x<=0.0)                 // just left side of pants
                {
                if (dx>x) dx=x; if (ex<x) ex=x; // min,max
                if (dy>y) dy=y; if (ey<y) ey=y;
                if (dz>z) dz=z; if (ez<z) ez=z;
                }
            }
        if (dz>0.25) break;             // stop before pants join
        path.add(0.5*(dx+ex));
        path.add(0.5*(dy+ey));
        path.add(0.5*(dz+ez));
        }
    // smooth by averaging
    for (j=0;j<20;j++)
     for (i=3;i<path.num;i+=3)
        {
        path.dat[i+0]=0.75*path.dat[i+0]+0.25*path.dat[i-3+0];
        path.dat[i+1]=0.75*path.dat[i+1]+0.25*path.dat[i-3+1];
        path.dat[i+2]=0.75*path.dat[i+2]+0.25*path.dat[i-3+2];
        }
    // interpolate bridge between pants from last path to (0.0,0.0,0.75)
    i=path.num-3;
    dx=path.dat[i+0];
    dy=path.dat[i+1];
    dz=path.dat[i+2];
    for (a=0.0;a<1.0;a+=d)
        {
        x=dx*(1.0-a*a);
        y=dy;
        z=dz+0.5*a;
        path.add(x);
        path.add(y);
        path.add(z);
        }
    // mirror/reverse other half
    for (i=path.num-3;i>=0;i-=3)
        {
        path.add(-path.dat[i+0]);
        path.add( path.dat[i+1]);
        path.add( path.dat[i+2]);
        }

    // [path aligned spiral line envelope]
    spir.num=0; _zero=1e-2; d=0.01;
    double *p1,*p0,t[3],b[3],n[3];      // spiral center (actual,previous), TBN (tangent,binormal,normal,tangent)
    double u[3],v[3],p[3],dp[3];
    vector_sub(p,path.dat+3,path.dat);  // mirro first path point
    vector_sub(p,path.dat,p);
    p1=p;
    for (j=0;j<path.num;j+=3)           // go through whole path
        {
        // path center previous,actual
        p0=p1;
        p1=path.dat+j;
        // TBN basis vectors of the spiral slice
        vector_sub(t,p1,p0);    vector_one(t,t);    // tangent direction to next center o npath
        vector_ld(n,0.0,1.0,0.0);
        vector_mul(b,n,t);      vector_one(b,b);    // binormal perpendicular to Y axis (path does not change in Y) and tangent
        vector_mul(n,t,b);      vector_one(n,n);    // normal perpendiculer to tangent and binormal
        // angle from j as parameter and screws N
        a=N*2.0*M_PI*double(j)/double(path.num-3);
        // dp = direction to spiral point
        vector_mul(u,n,sin(a));
        vector_mul(v,b,cos(a));
        vector_add(dp,u,v);
        vector_mul(dp,dp,d);
        // center, step
        x=p1[0]; dx=dp[0];
        y=p1[1]; dy=dp[1];
        z=p1[2]; dz=dp[2];
        // find intersection between p+t*dp and surface (ray casting)
        for (r=0.0;r<2.0;r+=d,x+=dx,y+=dy,z+=dz)
            {
            zz=(z+2.0)*0.25;
            if (fabs(((1.0-zz)*(((x-1.0)*(x-1.0))+y*y-(1.0/3.0))*(((x+1.0)*(x+1.0))+(y*y)-(1.0/3.0)))+(zz*((x*x)+(y*y)-(1.0/3.0))))<=_zero)
                {
                spir.add(x);
                spir.add(y);
                spir.add(z);
                break;
                }
            }
        }
    }
//---------------------------------------------------------------------------
void obj_draw()
    {
    int i,j;
    glColor3f(0.3,0.3,0.3);
    glBegin(GL_POINTS);
    for (i=0;i<pnt.num;i+=3) glVertex3dv(pnt.dat+i);
    glEnd();
    glLineWidth(5.0);
    glColor3f(0.0,0.0,0.9);
    glBegin(GL_LINE_STRIP);
    for (i=0;i<path.num;i+=3) glVertex3dv(path.dat+i);
    glEnd();
    glColor3f(0.9,0.0,0.0);
    glBegin(GL_LINE_STRIP);
    for (i=0;i<spir.num;i+=3) glVertex3dv(spir.dat+i);
    glEnd();
    glLineWidth(1.0);
    }
//---------------------------------------------------------------------------

I also smoothed the path and compute just half of it ... as the rest is just copy/mirror ...

This approach should work for any analytical surface and center path ...

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

Some related QA readings:

Community
  • 1
  • 1
Spektre
  • 49,595
  • 11
  • 110
  • 380
  • Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/196609/discussion-on-answer-by-spektre-draw-spiral-curves-on-a-surface). – Samuel Liew Jul 18 '19 at 03:27