• 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

  • 相关阅读:
    记录我的第一次电话面试
    Spring整合Mybatis出现Access denied for user 'Think'@'localhost' (using password: YES)
    Lombok基本使用
    log4j整理
    mybatis常用的配置解析
    Java实现邮件发送
    Java获取UUID
    Java实现文件下载
    Java实现文件上传
    Java跳出多层for循环的4种方式
  • 原文地址:https://www.cnblogs.com/bitzhuwei/p/picking-and-dragging-point-line-or-primitive-inside-any-VBO.html
Copyright © 2020-2023  润新知