求两向量超过180度的夹角

clever101
博客专家认证
2011-07-05 06:11:59
一般通过反余弦来求两向量夹角,计算两个向量的夹角的公式如下:

角度 = 向量1 点乘 向量2 / sqrt(|向量1|*|向量2|);

代码如下,这段程序用于计算两个向量的幅度,范围在0到180度:


struct Point
{
float x,y;
};

float Angle(Point cen,Point first,Point second)
{
float dx1,dx2,dy1,dy2;
float angle;

dx1 = first.x - cen.x;
dx2 = second.x - cen.x;
dy1 = first.y - cen.y;
dy2 = second.y - cen.y;

float c=(float)sqrt(dx1*dx1+dx2*dx2+dy1*dy1+dy2*dy2);
if(c==0) // 这里浮点数比较不对,大家明白和零比较就行
return -1;
angle = (float)acos((dx1*dx2+dy1*dy2)/c);

return angle;
}



现在我想求两向量超过180度(同时小于360度)的夹角,以顺时针为正向,怎么求?
...全文
5344 35 打赏 收藏 转发到动态 举报
写回复
用AI写文章
35 条回复
切换为时间正序
请发表友善的回复…
发表回复
lm_whales 2013-12-01
  • 打赏
  • 举报
回复
方法一,计算方位角 a = atan2(x,y) //0~90度 的弧度表示 其他根据象限处理 两角相减,得到角度差 d 统一化为 0~360 或者 -180 ~180 方法2 求向量点积,可以求夹角出余弦。和360 的差是较大的角。
victor_woo 2013-09-12
  • 打赏
  • 举报
回复
引用 楼主 clever101 的回复:
一般通过反余弦来求两向量夹角,计算两个向量的夹角的公式如下: 角度 = 向量1 点乘 向量2 / sqrt(|向量1|*|向量2|); 代码如下,这段程序用于计算两个向量的幅度,范围在0到180度:

struct Point 
{
    float x,y;
};

float Angle(Point cen,Point first,Point second)
{
   float dx1,dx2,dy1,dy2;
   float angle;

   dx1 = first.x - cen.x;
   dx2 = second.x - cen.x;
   dy1 = first.y - cen.y;
   dy2 = second.y - cen.y;
   
   float c=(float)sqrt(dx1*dx1+dx2*dx2+dy1*dy1+dy2*dy2);
   if(c==0)    // 这里浮点数比较不对,大家明白和零比较就行
     return -1;
   angle = (float)acos((dx1*dx2+dy1*dy2)/c);

   return angle;
}

现在我想求两向量超过180度(同时小于360度)的夹角,以顺时针为正向,怎么求?
1:选定基准向量和基准点,把两个向量都处理为基准点发出的射线 2:计算出两向量在0-180之间的夹角 3:判断第二向量射线相对于基准向量方向的位置,沿基准向量方向走,第二向量在其左侧右侧? 4:如果右侧,<180;如果左侧,360-夹角
victor_woo 2013-09-12
  • 打赏
  • 举报
回复
两向量的夹角一定是在0-180度之间 和两点之间的距离是一样的概念 不涉及顺逆方向
zyc102030058 2013-09-10
  • 打赏
  • 举报
回复 1
http://bbs.csdn.net/topics/80026626 我觉得15楼正解!
hellocdw 2012-03-16
  • 打赏
  • 举报
回复
楼主,我也遇到这个问题了,你搞出来了,给答案啊。。。。。。
beyond071 2011-07-06
  • 打赏
  • 举报
回复
楼主,我看你前面的要求是要求大于180的夹角。angle从acos算出来的时候都是在[0,PI]内的,你不是要求对应的那个大于180度的夹角么,所以用2*PI - angle
clever101 2011-07-06
  • 打赏
  • 举报
回复
[Quote=引用 18 楼 beyond071 的回复:]

噢 没看清楼主是要求大于180度的夹角...
C/C++ code

