Trackball rotate的OSG实现(一)(不是manipulator那种~是动物体那种!)

    xiaoxiao2021-03-25  127

    先写一下我的大致实现流程 鼠标单击模型,调用pick函数,来判断是否pick到了物体,并且获取模型的包围球中心和半径来作为我们这个virtual trackball的方程,哦,还要获取当前模型的位置。 然后将屏幕上的点映射到世界坐标下的球面上。

    Created with Raphaël 2.1.0 开始 获取屏幕上的点 (x,y) 得出球面上映射出来的z坐标 求出rotateParameter 结束

    详细叙述将鼠标位置映射到球面(或曲面,当鼠标超出球的范围时的过程) 首先得到的是屏幕空间的点A,将该点转换到相机坐标系下A’(x,y,0),再将获取到的物体包围球(在世界坐标系下的)中心O,转换到相机坐标系下O’,为什么要这么做稍后再讲。然后根据O’和半径在相机空间中虚拟了trackball和曲面,然后用A’(x’,y’,z’)去求解下列方程获得z”值。于是得到在相机空间中的A’映射到球面(曲面)上的点V=(x’,y’,z”)。再将这个点V转换到世界坐标中得到V’,用V’- O(世界坐标下的包围球中心)得到映射点到trackball球心的向量V’O。

    z(x,y)=r2((xOx)2+(yOy)2)+Oz,r2/2(xOx)2+(yOy)2+Ozif (xOx)2+(yOy)2r22  所得到的V1为 V1=(x1Ox,y1Oy,z1Oz) 这里注意一下OSG中默认的坐标是x向右,y向里,z向上。所以要注意区别,不要弄混掉了。 然后还有一个需要注意的点,通常通过getBound()获取到的包围球以及其球心都是以当前节点的父节点的局部坐标系下的。我这里的情况是,这个transform节点,就是根节点,所以获取到的包围球的中心可以说是世界坐标下的中心。如果场景更加复杂,有多层节点,则需要进行局部坐标转世界坐标,保证构造的这个虚拟的trackball都是在世界坐标下进行计算的。可以使用的方法,可能有computeLocalToWorld()以及getWorldMatrix()这两个方法。。

    osg::Vec3 TrackballRotate::projectToSphere(osg::Vec3 xy, osg::Vec3 center, float radius){ osg::Vec3 cameraPoint = screen2Camera(xy); osg::Vec3 cameraCenter = world2Camera(center); float restrain = radius*radius / 2; float x2 = (cameraPoint.x() - cameraCenter.x())*(cameraPoint.x() - cameraCenter.x()); float y2 = (cameraPoint.y() - cameraCenter.y())*(cameraPoint.y() - cameraCenter.y()); float z; if (x2 + y2 <= restrain)//在球内部 { z = sqrtf(radius*radius - (x2 + y2)) + cameraCenter.z(); } else { z = restrain / sqrtf(x2 + y2) + cameraCenter.z(); } osg::Vec3 v; v = { cameraPoint.x(), cameraPoint.y() , z };//相机空间中映射到的点 v = camera2World(v); osg::Vec3 vr = v - center; float length = vr.length(); vr = vr / length; return v; }

    接下来就是获取旋转参数了

    void TrackballRotate::GetRotateParameter(osg::Vec3 oldP, osg::Vec3 newP, osg::Vec3 center, float radius) { osg::Vec3 delta = newP - oldP; if (delta.length2() <= 4) { return; } //先将这两个点投影到球面,获得z坐标。 osg::Vec3 p1 = projectToSphere(oldP, center, radius); osg::Vec3 p2 = projectToSphere(newP, center, radius); osg::Vec3 k = p1^p2;//叉乘为旋转轴 k = k / k.length(); float angle = acos(p1*p2 / (p1.length()*p2.length()));//点乘求得夹角 //float angle = acos(p1*p2);//点乘求得夹角 float cosangle = cos(angle / 2); float sinangle = sin(angle / 2);// k *= sinangle; //osg::Vec3 rotate_axis = k; rotateParameter = { angle, k };}

    之前的那篇trackball原理分析讲的挺清楚的,这里也注释了,应该不难理解。最后给出handle部分的代码。不知道为什么贴不上来,大概是篇幅有限,那么我就可以再水一篇博客了。。哈哈。。

    刚刚说的为什么要在相机坐标系下计算,一开始的时候,我是都把所有的坐标变换到了世界坐标下,为了方便对物体进行操作,因为我搞不懂旋转量是针对什么世界坐标还是什么坐标而言的。所以索性都在世界坐标里计算。也是一样的思路,屏幕上的点A(x,y,0)直接转到世界坐标中,这里就出现了问题。看下图。其实归根结底就是透视投影的问题,在另一篇博客中,正确计算鼠标移动量中也是这个问题。

    可以看到我所以为的映射是A到A’。B到B’。和实际中点到的点是不一样的。所以就是觉得这个trackball的旋转特别慢。如果说在相机空间中就不存在这个问题了。因为还没有经过投影这一步,所有的屏幕上的点都可以垂直映射到空间中的任何位置。

    转载请注明原文地址: https://ju.6miu.com/read-14305.html

    最新回复(0)