文章出處

CSharpGL(20)用unProject和Project實現鼠標拖拽圖元

效果圖

例如,你可以把Big Dipper這個模型拽成下面這個樣子。

配合旋轉,還可以繼續拖拽成這樣。

當然,能拖拽的不只是線段。還可以拖拽三角形(如下圖)、四邊形。

另外,還可以單點拖拽。

2016-04-28

現在實現了高亮顯示拾取、拖拽的圖元的功能。

下面演示了鼠標移動到圖元上時顯示圖元的索引值的功能。

 起初會出現stitching和z-fighting的現象。例如下面選中一個三角形時,由于stitching問題,沒高亮其斜邊。

于是我添加了PolygonOffsetSwtich開關,解決了這個問題。

 

下載

CSharpGL已在GitHub開源,歡迎對OpenGL有興趣的同學加入(https://github.com/bitzhuwei/CSharpGL

unProject/Project

這個兩個函數的執行結果完全相反。拖拽功能全靠他們了。

Project把模型坐標系上的點轉換為窗口坐標系上的點。這可以通過其實現代碼來驗證。

 1         /// <summary>
 2         /// Map the specified object coordinates (obj.x, obj.y, obj.z) into window coordinates.
 3         /// </summary>
 4         /// <param name="obj">The object.</param>
 5         /// <param name="model">The model.</param>
 6         /// <param name="proj">The proj.</param>
 7         /// <param name="viewport">The viewport.</param>
 8         /// <returns></returns>
 9         public static vec3 project(vec3 obj, mat4 model, mat4 proj, vec4 viewport)
10         {
11             vec4 tmp = new vec4(obj, (1f));
12             tmp = model * tmp;
13             tmp = proj * tmp;
14 
15             tmp /= tmp.w;
16             tmp = tmp * 0.5f + new vec4(0.5f, 0.5f, 0.5f, 0.5f);
17             tmp[0] = tmp[0] * viewport[2] + viewport[0];
18             tmp[1] = tmp[1] * viewport[3] + viewport[1];
19 
20             return new vec3(tmp.x, tmp.y, tmp.z);
21         }

通過試驗發現,一個vec3經過Project后再經過unProject,會變回原來的值。這就是說,unProject把窗口坐標系上的點轉換為模型坐標系上的點

OpenGL是以窗口左下角為原點(0, 0)的。而Windows窗口是以左上角為原點的。所以用的時候要注意轉換一下。

弄清楚了這兩個函數,才能實現鼠標拖拽的功能。

拖拽原理

既然可以把模型空間的點轉換為平面坐標系上的點,并且可以逆向操作。那么只需將要拖拽的點A通過project函數投影到屏幕上(變成a);根據鼠標在屏幕上的移動,相應的移動a,變成a',最后把a'通過unProject反射回模型空間,就是拖拽后的A'了。在VBO里,把A改為A'即可。

 1         /// <summary>
 2         /// 根據<paramref name="differenceOnScreen"/>來修改指定索引處的頂點位置。
 3         /// </summary>
 4         /// <param name="differenceOnScreen"></param>
 5         /// <param name="viewMatrix"></param>
 6         /// <param name="projectionMatrix"></param>
 7         /// <param name="viewport"></param>
 8         /// <param name="positionIndexes"></param>
 9         public void MovePositions(Point differenceOnScreen,
10             mat4 viewMatrix, mat4 projectionMatrix, vec4 viewport, uint[] positionIndexes)
11         {
12             if (positionIndexes == null) { return; }
13             if (positionIndexes.Length == 0) { return; }
14 
15             GL.BindBuffer(BufferTarget.ArrayBuffer, this.positionBufferPtr.BufferId);
16             IntPtr pointer = GL.MapBuffer(BufferTarget.ArrayBuffer, MapBufferAccess.ReadWrite);
17             unsafe
18             {
19                 var array = (vec3*)pointer.ToPointer();
20                 for (int i = 0; i < positionIndexes.Length; i++)
21                 {
22                     vec3 projected = glm.project(array[positionIndexes[i]],
23                         viewMatrix, projectionMatrix, viewport);
24                     vec3 newProjected = new vec3(projected.x + differenceOnScreen.X,
25                         projected.y + differenceOnScreen.Y, projected.z);
26                     array[positionIndexes[i]]=glm.unProject(newProjected,
27                         viewMatrix, projectionMatrix, viewport);
28                 }
29             }
30             GL.UnmapBuffer(BufferTarget.ArrayBuffer);
31             GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
32         }

MouseDown

鼠標按下時,如果拾取到圖元,就要為拖拽做準備。(如果想了解拾取的原理,可參考CSharpGL(18)分別處理glDrawArrays()和glDrawElements()兩種方式下的拾取(ColorCodedPicking)

 1         private void glCanvas1_MouseDown(object sender, MouseEventArgs e)
 2         {
 3             if (e.Button == System.Windows.Forms.MouseButtons.Left)
 4             {
 5                 // move vertex
 6                 PickedGeometry pickedGeometry = RunPicking(e.X, e.Y);
 7                 if (pickedGeometry != null)
 8                 {
 9                     var dragParam = new DragParam(pickedGeometry,
10                         camera.GetProjectionMat4(),
11                         camera.GetViewMat4(),
12                         new Point(e.X, glCanvas1.Height - e.Y - 1));
13                     this.dragParam = dragParam;
14                 }
15             }
16         }

其中的RunPicking就是執行一次拾取操作。

 1         private PickedGeometry RunPicking(int x, int y)
 2         {
 3             this.glCanvas1_OpenGLDraw(selectedModel, null);
 4             IColorCodedPicking pickable = this.rendererDict[this.SelectedModel];
 5             pickable.MVP = this.camera.GetProjectionMat4() * this.camera.GetViewMat4();
 6             PickedGeometry pickedGeometry = ColorCodedPicking.Pick(
 7                 this.camera, x, y, this.glCanvas1.Width, this.glCanvas1.Height, pickable);
 8 
 9             return pickedGeometry;
10         }

這里有個dragParam類型,記錄了按下后的一些數據。

 1     class DragParam
 2     {
 3 
 4         public PickedGeometry pickedGeometry;
 5         public mat4 projectionMatrix;
 6         public mat4 viewMatrix;
 7         public Point lastMousePositionOnScreen;
 8         public vec4 viewport;
 9 
10         public DragParam(PickedGeometry pickedGeometry, mat4 projectionMatrix, mat4 viewMatrix, Point lastMousePositionOnScreen)
11         {
12             this.pickedGeometry = pickedGeometry;
13             this.projectionMatrix = projectionMatrix;
14             this.viewMatrix = viewMatrix;
15             this.lastMousePositionOnScreen = lastMousePositionOnScreen;
16             var viewport = new int[4]; GL.GetInteger(GetTarget.Viewport, viewport);
17             this.viewport = new vec4(viewport[0], viewport[1], viewport[2], viewport[3]);
18         }
19     }

MouseMove

鼠標開始移動后,就要實時更新模型頂點的位置了。

 1         private void glCanvas1_MouseMove(object sender, MouseEventArgs e)
 2         {
 3             if (e.Button == System.Windows.Forms.MouseButtons.Left)
 4             {
 5                 // move vertex
 6                 DragParam dragParam = this.dragParam;
 7                 if (dragParam != null)
 8                 {
 9                     var current = new Point(e.X, glCanvas1.Height - e.Y - 1);
10                     Point differenceOnScreen = new Point(
11                         current.X - dragParam.lastMousePositionOnScreen.X,
12                         current.Y - dragParam.lastMousePositionOnScreen.Y);
13                     dragParam.lastMousePositionOnScreen = current;
14                     this.rendererDict[this.selectedModel].MovePositions(
15                         differenceOnScreen,
16                         dragParam.viewMatrix, dragParam.projectionMatrix,
17                         dragParam.viewport,
18                         dragParam.pickedGeometry.Indexes);
19                 }
20             }
21         }

MouseUp

鼠標抬起,清空數據,恢復狀態。

1         private void glCanvas1_MouseUp(object sender, MouseEventArgs e)
2         {
3             if (e.Button == System.Windows.Forms.MouseButtons.Left)
4             {
5                 // move vertex
6                 this.dragParam = null;
7             }
8         }

總結

本文雖然簡單,但是我卻花了好幾天才解決拖拽的問題。過程中想過試過種種奇葩的方案。最后,在弄明白了project和unProject的功能后,立即想到了現在這個方案,既簡單又實用。

所以說必須戒除浮躁和急切的心理,慢慢地搞清楚每一個小問題。這才是磨刀不誤砍柴工。

原CSharpGL的其他功能(UI、3ds解析器、TTF2Bmp、CSSL等),我將逐步加入新CSharpGL。

歡迎對OpenGL有興趣的同學關注(https://github.com/bitzhuwei/CSharpGL


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()