文章出處

CSharpGL(21)用鼠標拾取、拖拽VBO圖元內的點、線或本身

效果圖

以最常見的三角形網格(用GL_TRIANGLES方式進行渲染)為例。

在拾取模式為GeometryType.Point時,你可以拾取單個的頂點。

在拾取模式為GeometryType.Line時,你可以拾取任意一個三角形里的任意一條線。即同時拾取此線段的兩個頂點。

在拾取模式為GeometryType.Triangle時,你可以拾取任意一個三角形。即同時拾取此三角形的三個頂點。

實際上,CSharpGL實現了在所有渲染模式下拾取Point、Line、Triangle、Quad和Polygon的功能。(當然,你可以想象,如果想在一個GL_TRIANGLES渲染方式下拾取一個Quad,那是什么都拾取不到的)下面是描述這一功能的圖示。由于我的白板小,就沒有列出GL_TRIANGLES_ADJACENCY、GL_TRIANGLE_STRIP_ADJACENCY、GL_LINES_ADJACENCY、GL_LINE_STRIP_ADJCANCEY這幾個情況。

下載

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

規定

為了簡便描述,我用GL_LINE*代表GL_LINES、GL_LINE_STRIP、GL_LINES_ADJACENCY、GL_LINE_STRIP_ADJACENCY,用GL_TRIANGLE*代表GL_TRIANGLES、GL_TRIANGLE_STRIP、GL_TRIANGLE_FAN、GL_TRIANGLES_ADJACENCY、GL_TRIANGLE_STRIP_ADJACENCY,用GL_QUAD*代表GL_QUADS、GL_QUAD_STRIP。

如何使用

使用方式十分簡單,只需給RenderEventArgs傳入如下的參數:

 1 GeometryType PickingGeometryType = Geometry.Point;
 2 var arg = new RenderEventArgs(
 3     // 為了拾取而進行的渲染
 4     RenderModes.ColorCodedPicking,
 5     this.glCanvas1.ClientRectangle,
 6     this.camera, 
 7     // 我想拾取的類型(Geometry)
 8     this.PickingGeometryType);
 9 // 要拾取的位置(鼠標位置)
10 Point mousePostion = GetMousePosition();
11 // 支持Picking的Renderer列表 
12 PickableRenderer[] pickableElements = GetRenderersInScene();
13 // 執行拾取操作
14 PickedGeometry pickedGeometry = ColorCodedPicking.Pick(arg, mousePostion, pickableElements);

