Apply Rotation to Cylinder based on Tube Ending Normal

后端 未结 1 582
盖世英雄少女心
盖世英雄少女心 2020-12-11 18:12

I am attempting to make a curved 3D arrow in three.js. To accomplish this task, I have created a Tube that follows a curved path and a Cylinder shaped as a cone (by setting

相关标签:
1条回答
  • 2020-12-11 18:35

    Do not use rotation for this when you can create the arrowhead directly in place. Similarly the bended tube can be done this way too. Only thing you need for it is the last line segment defined by A,B endpoints.

    Let A be the sharp point and B the disc base center. To create arrowhead you need 2 additional basis vectors let call them U,V and radius r of base disc. From them you can create disc points with simple circle formula like this:

    1. obtain AB endpoints

    2. compute U,V basis vectors

      The U,V should lie in the disc base of arrowhead and should be perpendicular to each other. direction of the arrowhead (line |BA|) is the disc base normal so exploit cross product which returns perpendicular vector to the multiplied ones so:

      W = B-A;
      W /= |W|;    // unit vector
      T = (1,0,0); // temp any non zero vector not parallel to W
      if ( |(W.T)|>0.75 ) T = (0,1,0); // if abs dot product of T and W is close to 1 it means they are close to parallel so chose different T
      U = (T x W) // U is perpendicular to T,W
      V = (U x W) // V is perpendicular to U,W
      
    3. create/render arrowhead geometry

      That is easy booth A,B are centers of triangle fan (need 2) and the disc base points are computed like this:

      P(ang) = B + U.r.cos(ang) + V.r.sin(ang)
      

      So just loop ang through the whole circle with some step so you got enough points (usually 36 is enough) and do both triangle fans from them. Do not forget the last disc point must be the same as the first one otherwise you will got ugly seems or hole on the ang = 0 or 360 deg.

    If you still want to go for rotations instead then this is doable like this. compute U,V,W in the same way as above and construct transformation matrix from them. the origin O will be point B and axises X,Y,Z will be U,V,W the order depends on your arrowhead model. W should match the model axis. U,V can be in any order. So just copy all the vectors to their places and use this matrix for rendering. For more info see:

    • Understanding 4x4 homogenous transform matrices

    [Notes]

    If you do not know how to compute vector operations like cross/dot products or absolute value see:

    // cross product: W = U x V
    W.x=(U.y*V.z)-(U.z*V.y)
    W.y=(U.z*V.x)-(U.x*V.z)
    W.z=(U.x*V.y)-(U.y*V.x)
    // dot product: a = (U.V)
    a=U.x*V.x+U.y*V.y+U.z*V.z
    // abs of vector a = |U|
    a=sqrt((U.x*U.x)+(U.y*U.y)+(U.z*U.z))
    

    [Edit1] simple GL implementation

    I do not code in your environment but as downvote and comment suggest you guys are not able to put this together on your own which is odd considering you got this far so here simple C++/GL exmaple of how to do this (you can port this to your environment):

    void glArrowRoundxy(GLfloat x0,GLfloat y0,GLfloat z0,GLfloat r,GLfloat r0,GLfloat r1,GLfloat a0,GLfloat a1,GLfloat a2)
        {
        const int _glCircleN=50;    // points per circle
        const int n=3*_glCircleN;
        int i,j,ix,e;
        float x,y,z,x1,y1,z1,a,b,da,db=pi2/(_glCircleN-1);
        float ux,uy,uz,vx,vy,vz,u,v;
        // buffers
        GLfloat ptab[6*_glCircleN],*p0,*p1,*n0,*n1,*p;
        p0=ptab+(0*_glCircleN);     // previous tube segment circle points
        p1=ptab+(3*_glCircleN);     // actual tube segment circle points
        da=+db; if (a0>a1) da=-db;  // main angle step direction
        ux=0.0;                     // U is normal to arrow plane
        uy=0.0;
        uz=1.0;
        // arc interpolation a=<a0,a1>
        for (e=1,j=0,a=a0;e;j++,a+=da)
            {
            // end conditions
            if ((da>0.0)&&(a>=a1)) { a=a1; e=0; }
            if ((da<0.0)&&(a<=a1)) { a=a1; e=0; }
            // compute actual tube ceneter
            x1=x0+(r*cos(a));
            y1=y0+(r*sin(a));
            z1=z0;
            // V is direction from (x0,y0,z0) to (x1,y1,z1)
            vx=x1-x0;
            vy=y1-y0;
            vz=z1-z0;
            // and unit of coarse
            b=sqrt((vx*vx)+(vy*vy)+(vz*vz));
            if (b>1e-6) b=1.0/b; else b=0.0;
            vx*=b;
            vy*=b;
            vz*=b;
            // tube segment
            for (ix=0,b=0.0,i=0;i<_glCircleN;i++,b+=db)
                {
                u=r0*cos(b);
                v=r0*sin(b);
                p1[ix]=x1+(ux*u)+(vx*v); ix++;
                p1[ix]=y1+(uy*u)+(vy*v); ix++;
                p1[ix]=z1+(uz*u)+(vz*v); ix++;
                }
            if (!j)
                {
                glBegin(GL_TRIANGLE_FAN);
                glVertex3f(x1,y1,z1);
                for (ix=0;ix<n;ix+=3) glVertex3fv(p1+ix);
                glEnd();
                }
            else{
                glBegin(GL_QUAD_STRIP);
                for (ix=0;ix<n;ix+=3)
                    {
                    glVertex3fv(p0+ix);
                    glVertex3fv(p1+ix);
                    }
                glEnd();
                }
            // swap buffers
            p=p0; p0=p1; p1=p;
            p=n0; n0=n1; n1=p;
            }
        // arrowhead a=<a1,a2>
        for (ix=0,b=0.0,i=0;i<_glCircleN;i++,b+=db)
            {
            u=r1*cos(b);
            v=r1*sin(b);
            p1[ix]=x1+(ux*u)+(vx*v); ix++;
            p1[ix]=y1+(uy*u)+(vy*v); ix++;
            p1[ix]=z1+(uz*u)+(vz*v); ix++;
            }
        glBegin(GL_TRIANGLE_FAN);
        glVertex3f(x1,y1,z1);
        for (ix=0;ix<n;ix+=3) glVertex3fv(p1+ix);
        glEnd();
        x1=x0+(r*cos(a2));
        y1=y0+(r*sin(a2));
        z1=z0;
        glBegin(GL_TRIANGLE_FAN);
        glVertex3f(x1,y1,z1);
        for (ix=n-3;ix>=0;ix-=3) glVertex3fv(p1+ix);
        glEnd();
        }
    

    This renders bended arrow in XY plane with center x,y,z and big radius r. The r0 is tube radius and r1 is arrowhead base radius. As I do not have your curve definition I choose circle in XY plane. The a0,a1,a2 are angles where arrow starts (a0), arrowhead starts (a1) and ends (a2). The pi2 is just constant pi2=6.283185307179586476925286766559.

    The idea is to remember actual and previous tube segment circle points so there for the ptab,p0,p1 otherwise you would need to compute everything twice.

    As I chose XY plane directly then I know that one base vector is normal to it. and second is perpendicular to it and to arrow direction luckily circle properties provides this on its own therefore no need for cross products in this case.

    Hope it is clear enough if not comment me.

    [Edit2]

    I needed to add this to my engine so here is the 3D version (not bound just to axis aligned arrows and the cone is bended too). It is the same except the basis vector computation and I also change the angles a bit in the header <a0,a1> is the whole interval and aa is the arrowhead size but latter in code it is converted to the original convention. I added also normals for lighting computations. I added also linear Arrow where the computation of basis vectors is not taking advantage of circle properties in case you got different curve. Here result:

    //---------------------------------------------------------------------------
    const int _glCircleN=50;    // points per circle
    //---------------------------------------------------------------------------
    void glCircleArrowxy(GLfloat x0,GLfloat y0,GLfloat z0,GLfloat r,GLfloat r0,GLfloat r1,GLfloat a0,GLfloat a1,GLfloat aa)
        {
        double pos[3]={ x0, y0, z0};
        double nor[3]={0.0,0.0,1.0};
        double bin[3]={1.0,0.0,0.0};
        glCircleArrow3D(pos,nor,bin,r,r0,r1,a0,a1,aa);
        }
    //---------------------------------------------------------------------------
    void glCircleArrowyz(GLfloat x0,GLfloat y0,GLfloat z0,GLfloat r,GLfloat r0,GLfloat r1,GLfloat a0,GLfloat a1,GLfloat aa)
        {
        double pos[3]={ x0, y0, z0};
        double nor[3]={1.0,0.0,0.0};
        double bin[3]={0.0,1.0,0.0};
        glCircleArrow3D(pos,nor,bin,r,r0,r1,a0,a1,aa);
        }
    //---------------------------------------------------------------------------
    void glCircleArrowxz(GLfloat x0,GLfloat y0,GLfloat z0,GLfloat r,GLfloat r0,GLfloat r1,GLfloat a0,GLfloat a1,GLfloat aa)
        {
        double pos[3]={ x0, y0, z0};
        double nor[3]={0.0,1.0,0.0};
        double bin[3]={0.0,0.0,1.0};
        glCircleArrow3D(pos,nor,bin,r,r0,r1,a0,a1,aa);
        }
    //---------------------------------------------------------------------------
    void glCircleArrow3D(double *pos,double *nor,double *bin,double r,double r0,double r1,double a0,double a1,double aa)
        {
    //  const int _glCircleN=20;    // points per circle
        int e,i,j,N=3*_glCircleN;
        double U[3],V[3],u,v;
        double a,b,da,db=pi2/double(_glCircleN-1),a2,rr;
        double *ptab,*p0,*p1,*n0,*n1,*pp,p[3],q[3],c[3],n[3],tan[3];
        // buffers
        ptab=new double [12*_glCircleN]; if (ptab==NULL) return;
        p0=ptab+(0*_glCircleN);
        n0=ptab+(3*_glCircleN);
        p1=ptab+(6*_glCircleN);
        n1=ptab+(9*_glCircleN);
        // prepare angles
        a2=a1; da=db; aa=fabs(aa);
        if (a0>a1) { da=-da; aa=-aa; }
        a1-=aa;
        // compute missing basis vectors
        vector_copy(U,nor);         // U is normal to arrow plane
        vector_mul(tan,nor,bin);    // tangent is perpendicular to normal and binormal
        // arc interpolation a=<a0,a2>
        for (e=0,j=0,a=a0;e<5;j++,a+=da)
            {
            // end conditions
            if (e==0)   // e=0
                {
                if ((da>0.0)&&(a>=a1)) { a=a1; e++; }
                if ((da<0.0)&&(a<=a1)) { a=a1; e++; }
                rr=r0;
                }
            else{       // e=1,2,3,4
                if ((da>0.0)&&(a>=a2)) { a=a2; e++; }
                if ((da<0.0)&&(a<=a2)) { a=a2; e++; }
                rr=r1*fabs(divide(a-a2,a2-a1));
                }
            // compute actual tube segment center c[3]
            u=r*cos(a);
            v=r*sin(a);
            vector_mul(p,bin,u);
            vector_mul(q,tan,v);
            vector_add(c,p,  q);
            vector_add(c,c,pos);
            // V is unit direction from arrow center to tube segment center
            vector_sub(V,c,pos);
            vector_one(V,V);
            // tube segment interpolation
            for (b=0.0,i=0;i<N;i+=3,b+=db)
                {
                u=cos(b);
                v=sin(b);
                vector_mul(p,U,u);      // normal
                vector_mul(q,V,v);
                vector_add(n1+i,p,q);
                vector_mul(p,n1+i,rr);  // vertex
                vector_add(p1+i,p,c);
                }
            if (e>1)                    // recompute normals for cone
                {
                for (i=3;i<N;i+=3)
                    {
                    vector_sub(p,p0+i  ,p1+i);
                    vector_sub(q,p1+i-3,p1+i);
                    vector_mul(p,p,q);
                    vector_one(n1+i,p);
                    }
                vector_sub(p,p0    ,p1);
                vector_sub(q,p1+N-3,p1);
                vector_mul(p,q,p);
                vector_one(n1,p);
                if (da>0.0) for (i=0;i<N;i+=3) vector_neg(n1+i,n1+i);
                if (e==  3) for (i=0;i<N;i+=3) vector_copy(n0+i,n1+i);
                }
            // render base disc
            if (!j)
                {
                vector_mul(n,U,V);
                glBegin(GL_TRIANGLE_FAN);
                glNormal3dv(n);
                glVertex3dv(c);
                if (da<0.0) for (i=N-3;i>=0;i-=3) glVertex3dv(p1+i);
                else        for (i=  0;i< N;i+=3) glVertex3dv(p1+i);
                glEnd();
                }
            // render tube
            else{
                glBegin(GL_QUAD_STRIP);
                if (da<0.0) for (i=0;i<N;i+=3)
                    {
                    glNormal3dv(n1+i); glVertex3dv(p1+i);
                    glNormal3dv(n0+i); glVertex3dv(p0+i);
                    }
                else for (i=0;i<N;i+=3)
                    {
                    glNormal3dv(n0+i); glVertex3dv(p0+i);
                    glNormal3dv(n1+i); glVertex3dv(p1+i);
                    }
                glEnd();
                }
            // swap buffers
            pp=p0; p0=p1; p1=pp;
            pp=n0; n0=n1; n1=pp;
            // handle r0 -> r1 edge
            if (e==1) a-=da;
            if ((e==1)||(e==2)||(e==3)) e++;
            }
        // release buffers
        delete[] ptab;
        }
    //---------------------------------------------------------------------------
    void glLinearArrow3D(double *pos,double *dir,double r0,double r1,double l,double al)
        {
    //  const int _glCircleN=20;    // points per circle
        int e,i,N=3*_glCircleN;
        double U[3],V[3],W[3],u,v;
        double a,da=pi2/double(_glCircleN-1),r,t;
        double *ptab,*p0,*p1,*n1,*pp,p[3],q[3],c[3],n[3];
        // buffers
        ptab=new double [9*_glCircleN]; if (ptab==NULL) return;
        p0=ptab+(0*_glCircleN);
        p1=ptab+(3*_glCircleN);
        n1=ptab+(6*_glCircleN);
        // compute basis vectors
        vector_one(W,dir);
        vector_ld(p,1.0,0.0,0.0);
        vector_ld(q,0.0,1.0,0.0);
        vector_ld(n,0.0,0.0,1.0);
        a=fabs(vector_mul(W,p));            pp=p; t=a;
        a=fabs(vector_mul(W,q)); if (t>a) { pp=q; t=a; }
        a=fabs(vector_mul(W,n)); if (t>a) { pp=n; t=a; }
        vector_mul(U,W,pp);
        vector_mul(V,U,W);
        vector_mul(U,V,W);
        for (e=0;e<4;e++)
            {
            // segment center
            if (e==0) { t=0.0;  r= r0; }
            if (e==1) { t=l-al; r= r0; }
            if (e==2) { t=l-al; r= r1; }
            if (e==3) { t=l;    r=0.0; }
            vector_mul(c,W,t);
            vector_add(c,c,pos);
            // tube segment interpolation
            for (a=0.0,i=0;i<N;i+=3,a+=da)
                {
                u=cos(a);
                v=sin(a);
                vector_mul(p,U,u);      // normal
                vector_mul(q,V,v);
                vector_add(n1+i,p,q);
                vector_mul(p,n1+i,r);   // vertex
                vector_add(p1+i,p,c);
                }
            if (e>2)                    // recompute normals for cone
                {
                for (i=3;i<N;i+=3)
                    {
                    vector_sub(p,p0+i  ,p1+i);
                    vector_sub(q,p1+i-3,p1+i);
                    vector_mul(p,p,q);
                    vector_one(n1+i,p);
                    }
                vector_sub(p,p0    ,p1);
                vector_sub(q,p1+N-3,p1);
                vector_mul(p,q,p);
                vector_one(n1,p);
                }
            // render base disc
            if (!e)
                {
                vector_neg(n,W);
                glBegin(GL_TRIANGLE_FAN);
                glNormal3dv(n);
                glVertex3dv(c);
                for (i=0;i<N;i+=3) glVertex3dv(p1+i);
                glEnd();
                }
            // render tube
            else{
                glBegin(GL_QUAD_STRIP);
                for (i=0;i<N;i+=3)
                    {
                    glNormal3dv(n1+i);
                    glVertex3dv(p0+i);
                    glVertex3dv(p1+i);
                    }
                glEnd();
                }
            // swap buffers
            pp=p0; p0=p1; p1=pp;
            }
        // release buffers
        delete[] ptab;
        }
    //---------------------------------------------------------------------------
    

    usage:

    glColor3f(0.5,0.5,0.5);
    
    glCircleArrowyz(+3.5,0.0,0.0,0.5,0.1,0.2,0.0*deg,+270.0*deg,45.0*deg);
    
    glCircleArrowyz(-3.5,0.0,0.0,0.5,0.1,0.2,0.0*deg,-270.0*deg,45.0*deg);
    glCircleArrowxz(0.0,+3.5,0.0,0.5,0.1,0.2,0.0*deg,+270.0*deg,45.0*deg);
    glCircleArrowxz(0.0,-3.5,0.0,0.5,0.1,0.2,0.0*deg,-270.0*deg,45.0*deg);
    glCircleArrowxy(0.0,0.0,+3.5,0.5,0.1,0.2,0.0*deg,+270.0*deg,45.0*deg);
    glCircleArrowxy(0.0,0.0,-3.5,0.5,0.1,0.2,0.0*deg,-270.0*deg,45.0*deg);
    glColor3f(0.2,0.2,0.2);
    glLinearArrow3D(vector_ld(+2.0,0.0,0.0),vector_ld(+1.0,0.0,0.0),0.1,0.2,2.0,0.5);
    glLinearArrow3D(vector_ld(-2.0,0.0,0.0),vector_ld(-1.0,0.0,0.0),0.1,0.2,2.0,0.5);
    glLinearArrow3D(vector_ld(0.0,+2.0,0.0),vector_ld(0.0,+1.0,0.0),0.1,0.2,2.0,0.5);
    glLinearArrow3D(vector_ld(0.0,-2.0,0.0),vector_ld(0.0,-1.0,0.0),0.1,0.2,2.0,0.5);
    glLinearArrow3D(vector_ld(0.0,0.0,+2.0),vector_ld(0.0,0.0,+1.0),0.1,0.2,2.0,0.5);
    glLinearArrow3D(vector_ld(0.0,0.0,-2.0),vector_ld(0.0,0.0,-1.0),0.1,0.2,2.0,0.5);
    

    and overview of the arows (on the right side of image):

    I am using my vector lib so here are some explanations:


    vector_mul(a[3],b[3],c[3]) is cross product a = b x c
    vector_mul(a[3],b[3],c) is simple multiplication by scalar a = b.c
    a = vector_mul(b[3],c[3]) is dot product a = (b.c)
    vector_one(a[3],b[3]) is unit vector a = b/|b|
    vector_copy(a[3],b[3]) is just copy a = b
    vector_add(a[3],b[3],c[3]) is adding a = b + c
    vector_sub(a[3],b[3],c[3]) is substracting a = b - c
    vector_neg(a[3],b[3]) is negation a = -b
    vector_ld(a[3],x,y,z) is just loading a = (x,y,z)

    The pos is the center position of your circle arrow and nor is normal of the plane in which the arrow lies. bin is bi-normal and the angles are starting from this axis. should be perpendicular to nor. r,r0,r1 are the radiuses of the arrow (bend,tube,cone)

    The linear arrow is similar the dir is direction of the arrow, l is arrow size and al is arrowhead size.

    0 讨论(0)
提交回复
热议问题