CSharpGL(21)用鼠标拾取、拖拽VBO图元内的点、线或本身

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

posted @ 2016-05-03 19:21  BIT祝威  阅读(2549)  评论(6编辑  收藏  举报