具體用法詳見(CSharpGL(20)用unProject和Project實現鼠標拖拽圖元

如何實現

在GL_POINTS時拾取Point,在GL_LINE*時拾取Line,在GL_TRIANGL*時拾取Triangle,在GL_QUAD*時拾取Quad,在GL_POLYGON時拾取Polygon,這都是已經實現了的(CSharpGL(18)分別處理glDrawArrays()和glDrawElements()兩種方式下的拾取(ColorCodedPicking))。這些不再詳述。

拾取Point

ZeroIndexRenderer

在除了GL_POINTS時,想拾取一個Point,只能用 glDrawArrays(GL_POINTS, ..); 來代替原有的 glDrawArrays(OriginalMode, ..); 。但這會渲染所有的頂點。而在OriginalMode下,未必渲染所有的頂點。所以在拾取到一個Point后要判斷一下是否真的應該拾取到它。

  1         /// <summary>
  2         /// 現在,已經判定了鼠標在某個點上。
  3         /// 我需要判定此點是否出現在圖元上。
  4         /// now that I know the mouse is picking on some point,
  5         /// I need to make sure that point should appear.
  6         /// </summary>
  7         /// <param name="lastVertexId"></param>
  8         /// <param name="mode"></param>
  9         /// <returns></returns>
 10         private bool OnPrimitiveTest(uint lastVertexId, DrawMode mode)
 11         {
 12             bool result = false;
 13             int first = this.zeroIndexBufferPtr.FirstVertex;
 14             if (first < 0) { return false; }
 15             int vertexCount = this.zeroIndexBufferPtr.VertexCount;
 16             if (vertexCount <= 0) { return false; }
 17             int last = first + vertexCount - 1;
 18             switch (mode)
 19             {
 20                 case DrawMode.Points:
 21                     result = true;
 22                     break;
 23                 case DrawMode.LineStrip:
 24                     result = vertexCount > 1;
 25                     break;
 26                 case DrawMode.LineLoop:
 27                     result = vertexCount > 1;
 28                     break;
 29                 case DrawMode.Lines:
 30                     if (vertexCount > 1)
 31                     {
 32                         if (vertexCount % 2 == 0)
 33                         {
 34                             result = (first <= lastVertexId && lastVertexId <= last);
 35                         }
 36                         else
 37                         {
 38                             result = (first <= lastVertexId && lastVertexId <= last - 1);
 39                         }
 40                     }
 41                     break;
 42                 case DrawMode.LineStripAdjacency:
 43                     if (vertexCount > 3)
 44                     {
 45                         result = (first < lastVertexId && lastVertexId < last);
 46                     }
 47                     break;
 48                 case DrawMode.LinesAdjacency:
 49                     if (vertexCount > 3)
 50                     {
 51                         var lastPart = last - (last + 1 - first) % 4;
 52                         if (first <= lastVertexId && lastVertexId <= lastPart)
 53                         {
 54                             var m = (lastVertexId - first) % 4;
 55                             result = (m == 1 || m == 2);
 56                         }
 57                     }
 58                     break;
 59                 case DrawMode.TriangleStrip:
 60                     if (vertexCount > 2)
 61                     {
 62                         result = vertexCount > 2;
 63                     }
 64                     break;
 65                 case DrawMode.TriangleFan:
 66                     if (vertexCount > 2)
 67                     {
 68                         result = vertexCount > 2;
 69                     }
 70                     break;
 71                 case DrawMode.Triangles:
 72                     if (vertexCount > 2)
 73                     {
 74                         if (first <= lastVertexId)
 75                         {
 76                             result = ((vertexCount % 3 == 0) && (lastVertexId <= last))
 77                                 || ((vertexCount % 3 == 1) && (lastVertexId < last))
 78                                 || ((vertexCount % 3 == 2) && (lastVertexId + 1 < last));
 79                         }
 80                     }
 81                     break;
 82                 case DrawMode.TriangleStripAdjacency:
 83                     if (vertexCount > 5)
 84                     {
 85                         var lastPart = last - (last + 1 - first) % 2;
 86                         if (first <= lastVertexId && lastVertexId <= lastPart)
 87                         {
 88                             result = (lastVertexId - first) % 2 == 0;
 89                         }
 90                     }
 91                     break;
 92                 case DrawMode.TrianglesAdjacency:
 93                     if (vertexCount > 5)
 94                     {
 95                         var lastPart = last - (last + 1 - first) % 6;
 96                         if (first <= lastVertexId && lastVertexId <= lastPart)
 97                         {
 98                             result = (lastVertexId - first) % 2 == 0;
 99                         }
100                     }
101                     break;
102                 case DrawMode.Patches:
103                     // not know what to do for now
104                     break;
105                 case DrawMode.QuadStrip:
106                     if (vertexCount > 3)
107                     {
108                         if (first <= lastVertexId && lastVertexId <= last)
109                         {
110                             result = (vertexCount % 2 == 0)
111                                 || (lastVertexId < last);
112                         }
113                     }
114                     break;
115                 case DrawMode.Quads:
116                     if (vertexCount > 3)
117                     {
118                         if (first <= lastVertexId && lastVertexId <= last)
119                         {
120                             var m = vertexCount % 4;
121                             if (m == 0) { result = true; }
122                             else if (m == 1) { result = lastVertexId + 0 < last; }
123                             else if (m == 2) { result = lastVertexId + 1 < last; }
124                             else if (m == 3) { result = lastVertexId + 2 < last; }
125                             else { throw new Exception("This should never happen!"); }
126                         }
127                     }
128                     break;
129                 case DrawMode.Polygon:
130                     if (vertexCount > 2)
131                     {
132                         result = (first <= lastVertexId && lastVertexId <= last);
133                     }
134                     break;
135                 default:
136                     throw new NotImplementedException();
137             }
138 
139             return result;
140         }
bool OnPrimitiveTest(uint lastVertexId, DrawMode mode)

OneIndexBuffer

如果是用glDrawElements(OriginalMode, ..);渲染,此時想拾取一個Point,那么我就不做類似的OnPrimitiveTest了。因為情況太復雜,且必須用MapBufferRange來檢測大量的頂點情況。而這僅僅是因為導入的IBufferable模型本身沒有使用某些頂點。沒用你就刪了它啊!這我就不管了。

 1         /// <summary>
 2         /// I don't know how to implement this method in a high effitiency way.
 3         /// So keep it like this.
 4         /// Also, why would someone use glDrawElements() when rendering GL_POINTS?
 5         /// </summary>
 6         /// <param name="lastVertexId"></param>
 7         /// <param name="mode"></param>
 8         /// <returns></returns>
 9         private bool OnPrimitiveTest(uint lastVertexId, DrawMode mode)
10         {
11             return true;
12         }

拾取Line

ZeroIndexRenderer

如果是在GL_LINE*下拾取線,那么這是上一篇文章已經實現了的情況。如果是想在GL_TRIANGLE*、GL_QUAD*、GL_POLYGON模式下拾取其某個圖元的某條Line,那么就分兩部走:第一,像上一篇一樣拾取圖元;第二,設計一個新的小小的索引,即用GL_LINES模式渲染此圖元(三角形、四邊形、多邊形)的所有邊的索引。用此索引重新執行渲染、拾取,那么就可以找到鼠標所在位置的Line了。

例如,下面是在一個三角形圖元中找到那個你想要的Line的過程。

 1     class ZeroIndexLineInTriangleSearcher : ZeroIndexLineSearcher
 2     {
 3         /// <summary>
 4         /// 在三角形圖元中拾取指定位置的Line
 5         /// </summary>
 6         /// <param name="arg">渲染參數</param>
 7         /// <param name="x">指定位置</param>
 8         /// <param name="y">指定位置</param>
 9         /// <param name="lastVertexId">三角形圖元的最后一個頂點</param>
10         /// <param name="modernRenderer">目標Renderer</param>
11         /// <returns></returns>
12         internal override uint[] Search(RenderEventArgs arg,
13             int x, int y, 
14             uint lastVertexId, ZeroIndexRenderer modernRenderer)
15         {
16             // 創建臨時索引
17             OneIndexBufferPtr indexBufferPtr = null;
18             using (var buffer = new OneIndexBuffer<uint>(DrawMode.Lines, BufferUsage.StaticDraw))
19             {
20                 buffer.Alloc(6);
21                 unsafe
22                 {
23                     var array = (uint*)buffer.FirstElement();
24                     array[0] = lastVertexId - 1; array[1] = lastVertexId - 0;
25                     array[2] = lastVertexId - 2; array[3] = lastVertexId - 1;
26                     array[4] = lastVertexId - 0; array[5] = lastVertexId - 2;
27                 }
28 
29                 indexBufferPtr = buffer.GetBufferPtr() as OneIndexBufferPtr;
30             }
31 
32             // 用臨時索引渲染此三角形圖元(僅渲染此三角形圖元)
33             modernRenderer.Render4InnerPicking(arg, indexBufferPtr);
34             // id是拾取到的Line的Last Vertex Id
35             uint id = ColorCodedPicking.ReadPixel(x, y, arg.CanvasRect.Height);
36 
37             indexBufferPtr.Dispose();
38 
39             // 對比臨時索引,找到那個Line
40             if (id + 2 == lastVertexId)
41             { return new uint[] { id + 2, id, }; }
42             else
43             { return new uint[] { id - 1, id, }; }
44         }
45     }

OneIndexBuffer

用glDrawElements()時,實現思路與上面一樣,只不過Index參數變化一下而已。

在(CSharpGL(18)分別處理glDrawArrays()和glDrawElements()兩種方式下的拾取(ColorCodedPicking)),已經能夠找到目標圖元的所有頂點,所以就簡單了。

繼續用"在一個三角形圖元中找到那個你想要的Line的過程"來舉例。

 1     class OneIndexLineInTrianglesSearcher : OneIndexLineSearcher
 2     {
 3         internal override uint[] Search(RenderEventArgs arg,
 4             int x, int y,
 5             RecognizedPrimitiveIndex lastIndexId,
 6             OneIndexRenderer modernRenderer)
 7         {
 8             if (lastIndexId.IndexIdList.Count != 3) { throw new ArgumentException(); }
 9             List<uint> indexList = lastIndexId.IndexIdList;
10             if (indexList[0] == indexList[1]) { return new uint[] { indexList[0], indexList[2], }; }
11             else if (indexList[0] == indexList[2]) { return new uint[] { indexList[0], indexList[1], }; }
12             else if (indexList[1] == indexList[2]) { return new uint[] { indexList[1], indexList[0], }; }
13 
14             OneIndexBufferPtr indexBufferPtr = null;
15             using (var buffer = new OneIndexBuffer<uint>(DrawMode.Lines, BufferUsage.StaticDraw))
16             {
17                 buffer.Alloc(6);
18                 unsafe
19                 {
20                     var array = (uint*)buffer.FirstElement();
21                     array[0] = indexList[0]; array[1] = indexList[1];
22                     array[2] = indexList[1]; array[3] = indexList[2];
23                     array[4] = indexList[2]; array[5] = indexList[0];
24                 }
25 
26                 indexBufferPtr = buffer.GetBufferPtr() as OneIndexBufferPtr;
27             }
28 
29             modernRenderer.Render4InnerPicking(arg, indexBufferPtr);
30             uint id = ColorCodedPicking.ReadPixel(x, y, arg.CanvasRect.Height);
31 
32             indexBufferPtr.Dispose();
33 
34             if (id == indexList[1])
35             { return new uint[] { indexList[0], indexList[1], }; }
36             else if (id == indexList[2])
37             { return new uint[] { indexList[1], indexList[2], }; }
38             else if (id == indexList[0])
39             { return new uint[] { indexList[2], indexList[0], }; }
40             else
41             { throw new Exception("This should not happen!"); }
42         }
43     }

Polygon

這里順便提一下GL_POLYGON,這是個特別的圖元,因為它的頂點數是不確定的。它產生的臨時小索引就可能不再小。但神奇的是,它不再需要OneIndexBufferPtr類型的臨時索引,而只需一個幾乎不占空間的ZeroIndexBufferPtr。

 1     class ZeroIndexLineInPolygonSearcher : ZeroIndexLineSearcher
 2     {
 3         internal override uint[] Search(RenderEventArgs arg,
 4             int x, int y,
 5             uint lastVertexId, ZeroIndexRenderer modernRenderer)
 6         {
 7             var zeroIndexBufferPtr = modernRenderer.GetIndexBufferPtr() as ZeroIndexBufferPtr;
 8             ZeroIndexBufferPtr indexBufferPtr = null;
 9             // when the temp index buffer could be long, it's no longer needed. 
10             // what a great OpenGL API design!
11             using (var buffer = new ZeroIndexBuffer(DrawMode.LineLoop,
12                 zeroIndexBufferPtr.FirstVertex, zeroIndexBufferPtr.VertexCount))
13             {
14                 indexBufferPtr = buffer.GetBufferPtr() as ZeroIndexBufferPtr;
15             }
16             modernRenderer.Render4InnerPicking(arg, indexBufferPtr);
17             uint id = ColorCodedPicking.ReadPixel(x, y, arg.CanvasRect.Height);
18 
19             indexBufferPtr.Dispose();
20 
21             if (id == zeroIndexBufferPtr.FirstVertex)
22             { return new uint[] { (uint)(zeroIndexBufferPtr.FirstVertex + zeroIndexBufferPtr.VertexCount - 1), id, }; }
23             else
24             { return new uint[] { id - 1, id, }; }
25         }
26     }

拾取本身

所謂拾取本身,就是:如果用GL_TRIANGLE*進行渲染,就拾取一個Triangle;如果用GL_QUAD*進行渲染,就拾取一個Quad;如果用GL_POLYGON進行渲染,就拾取一個Polygon。這都是在(CSharpGL(18)分別處理glDrawArrays()和glDrawElements()兩種方式下的拾取(ColorCodedPicking))中已經實現了的功能。

整合

三種情況都解決了,下面整合進來就行了。

ZeroIndexRenderer

這是對ZeroIndexRenderer的Pick。

 1         public override PickedGeometry Pick(RenderEventArgs arg, uint stageVertexId,
 2             int x, int y)
 3         {
 4             uint lastVertexId;
 5             if (!this.GetLastVertexIdOfPickedGeometry(stageVertexId, out lastVertexId))
 6             { return null; }
 7 
 8             GeometryType geometryType = arg.PickingGeometryType;
 9 
10             if (geometryType == GeometryType.Point)
11             {
12                 DrawMode mode = this.GetIndexBufferPtr().Mode;
13                 if (this.OnPrimitiveTest(lastVertexId, mode))
14                 { return PickPoint(stageVertexId, lastVertexId); }
15                 else
16                 { return null; }
17             }
18             else if (geometryType == GeometryType.Line)
19             {
20                 DrawMode mode = this.GetIndexBufferPtr().Mode;
21                 GeometryType typeOfMode = mode.ToGeometryType();
22                 if (geometryType == typeOfMode)
23                 { return PickWhateverItIs(stageVertexId, lastVertexId, mode, typeOfMode); }
24                 else
25                 {
26                     ZeroIndexLineSearcher searcher = GetLineSearcher(mode);
27                     if (searcher != null)// line is from triangle, quad or polygon
28                     { return SearchLine(arg, stageVertexId, x, y, lastVertexId, searcher); }
29                     else if (mode == DrawMode.Points)// want a line when rendering GL_POINTS
30                     { return null; }
31                     else
32                     { throw new Exception(string.Format("Lack of searcher for [{0}]", mode)); }
33                 }
34             }
35             else
36             {
37                 DrawMode mode = this.GetIndexBufferPtr().Mode;
38                 GeometryType typeOfMode = mode.ToGeometryType();
39                 if (typeOfMode == geometryType)// I want what it is
40                 { return PickWhateverItIs(stageVertexId, lastVertexId, mode, typeOfMode); }
41                 else
42                 { return null; }
43                 //{ throw new Exception(string.Format("Lack of searcher for [{0}]", mode)); }
44             }
45         }
public override PickedGeometry Pick(RenderEventArgs arg, uint stageVertexId, int x, int y)

OneIndexRenderer

這是對OneIndexRenderer的Pick。

 1         public override PickedGeometry Pick(RenderEventArgs arg, uint stageVertexId,
 2             int x, int y)
 3         {
 4             uint lastVertexId;
 5             if (!this.GetLastVertexIdOfPickedGeometry(stageVertexId, out lastVertexId))
 6             { return null; }
 7 
 8             GeometryType geometryType = arg.PickingGeometryType;
 9 
10             if (geometryType == GeometryType.Point)
11             {
12                 DrawMode mode = this.GetIndexBufferPtr().Mode;
13                 if (this.OnPrimitiveTest(lastVertexId, mode))
14                 { return PickPoint(stageVertexId, lastVertexId); }
15                 else
16                 { return null; }
17             }
18             else if (geometryType == GeometryType.Line)
19             {
20                 // 找到 lastIndexId
21                 RecognizedPrimitiveIndex lastIndexId = this.GetLastIndexIdOfPickedGeometry(
22                     arg, lastVertexId, x, y);
23                 if (lastIndexId == null)
24                 {
25                     Debug.WriteLine(
26                         "Got lastVertexId[{0}] but no lastIndexId! Params are [{1}] [{2}] [{3}] [{4}]",
27                         lastVertexId, arg, stageVertexId, x, y);
28                     { return null; }
29                 }
30                 else
31                 {
32                     // 獲取pickedGeometry
33                     DrawMode mode = this.GetIndexBufferPtr().Mode;
34                     GeometryType typeOfMode = mode.ToGeometryType();
35                     if (geometryType == typeOfMode)
36                     { return PickWhateverItIs(stageVertexId, lastIndexId, typeOfMode); }
37                     else
38                     {
39                         OneIndexLineSearcher searcher = GetLineSearcher(mode);
40                         if (searcher != null)// line is from triangle, quad or polygon
41                         { return SearchLine(arg, stageVertexId, x, y, lastVertexId, lastIndexId, searcher); }
42                         else if (mode == DrawMode.Points)// want a line when rendering GL_POINTS
43                         { return null; }
44                         else
45                         { throw new Exception(string.Format("Lack of searcher for [{0}]", mode)); }
46                     }
47                 }
48             }
49             else
50             {
51                 // 找到 lastIndexId
52                 RecognizedPrimitiveIndex lastIndexId = this.GetLastIndexIdOfPickedGeometry(
53                     arg, lastVertexId, x, y);
54                 if (lastIndexId == null)
55                 {
56                     Debug.WriteLine(
57                         "Got lastVertexId[{0}] but no lastIndexId! Params are [{1}] [{2}] [{3}] [{4}]",
58                         lastVertexId, arg, stageVertexId, x, y);
59                     { return null; }
60                 }
61                 else
62                 {
63                     DrawMode mode = this.GetIndexBufferPtr().Mode;
64                     GeometryType typeOfMode = mode.ToGeometryType();
65                     if (typeOfMode == geometryType)// I want what it is
66                     { return PickWhateverItIs(stageVertexId, lastIndexId, typeOfMode); }
67                     else
68                     { return null; }
69                     //{ throw new Exception(string.Format("Lack of searcher for [{0}]", mode)); }
70                 }
71             }
72         }
public override PickedGeometry Pick(RenderEventArgs arg, uint stageVertexId, int x, int y)

總結

在完成后,我以為徹底解決了拾取問題。等完成本文后,我不再這么想了。還是謙虛點好。

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

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


文章列表


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

    IT工程師數位筆記本

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