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 }
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 }
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 }
总结
在完成后,我以为彻底解决了拾取问题。等完成本文后,我不再这么想了。还是谦虚点好。
原CSharpGL的其他功能(3ds解析器、TTF2Bmp、CSSL等),我将逐步加入新CSharpGL。
欢迎对OpenGL有兴趣的同学关注(https://github.com/bitzhuwei/CSharpGL)
来源:https://www.cnblogs.com/bitzhuwei/p/picking-and-dragging-point-line-or-primitive-inside-any-VBO.html