根据管端法线向圆柱体应用旋转
Posted
技术标签:
【中文标题】根据管端法线向圆柱体应用旋转【英文标题】:Apply Rotation to Cylinder based on Tube Ending Normal 【发布时间】:2017-02-01 14:10:49 【问题描述】:我正在尝试在 three.js 中制作一个弯曲的 3D 箭头。为了完成这项任务,我创建了一个遵循弯曲路径的Tube 和一个形状为锥形的Cylinder(通过将radiusTop 设置为很小)。它们目前看起来像这样:
我正在尝试将箭头(圆柱形状为锥形)定位在管的末端,如下所示:(Photoshopped)
我的数学不是特别强,而且对 three.js 很陌生。有人可以帮助我了解如何将两者联系起来吗?
这是我当前的代码:
import T from 'three';
var findY = function(r, x)
return Math.sqrt((r * r) - (x * x));
var radius = 25;
var x = 0;
var z = 0;
var numberOfPoints = 10;
var interval = (radius/numberOfPoints);
var points = [];
for (var i = numberOfPoints; i >= 0; i--)
var y = findY(radius, x);
points.push(new T.Vector3(x, y, z))
x = x + interval;
x = x - interval;
for (var i = numberOfPoints - 1 ; i >= 0; i--)
y = findY(radius, x) * -1;
points.push(new T.Vector3(x, y, z));
x = x - interval;
var path = new T.CatmullRomCurve3(points);
var tubeGeometry = new T.TubeGeometry(
path, //path
10, //segments
radius / 10, //radius
8, //radiusSegments
false //closed
);
var coneGeometry = new T.CylinderGeometry(
radiusTop = 0.1,
radiusBottom = radius/5,
height = 10,
radialSegments = 10,
heightSegments = 10,
openEnded = 1
);
var material = new T.MeshBasicMaterial( color: 0x00ff00 );
var tube = new T.Mesh( tubeGeometry, material );
var cone = new T.Mesh( coneGeometry, material );
// Translate and Rotate cone?
如果有人可以尝试简单地解释一下数学和编程完成的必要条件,我将不胜感激
找到位于管末端的法线 将锥体移动到正确位置感谢任何帮助!
【问题讨论】:
在我的回答中添加了edit1,2 【参考方案1】:当您可以直接在原地创建箭头时,请勿为此使用旋转。同样,弯管也可以这样做。唯一需要的就是A,B
端点定义的最后一条线段。
设A
为尖点,B
为圆盘基中心。要创建箭头,您需要 2 个额外的基向量,我们将它们称为基盘的 U,V
和半径 r
。从它们中,您可以使用如下简单的圆形公式创建圆盘点:
获取AB
端点
计算U,V
基向量
U,V
应位于箭头的圆盘底部,且应相互垂直。箭头的方向(线|BA|
)是圆盘基础法线,因此利用叉积将垂直向量返回到相乘的向量,因此:
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
创建/渲染箭头几何
那是简单的展位A,B
是三角扇的中心(需要 2),圆盘基点的计算如下:
P(ang) = B + U.r.cos(ang) + V.r.sin(ang)
所以只需将ang
循环通过一些步骤,这样您就可以获得足够的积分(通常 36 就足够了),并从它们中做两个三角扇。不要忘记最后一个圆盘点必须与第一个圆盘点相同,否则ang = 0
或360
度数上会出现丑陋的外观或孔。
如果您仍想进行轮换,则可以这样做。以与上述相同的方式计算U,V,W
,并从中构造变换矩阵。原点O
将是点B
和轴X,Y,Z
将是U,V,W
顺序取决于您的箭头型号。 W
应该与模型轴匹配。 U,V
可以按任何顺序排列。所以只需将所有向量复制到它们的位置并使用此矩阵进行渲染。欲了解更多信息,请参阅:
[备注]
如果您不知道如何计算交叉/点积或绝对值等向量运算,请参阅:
// 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] 简单的 GL 实现
我不在您的环境中编写代码,但由于投票和评论表明你们无法自行将其放在一起,考虑到您已经走到了这一步,这很奇怪,所以这里是简单的 C++/GL 示例如何做到这一点(您可以将其移植到您的环境中):
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();
这会在 XY 平面上渲染弯曲箭头,中心为 x,y,z
,大半径为 r
。 r0
是管半径,r1
是箭头基部半径。因为我没有你的曲线定义,所以我选择 XY 平面中的圆。 a0,a1,a2
是箭头开始 (a0
)、箭头开始 (a1
) 和结束 (a2
) 的角度。 pi2
只是常量 pi2=6.283185307179586476925286766559
。
我们的想法是记住实际和之前的管段圆点,以便ptab,p0,p1
存在,否则您需要计算所有内容两次。
当我直接选择 XY 平面时,我知道一个基向量是垂直于它的。第二个是垂直于它和箭头方向幸运的是圆形属性提供了它自己,因此在这种情况下不需要叉积。
如果不评论我,希望它足够清楚。
[编辑2]
我需要将它添加到我的引擎中,所以这里是 3D 版本(不仅绑定到轴对齐的箭头,而且圆锥体也弯曲了)。除了基础向量计算之外,它是相同的,我还在标题中稍微改变了角度<a0,a1>
是整个间隔,aa
是箭头大小,但在代码中它被转换为原始约定。我还添加了用于照明计算的法线。我还添加了线性箭头,其中基向量的计算没有利用圆形属性,以防你得到不同的曲线。这里的结果:
//---------------------------------------------------------------------------
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;
//---------------------------------------------------------------------------
用法:
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);
和箭头的概述(在图像的右侧):
我正在使用我的矢量库,所以这里有一些解释:
vector_mul(a[3],b[3],c[3])
是叉积 a = b x c
vector_mul(a[3],b[3],c)
是标量 a = b.c
的简单乘法
a = vector_mul(b[3],c[3])
是点积 a = (b.c)
vector_one(a[3],b[3])
是单位向量 a = b/|b|
vector_copy(a[3],b[3])
只是复制a = b
vector_add(a[3],b[3],c[3])
正在添加a = b + c
vector_sub(a[3],b[3],c[3])
正在减去 a = b - c
vector_neg(a[3],b[3])
是否定的a = -b
vector_ld(a[3],x,y,z)
正在加载 a = (x,y,z)
pos
是圆箭头的中心位置,nor
是箭头所在平面的法线。 bin
是双法线,角度从这个轴开始。应该垂直于nor
。 r,r0,r1
是箭头的半径(弯、管、锥)
线性箭头类似,dir
是箭头方向,l
是箭头大小,al
是箭头大小。
【讨论】:
您对数学的解释非常透彻,但所要求的是如何实现这一点的程序化解释。例如,如何使用three.js 找到单位向量v
和u
?
@MattVS 添加 GL 示例以上是关于根据管端法线向圆柱体应用旋转的主要内容,如果未能解决你的问题,请参考以下文章