opengl學習筆記Ⅴ——磨人的軌跡球真可愛
這回我們繼續優化互動。使用軌跡球來使得模型轉得更加人性化。
軌跡球可以理解為模型外部套一個球體,在我們滑鼠拖動旋轉模型時,能夠準確地模擬出球體被拖動的感覺。
首先,我在這裡放棄了透視投影,考慮透視投影的話太複雜了。(搞不出來,就很煩)
用簡單的正交投影來做吧
glMatrixMode(GL_PROJECTION);//設定投影矩陣
glLoadIdentity();
glOrtho(-1500, 1500, -1500, 1500, 1, 10000);
這樣,我們可以簡單地算出球在視窗中的大小。(這裡我的視窗大小為250,1500為正交投影規定的視景體的一半)
work_size = a*(R/1500.0)*250;//a為縮放比例,初始為1
接著我們換算一下滑鼠回撥函式得到的座標,把它轉化成球心為原點的座標系(視窗中間)。因為滑鼠回饋的座標是以左上為原點,向右為x,向下為y。所以我們這麼轉化:
x -= 250;
y -= 250;
y = -y;
這樣,可以算出我們點選的點的“座標”A,這裡是針對窗口裡顯示的球(work_size為半徑)的中心的座標系,朝外為Z正。
r_x1 = x;
r_y1 = y;
r_z1 = sqrt(work_size*work_size - x*x - y*y);
同樣,在滑鼠移動的回撥函式裡,我們會得到另外一個點B的座標(r_x2,r_y2,r_z2)。
計算出OA X OB(叉乘)的值,即為旋轉軸,記得要把長度縮放為1。
rx = r_y1*r_z2-r_z1*r_y2;
ry = r_z1*r_x2 - r_x1*r_z2;
rz = r_x1*r_y2 - r_y1*r_x2;
mod = sqrt(rx*rx + ry*ry + rz*rz);
rx /= mod;
ry /= mod;
rz /= mod;
計算旋轉角度,l為兩點之間的距離。
l = sqrt((r_x1 - r_x2)*(r_x1 - r_x2) + (r_y1 - r_y2)*(r_y1 - r_y2) + (r_z1 - r_z2)*(r_z1 - r_z2)); theta =-2*asin(l/(2.0*work_size));
參考https://www.cnblogs.com/graphics/archive/2012/08/10/2627458.html裡的文章,我們可以得到對應的旋轉矩陣如下
m2[0] = rx*rx + (1 - rx*rx)*cos(theta);
m2[1] = rx*ry*(1 - cos(theta)) - rz*sin(theta);
m2[2]= rx*rz*(1 - cos(theta)) + ry*sin(theta);
m2[4] = rx*ry*(1 - cos(theta)) + rz*sin(theta);
m2[5] = ry*ry + (1 - ry*ry)*cos(theta);
m2[6] = ry*rz*(1 - cos(theta)) - rx*sin(theta);
m2[8] = rx*rz*(1 - cos(theta)) - ry*sin(theta);
m2[9] = ry*rz*(1 - cos(theta)) + rx*sin(theta);
m2[10] = rz*rz + (1 - rz*rz)*cos(theta);
m2[15] = 1;
注意opengl中,矩陣為4X4,實際上接受的是一個一維陣列指標,所以我們定義為m2[16]。因為opengl是一列一列存資料,和我們正常的一行一行的存不一樣,所以注意下標的選擇哦。
在我們放開滑鼠左鍵之後,把這一次的旋轉矩陣作用在m1上,使得m1代表所有歷史旋轉矩陣的綜合作用。
glLoadMatrixd(m2);
glMultMatrixd(m1);
glGetDoublev(GL_MODELVIEW_MATRIX, m1);
glLoadIdentity();
glGetDoublev(GL_MODELVIEW_MATRIX, m2);
這裡要注意的是,opengl是右乘,如果我們直接把m2乘進m1,得到的結果會是m1m2 V,我們實際上是希望在完成m1旋轉之後進行m2旋轉,所以才有了上面的倒來倒去的操作。
同樣的,在繪製函式裡,我們的繪製順序也是
glMultMatrixd(m2);
glMultMatrixd(m1);
這樣我們的旋轉已經基本完成,接下來談一下畫球的問題,因為我們用的是光照模型,所以球畫出來會有明暗的問題。但我們實際上不希望球的顏色會隨著位置變化,所以我們呼叫如下函式設定球的自發光顏色,注意,這個光不會像光源一樣影響其他物體的顏色。
glMaterialfv(GL_FRONT, GL_EMISSION, ball);
效果還不錯。
附:對於在球外部的拖動,我們會希望變為Z軸方向的旋轉。這裡具體的講解讀者自己思考,我只放下我寫的程式碼
GLfloat theta,la,lb,lc;
r_x2 = x;
r_y2 = y;
la = sqrt(r_x1*r_x1 + r_y1*r_y1);
lb = sqrt(r_x2*r_x2 + r_y2*r_y2);
lc = sqrt((r_x1 - r_x2)*(r_x1 - r_x2) + (r_y1 - r_y2)*(r_y1 - r_y2));
theta = 180/PI*acos((la*la + lb*lb - lc*lc) / (2 * la*lb));
//if (theta > 0.1)
//{
// cout << endl;
//}
glLoadIdentity();
if (r_x1*r_y2 - r_y1*r_x2>0)
glRotated(theta, 0, 0, 1);
else
glRotated(theta, 0, 0, -1);
glGetDoublev(GL_MODELVIEW_MATRIX, m2);