Math 计算两个单位长度3D向量的中间值,不带平方根?

Math 计算两个单位长度3D向量的中间值,不带平方根?,math,vector,3d,trigonometry,Math,Vector,3d,Trigonometry,对于两个3D单位长度向量,是否有一种方法可以在不重新规范化的情况下计算它们之间的单位长度向量?(更具体地说,没有平方根) 目前,我只是将它们都添加并进行规范化,但为了提高效率,我认为可能有更好的方法 (在本问题中,忽略两个向量直接相反的情况)首先,找到两个向量之间的角度。从理论上,我们知道 |a| * cos(theta) = a . b_hat sin(theta/2)/1 = sin(180-theta)/N 是运算符,a是a的长度,θ是a和b之间的角度,b_hat是b的规范化形式。

对于两个3D单位长度向量,是否有一种方法可以在不重新规范化的情况下计算它们之间的单位长度向量?(更具体地说,没有平方根)

目前,我只是将它们都添加并进行规范化,但为了提高效率,我认为可能有更好的方法



(在本问题中,忽略两个向量直接相反的情况)

首先,找到两个向量之间的角度。从理论上,我们知道

|a| * cos(theta) = a . b_hat
sin(theta/2)/1 = sin(180-theta)/N
是运算符,
a
a
的长度,
θ
a
b
之间的角度,
b_hat
b
的规范化形式。 在您的情况下,a和b已经是单位向量,因此这简化为:

cos(theta) = a . b
我们可以将其重新安排为:

theta = acos(a . b)
将向量A和B首尾相连,并通过从第一个向量的起点到第二个向量的终点画一条线来完成三角形。因为两条边的长度相等,我们知道三角形是等角的,所以如果你已经知道θ,就很容易确定所有的角度

长度为N的线是中间向量。如果我们把它除以N,我们可以使它正常化

从这个角度,我们知道这一点

|a| * cos(theta) = a . b_hat
sin(theta/2)/1 = sin(180-theta)/N
我们可以重新安排

N = sin(180-theta) / sin(theta/2)
请注意,如果A和B相等,则在计算N时将除以零,因此在开始之前检查拐角情况可能会很有用


总结:

dot_product = a.x * b.x + a.y * b.y + a.z * b.z
theta = acos(dot_product)
N = sin(180-theta) / sin(theta/2)
middle_vector = [(a.x + b.x) / N, (a.y + b.y) / N, (a.z + b.z) / N]

根据答案,我做了一些速度比较

编辑。有了这个nieve基准,GCC优化了三角函数,使两种方法的速度大致相同,请阅读@Ali的帖子以获得更完整的解释

总之,使用重新规格化大约快4倍

#包括
#包括
/*gcc mid_v3_v3v3_slerp.c-lm-O3-o mid_v3_v3v3_slerp_a
*gcc mid_v3_v3v3_slerp.c-lm-O3-o mid_v3_v3v3_slerp_b-DUSE_规范化
*
*时间./mid_v3_v3v3_slerp_a
*时间./mid_v3_v3v3_slerp_b
*/
#ifdef使用_规范化
#警告“使用规范化”
void mid_v3_v3v3_slerp(浮点v[3],常量浮点v1[3],常量浮点v2[3])
{
浮动m;
v[0]=(v1[0]+v2[0]);
v[1]=(v1[1]+v2[1]);
v[2]=(v1[2]+v2[2]);
m=1.0f/sqrtf(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);
v[0]*=m;
v[1]*=m;
v[2]*=m;
}
#否则
#警告“不使用规范化”
void mid_v3_v3v3_slerp(浮点v[3],常量浮点v1[3],常量浮点v2[3])
{
常量浮点点积=v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2];
常数浮点θ=acosf(点积);
常数浮点n=1.0f/(2.0f*cosf(θ*0.5f));
v[0]=(v1[0]+v2[0])*n;
v[1]=(v1[1]+v2[1])*n;
v[2]=(v1[2]+v2[2])*n;
}
#恩迪夫
内部主(空)
{
无符号长整型i=1000000000;
常量浮点v1[3]={0.865911722183275,0.4995948076248169,0.024538060650229454};
常量浮点v2[3]={0.7000154256820679,0.7031427621841431,-0.12477479875087738};
浮动v[3];
而(--i){
asm(“”;/*阻止编译器优化循环*/
中期v3期v3v3期slerp(v、v1、v2);
}
printf(“完成%f%f%f\n”,v[0],v[1],v[2]);
返回0;
}

这不是对原始问题的回答;我更愿意尝试解决两个答案之间的问题,这不适合评论

  • 在我的机器上(Linux、Intel Core i5),使用平方根函数时,速度比原始版本慢4倍。你的里程数会有所不同

  • asm(“”)
    对他的兄弟姐妹来说总是一股难闻的气味
    volatile
    (void)x

  • 多次运行紧密循环是一种非常不可靠的基准测试方法

该怎么办

  • 分析生成的汇编代码,查看编译器对源代码的实际操作

  • 使用分析器。我可以推荐
    perf
    英特尔VTune


  • 如果您查看的汇编代码,您将看到编译器非常智能,并且发现v1和v2没有改变,并且在编译时尽可能地减少了工作量。在运行时,没有调用
    sqrtf
    acosf
    cosf
    。这解释了为什么您没有看到这两种方法之间的任何差异


    这是您的基准测试的编辑版本。我对它进行了一点置乱,并用
    1.0e-6f
    防止被零除。(这不会改变结论。)

    #包括
    #包括
    #ifdef使用_规范化
    #警告“使用规范化”
    void mid_v3_v3v3_slerp(浮点数[3],常数浮点数v1[3],常数浮点数v2[3])
    {
    浮动m;
    浮点数v[3]={(v1[0]+v2[0]),(v1[1]+v2[1]),(v1[2]+v2[2]);
    m=1.0f/sqrtf(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]+1.0e-6f);
    v[0]*=m;
    v[1]*=m;
    v[2]*=m;
    res[0]=v[0];
    res[1]=v[1];
    res[2]=v[2];
    }
    #否则
    #警告“不使用规范化”
    void mid_v3_v3v3_slerp(浮点v[3],常量浮点v1[3],常量浮点v2[3])
    {
    常量浮点点积=v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2];
    常数浮点θ=acosf(点积);
    常数浮点n=1.0f/(2.0f*cosf(θ*0.5f)+1.0e-6f);
    v[0]=(v1[0]+v2[0])*n;
    v[1]=(v1[1]+v2[1])*n;
    v[2]=(v1[2]+v2[2])*n;
    }
    #恩迪夫
    内部主(空)
    {
    无符号长整型i=20000000;
    浮动v1[3]={-0.8659117221832275,0.4995948076248169,0.024538060650229454};
    浮动v2[3]={0.7000154256820679,0.7031427621841431,-0.12477479875087738};
    float v[3]={0.0,0.0,0.0};
    而(--i){
    中期v3期v3v3期slerp(v、v1、v2);
    中间层(v1,v,v2);
    中间层(v1,v2,v);
    }
    printf(“完成%f%f%f\n”,v[0],v[1],v[2]);
    返回0;
    }
    
    我用gcc-ggdb3-O3-Wall-Wextra-fwhole程序-DUSE_NORMALIZE-march=native-static normal.c-lm编译了它