zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    Atitit. visual studio vs2003 vs2005 vs2008  VS2010 vs2012 vs2015新特性 新功能.doc
    Atitit. C#.net clr 2.0  4.0新特性
    Atitit. C#.net clr 2.0  4.0新特性
    Atitit.通过null 参数 反射  动态反推方法调用
    Atitit.通过null 参数 反射  动态反推方法调用
    Atitit..net clr il指令集 以及指令分类  与指令详细说明
    Atitit..net clr il指令集 以及指令分类  与指令详细说明
    Atitit.变量的定义 获取 储存 物理结构 基本类型简化 隐式转换 类型推导 与底层原理 attilaxDSL
    Atitit.变量的定义 获取 储存 物理结构 基本类型简化 隐式转换 类型推导 与底层原理 attilaxDSL
    Atitit.跨语言反射api 兼容性提升与增强 java c#。Net  php  js
  • 原文地址:https://www.cnblogs.com/bitzhuwei/p/picking-and-dragging-point-line-or-primitive-inside-any-VBO.html
Copyright © 2011-2022 走看看