//夹角范围[-PI, PI]
float Angle_180(Point cen,Point first,Point second)
{
float dx1,dx2,dy1,dy2;
float angle;

float mag = vectorMag(cen, first) * vectorM……
[/Quote]

if (sign)
{
angle = 2 * PI - angle;
}
else
{
angle = angle - 2 * PI;
}

兄弟,你这里判断有问题的。假设sign为true时,只有 angle 大于180度时,才需要angle = 2 * PI - angle; 否则应该直接返回angle.



beyond071 2011-07-06
  • 打赏
  • 举报
回复
噢 没看清楼主是要求大于180度的夹角...

//夹角范围[-PI, PI]
float Angle_180(Point cen,Point first,Point second)
{
float dx1,dx2,dy1,dy2;
float angle;

float mag = vectorMag(cen, first) * vectorMag(cen, second);
angle = (float)acos(DotProduct(cen, first, second) / mag);

bool sign = SignByCrossProduct(cen, first, second);

if (sign)
{
angle = 2 * PI - angle;
}
else
{
angle = angle - 2 * PI;
}

return angle;
}


clever101 2011-07-06
  • 打赏
  • 举报
回复 1
[Quote=引用 16 楼 bhybhybhy2 的回复:]

这么一个问题 半天高不定 可见 搞图形学的少
[/Quote]

兄弟,我已经搞出来了,有空写一篇文章出来。
  • 打赏
  • 举报
回复
这么一个问题 半天高不定 可见 搞图形学的少
夜行空 2011-07-06
  • 打赏
  • 举报
回复
哎~公式都记不清了
clever101 2011-07-06
  • 打赏
  • 举报
回复
[Quote=引用 13 楼 beyond071 的回复:]

C/C++ code

struct Point
{
float x,y;
};

float vectorMag(const Point &start, const Point &end)
{
return sqrtf((start.x - end.x) * (start.x - end.x) +
(start.y - end.y)……
[/Quote]

兄弟,大于180度的夹角的情况你没有考虑。
beyond071 2011-07-06
  • 打赏
  • 举报
回复

struct Point
{
float x,y;
};

float vectorMag(const Point &start, const Point &end)
{
return sqrtf((start.x - end.x) * (start.x - end.x) +
(start.y - end.y) * (start.y - end.y));
}

float DotProduct(const Point &cen, const Point &first, const Point &second)
{
dx1 = first.x - cen.x;
dx2 = second.x - cen.x;
dy1 = first.y - cen.y;
dy2 = second.y - cen.y;

return dx1 * dx2 + dy1 * dy2;
}

//假定逆时针为正向
bool SignByCrossProduct(const Point &cen, const Point &first, const Point &second)
{
dx1 = first.x - cen.x;
dx2 = second.x - cen.x;
dy1 = first.y - cen.y;
dy2 = second.y - cen.y;

if(dx1 * dy2 - dy1 * dx2 > 0) return true;
return false;
}

//夹角范围[-PI, PI]
float Angle(Point cen,Point first,Point second)
{
float dx1,dx2,dy1,dy2;
float angle;

float mag = vectorMag(cen, first) * vectorMag(cen, second);
angle = (float)acos(DotProduct(cen, first, second) / mag);

bool sign = SignByCrossProduct(cen, first, second);

if (!sign) angle = -angle;

return angle;
}

clever101 2011-07-06
  • 打赏
  • 举报
回复
夹角大于180度的判断办法想到了,通过叉乘判断
||a*b|| = ||a||*||b||*sin& (&为夹角)来判断。
clever101 2011-07-06
  • 打赏
  • 举报
回复 1
[Quote=引用 9 楼 bhybhybhy2 的回复:]

引用 7 楼 clever101 的回复:

引用 5 楼 bhybhybhy2 的回复:

1 判断从向量1旋转到向量2是 要顺时针还是逆时针 (近距离旋转)
2 算角度
3 考虑是否2PI-角度

若我没有记错的话 上面是可行的
另外我算角度是arctg


算角度用arctg是不行的,因为arctg的值域是(-pi/2,pi/2),这里没有等号。那么-pi/2和pi……
[/Quote]

兄弟,这个不行吧。
clever101 2011-07-06
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 woncomp 的回复:]

通过叉积判断是否大于180度

nA = normalize(A)
nB = normalize(B)

c1 = cross(nA, nB)
c2 = cross(-nA, nB)

d = dot(nA, nB)
r = acos(d)

如果c1和c2相等则大于180度,否则c1和c2相反
根据这个条件对r进行相应调整即可
[/Quote]

兄弟,我对你这种判断方法表示怀疑。现在顺时针、逆时针的判断很好判断,可以通过
((dx1*dy2-dy1*dx2)>0) 就是顺时针,反之逆时针。只是夹角大于180度的情况不好判断。
  • 打赏
  • 举报
回复
[Quote=引用 7 楼 clever101 的回复:]

引用 5 楼 bhybhybhy2 的回复:

1 判断从向量1旋转到向量2是 要顺时针还是逆时针 (近距离旋转)
2 算角度
3 考虑是否2PI-角度

若我没有记错的话 上面是可行的
另外我算角度是arctg


算角度用arctg是不行的,因为arctg的值域是(-pi/2,pi/2),这里没有等号。那么-pi/2和pi/2这些角度就永远求不出来了。
……
[/Quote]double ans = atan2(Dir1*Dir2, Dir1%Dir2); 晕倒,我还是参考一个高手的代码
clever101 2011-07-06
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 woncomp 的回复:]

通过叉积判断是否大于180度

nA = normalize(A)
nB = normalize(B)

c1 = cross(nA, nB)
c2 = cross(-nA, nB)

d = dot(nA, nB)
r = acos(d)

如果c1和c2相等则大于180度,否则c1和c2相反
根据这个条件对r进行相应调整即可
[/Quote]

那正方向和负方向怎么区分?
clever101 2011-07-06
  • 打赏
  • 举报
回复
[Quote=引用 5 楼 bhybhybhy2 的回复:]

1 判断从向量1旋转到向量2是 要顺时针还是逆时针 (近距离旋转)
2 算角度
3 考虑是否2PI-角度

若我没有记错的话 上面是可行的
另外我算角度是arctg
[/Quote]

算角度用arctg是不行的,因为arctg的值域是(-pi/2,pi/2),这里没有等号。那么-pi/2和pi/2这些角度就永远求不出来了。

woncomp 2011-07-06
  • 打赏
  • 举报
回复
通过叉积判断是否大于180度

nA = normalize(A)
nB = normalize(B)

c1 = cross(nA, nB)
c2 = cross(-nA, nB)

d = dot(nA, nB)
r = acos(d)

如果c1和c2相等则大于180度,否则c1和c2相反
根据这个条件对r进行相应调整即可
加载更多回复(15)
基于视频序列捕获人体运动和人体运动分析 如何制作一个可控制的人体骨骼模型 描述:本文提供一种将骨架动作矢量映射到人体骨架模型的一种方法,通过输入各个骨骼的当前方向,反馈给骨架模型,这样就实现了动画的效果。 实验开发工具是VC6.0在OpenGL平台上开发完成。 阅读对象: 假定读者已经熟悉OpenGL编程,就算不熟悉,只要了解基本的旋转,平移,堆栈操作就好。 假定读者已经了解基本的c++编程,其中需要了解递归的算法,递归的方法请参考一下数据结构吧。 制作过程: 第一步,3D模型准备 这一步骤的目的是提供分解的骨骼模型,它需要导出多个组成身体结构的文件,模型可以不用自己制作,只要到网上找找吧,应该很多,最好是是人体模型,如果用动物的模型也可以,不过需要自己定义映射骨架了,比如图中的骷髅模型是我从人体动画软件poser 5.0找到的。然后使用3d max 将身体的各个部位导出为3ds文件,这个步骤很简单,也不需要有什么3d max的基础。这里有一个小的技巧就是可以选中多个部分作为一个3ds模型导出,比如我需要将左右肩胛骨与脊椎骨肋骨作为同一个部分导出,这样可以将它命名为身体躯干(body)。这样我们就准备了各个3ds文件了,分别是: 身体躯干 BODY.3DS 头部 HEAD.3DS 左臂 LSHOULDER.3DS 右臂 RSHOULDER.3DS 左小臂 LELBOW.3DS 右小臂 RELBOW.3DS 左大腿 LTHIGH.3DS 右大腿 RTHIGH.3DS 左小腿 LFEET.3DS 右小腿 RFEET.3DS 这样这些组成部分就可以灵活的拼接出一个人体来了。 第二步,定义相关的核心数据结构 为了得到运动的各个身体部分数据信息,我们需要存储一些运动信息,主要有: 骨骼ID 骨骼关节的当前位置;r_x,r_y,r_z 骨骼之间的关系,例如手臂是躯干的延伸,而左小臂是左臂的延伸;PID,CID 我们可以通过下图来了解骨骼之间的结构关系 存放3ds文件位置;file_name_3ds 3ds模型的初始化方向;这个是比较抽象一点的概念,它是指从父节点指向子节点的方向,例如左小臂的初始位置是平放向下,那么对应的矢量就是 (-0.2,-1,0) 以下是数据结构部分: class bone { public: int y; int x; int r_z; //现实世界z坐标 int r_y; int r_x; int rotated_X; //旋转后的坐标 int rotated_Y; int is_marked; //是否已经标记 int PID; //父节点 int CID; //子节点,目前针对轴关节和膝盖有效 float start_arc_x,end_arc_x; //相对父节点的x 左右方向转动角限制 float start_arc_y,end_arc_y; //相对父节点的y 上下方向转动角限制 float start_arc_z,end_arc_z; //相对父节点的z 前后方向转动角限制 double LengthRatio; char name[80]; //名称 char file_name_3ds[180]; //3ds文件名称 int ID; bone(int ID,char *name,int PID); virtual ~bone(); float bone_init_x,bone_init_y,bone_init_z; //初始化骨骼的矢量方向,3d max 模型 }; 第三步,初始化骨架结构 在定义了bone的结构以后,我们定义一个skeleton类来在第一次初始化时加载这些结构, obone = bone (2,"head",1); //定义一个bone strcpy(obone.file_name_3ds,"head.3DS"); //设置它的3ds文件名 obone.bone_init_x = 0; //初始化骨骼的矢量方向 obone.bone_init_y = 1; obone.bone_init_z = 0; bonevec.push_back (obone); //放入vector结构,这里用到了STL编程技术中的vector 以下是实现的部分代码: skelecton::skelecton() { float fy = 0.56f ; float ftx = 0.19f; float ffx = 0.08f; bone obone = bone (1,"neck",0); bonevec.push_back (obone); obone = bone (2,"head",1); strcpy(obone.file_name_3ds,"head.3DS"); obone.bone_init_x = 0; obone.bone_init_y = 1; obone.bone_init_z = 0; bonevec.push_back (obone); obone = bone (3,"rShoulder",1); bonevec.push_back (obone); obone = bone (4,"lShoulder",1); bonevec.push_back (obone); obone = bone (5,"rElbow",3); strcpy(obone.file_name_3ds,"rShoulder.3DS"); obone.bone_init_x = fy; obone.bone_init_y = -1; obone.bone_init_z = 0; obone.CID = 7; bonevec.push_back (obone); obone = bone (6,"lElbow",4); strcpy(obone.file_name_3ds,"lShoulder.3DS"); obone.bone_init_x = -fy; obone.bone_init_y = -1; obone.bone_init_z = 0; obone.CID = 8; bonevec.push_back (obone); //.............太长只给出部分的代码.......................... } 第四步,学习3ds公共的类CLoad3DS,可以用来载入显示模型 这个类是公用一个类,详细的类CLoad3DS的接口信息可以到一个open source项目里参考。http://scourge.sourceforge.net http://scourge.sourceforge.net/api/3ds_8h-source.html 实际上在使用这个类时候,我做了一些修改,加了得到最大顶点的方法。这个在第五步会说明。 我们定义一个OpenGL的类来做模型控制类,负责载入模型, CLoad3DS* m_3ds; int OpenGL::Load3DS(int ID, char *filename) { if(m_3ds!=NULL) m_3ds->Init(filename,ID); return 0; } 然后在显示时候调用 int OpenGL::show3ds(int ID) { m_3ds->show3ds(ID,0,0,0,2); return 0; } 第五步,使用递归方法分层次载入模型 这里是重点的内容了,让我们思考一些问题,实现骨骼会随着输入的方向而改变方向,需要做那些事情呢? 首先针对一块骨骼来考虑: 第一,我们需要让骨骼绕着它的节点旋转到输入的方向上 第二,我们需要知道骨骼目前节点的位置,才能旋转。可是我们知道骨骼会跟着它的父骨骼转动的,例如左小臂会跟着左臂转动,当身体转动时左臂也会跟着身体转动的,这里看起来像是有一个父子连动的关系,所以当前节点的位置会与它的父骨骼有关,父骨骼转动的角,子骨骼也必须转动,所以这里自然想到了递归模型了,至于如何存储这些转动过程呢,还好openGL提供了glPushMatrix();glPopMatrix();那么所有的子骨骼必须包含在父骨骼的glPushMatrix();glPopMatrix();好了,这个变成 //递归实现3d现实 int skelecton::Render_skeleton_3D(int ID) { glPushMatrix(); //开始记录堆栈 joint_point = pgl->get_joint_point(ID); //找到节点位置 glTranslatef(joint_point.x,joint_point.y,joint_point.z); //坐标移到节点位置 pgl->rotate_bone (vt1,vt2,vto); //旋转骨骼到指定的方向 glTranslatef(-joint_point.x,-joint_point.y,-joint_point.z);//坐标移回来 pgl->show3ds(ID); //显示模型 //遍历子节点 for (theIterator = bonevec.begin(); theIterator != bonevec.end(); theIterator++) { pbone = theIterator; if((pbone->PID == ID) ) { Render_skeleton_3D(pbone->ID); //递归调用 } } glPopMatrix(); //退出记录堆栈 } 剩下需要解决的问题就是如何找到节点位置。 寻找节点位置, 我们看到上面代码 get_joint_point(ID)就是找到节点了,其实如果不追高的准确,我们可以假设每个模型的最高的点即为骨骼的节点,当然这个假设前提是人体模型是正面站立的,手臂自然垂下,这样可以近似认为每个模型的最高的点即为骨骼的节点,这样函数就很简单了,这个方法是修改了Cload3ds类的方法,如下: Vector3f CLoad3DS::get_joint_point(int j0) { CVector3 LastPoint; Vector3f vect; LastPoint.y = -1000 ; if(j0==2) LastPoint.y = 1000 ;//头部节点朝下 // 遍历模型中所有的对象 for(int l = 0; l < g_3DModel[j0].numOfObjects; l++) {if(g_3DModel[j0].pObject.size() <= 0) break;// 如果对象的大小小于0,则退出 t3DObject *pObject = &g_3DModel[j0].pObject[l];// 获得当前显示的对象 for(int j = 0; j < pObject->numOfFaces; j++) // 遍历所有的面 { for(int tex = 0; tex < 3; tex++) // 遍历三角形的所有点 { int index = pObject->pFaces[j].vertIndex[tex]; // 获得面对每个点的索引 if(j0==2) { if(pObject->pVerts[index].y < LastPoint.y ) LastPoint = pObject->pVerts[index]; } else { if(pObject->pVerts[index].y > LastPoint.y ) LastPoint = pObject->pVerts[index]; } } } } vect.x = LastPoint.x ; vect.y = LastPoint.y ; vect.z = LastPoint.z ; return vect; } 比较特殊的是头部节点是通过脖子连接的,所以它是取最低的点。 现在解决最后的问题了,如何旋转了,具体来讲就是骨骼从原来自然的状态旋转到目前的方向,例如手臂从自然垂下变成抬起,垂下和抬起个状态的矢量是不同的方向的,如何旋转呢? 这里就要用到了空间几何里的点积和叉积的概念了,简单来讲就是利用点积来矢量夹角余弦,利用叉积来个矢量的法向量,如果你忘记了这些概念,可以回去参考一下高等数学书,这个连接也提供了一些资料,可以帮助理解http://www.gameres.com/Articles/Program/Visual/Other/shiliang.htm 然后呢,我们知道了个矢量的夹角与它们的法向量,下面的事情就变得简单了,我们让骨骼原来的矢量以法向量为旋转轴,旋转一定角,这个角就是个矢量的夹角,这样问题就解决了,所以这里的代码如下: int OpenGL::rotate_bone(Vector3f vVector1, Vector3f vVector2, Vector3f vVectorOrgin) { Vector3f vt1 = Vector3f(vVector1.x,vVector1.y,vVector1.z); Vector3f vt2 = Vector3f(vVector2.x,vVector2.y,vVector2.z); Vector3f vt4 = vt2-vt1; double arc12 = AngleBetweenVectors(vVectorOrgin,vt4); double rarc12 = 180*arc12/pi; float len= Distance(vt1,vt2); Vector3f vt3 = Cross(vVectorOrgin,vt4); glRotatef ((float)rarc12,vt3.x,vt3.y,vt3.z); return 0; } 好了所有问题解决了,我们可以松一口气了。这里我提供一个参考的代码,因为这个模型的运行需要输入矢量方向,我目前还不能把它从我的其他系统程序完整提取出来,只是提供所有的代码,供读者参考。 本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/hardVB/archive/2005/08/10/449922.aspx

19,468

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 图形处理/算法
社区管理员
  • 图形处理/算法社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