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传入如下的参数:

 GeometryType PickingGeometryType = Geometry.Point;
var arg = new RenderEventArgs(
// 为了拾取而进行的渲染
RenderModes.ColorCodedPicking,
this.glCanvas1.ClientRectangle,
this.camera,
// 我想拾取的类型(Geometry)
this.PickingGeometryType);
// 要拾取的位置(鼠标位置)
Point mousePostion = GetMousePosition();
// 支持Picking的Renderer列表
PickableRenderer[] pickableElements = GetRenderersInScene();
// 执行拾取操作
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后要判断一下是否真的应该拾取到它。

         /// <summary>
/// 现在,已经判定了鼠标在某个点上。
/// 我需要判定此点是否出现在图元上。
/// now that I know the mouse is picking on some point,
/// I need to make sure that point should appear.
/// </summary>
/// <param name="lastVertexId"></param>
/// <param name="mode"></param>
/// <returns></returns>
private bool OnPrimitiveTest(uint lastVertexId, DrawMode mode)
{
bool result = false;
int first = this.zeroIndexBufferPtr.FirstVertex;
if (first < ) { return false; }
int vertexCount = this.zeroIndexBufferPtr.VertexCount;
if (vertexCount <= ) { return false; }
int last = first + vertexCount - ;
switch (mode)
{
case DrawMode.Points:
result = true;
break;
case DrawMode.LineStrip:
result = vertexCount > ;
break;
case DrawMode.LineLoop:
result = vertexCount > ;
break;
case DrawMode.Lines:
if (vertexCount > )
{
if (vertexCount % == )
{
result = (first <= lastVertexId && lastVertexId <= last);
}
else
{
result = (first <= lastVertexId && lastVertexId <= last - );
}
}
break;
case DrawMode.LineStripAdjacency:
if (vertexCount > )
{
result = (first < lastVertexId && lastVertexId < last);
}
break;
case DrawMode.LinesAdjacency:
if (vertexCount > )
{
var lastPart = last - (last + - first) % ;
if (first <= lastVertexId && lastVertexId <= lastPart)
{
var m = (lastVertexId - first) % ;
result = (m == || m == );
}
}
break;
case DrawMode.TriangleStrip:
if (vertexCount > )
{
result = vertexCount > ;
}
break;
case DrawMode.TriangleFan:
if (vertexCount > )
{
result = vertexCount > ;
}
break;
case DrawMode.Triangles:
if (vertexCount > )
{
if (first <= lastVertexId)
{
result = ((vertexCount % == ) && (lastVertexId <= last))
|| ((vertexCount % == ) && (lastVertexId < last))
|| ((vertexCount % == ) && (lastVertexId + < last));
}
}
break;
case DrawMode.TriangleStripAdjacency:
if (vertexCount > )
{
var lastPart = last - (last + - first) % ;
if (first <= lastVertexId && lastVertexId <= lastPart)
{
result = (lastVertexId - first) % == ;
}
}
break;
case DrawMode.TrianglesAdjacency:
if (vertexCount > )
{
var lastPart = last - (last + - first) % ;
if (first <= lastVertexId && lastVertexId <= lastPart)
{
result = (lastVertexId - first) % == ;
}
}
break;
case DrawMode.Patches:
// not know what to do for now
break;
case DrawMode.QuadStrip:
if (vertexCount > )
{
if (first <= lastVertexId && lastVertexId <= last)
{
result = (vertexCount % == )
|| (lastVertexId < last);
}
}
break;
case DrawMode.Quads:
if (vertexCount > )
{
if (first <= lastVertexId && lastVertexId <= last)
{
var m = vertexCount % ;
if (m == ) { result = true; }
else if (m == ) { result = lastVertexId + < last; }
else if (m == ) { result = lastVertexId + < last; }
else if (m == ) { result = lastVertexId + < last; }
else { throw new Exception("This should never happen!"); }
}
}
break;
case DrawMode.Polygon:
if (vertexCount > )
{
result = (first <= lastVertexId && lastVertexId <= last);
}
break;
default:
throw new NotImplementedException();
} return result;
}

bool OnPrimitiveTest(uint lastVertexId, DrawMode mode)

OneIndexBuffer

如果是用glDrawElements(OriginalMode, ..);渲染,此时想拾取一个Point,那么我就不做类似的OnPrimitiveTest了。因为情况太复杂,且必须用MapBufferRange来检测大量的顶点情况。而这仅仅是因为导入的IBufferable模型本身没有使用某些顶点。没用你就删了它啊!这我就不管了。

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

拾取Line

ZeroIndexRenderer

如果是在GL_LINE*下拾取线,那么这是上一篇文章已经实现了的情况。如果是想在GL_TRIANGLE*、GL_QUAD*、GL_POLYGON模式下拾取其某个图元的某条Line,那么就分两部走:第一,像上一篇一样拾取图元;第二,设计一个新的小小的索引,即用GL_LINES模式渲染此图元(三角形、四边形、多边形)的所有边的索引。用此索引重新执行渲染、拾取,那么就可以找到鼠标所在位置的Line了。

例如,下面是在一个三角形图元中找到那个你想要的Line的过程。

     class ZeroIndexLineInTriangleSearcher : ZeroIndexLineSearcher
{
/// <summary>
/// 在三角形图元中拾取指定位置的Line
/// </summary>
/// <param name="arg">渲染参数</param>
/// <param name="x">指定位置</param>
/// <param name="y">指定位置</param>
/// <param name="lastVertexId">三角形图元的最后一个顶点</param>
/// <param name="modernRenderer">目标Renderer</param>
/// <returns></returns>
internal override uint[] Search(RenderEventArgs arg,
int x, int y,
uint lastVertexId, ZeroIndexRenderer modernRenderer)
{
// 创建临时索引
OneIndexBufferPtr indexBufferPtr = null;
using (var buffer = new OneIndexBuffer<uint>(DrawMode.Lines, BufferUsage.StaticDraw))
{
buffer.Alloc();
unsafe
{
var array = (uint*)buffer.FirstElement();
array[] = lastVertexId - ; array[] = lastVertexId - ;
array[] = lastVertexId - ; array[] = lastVertexId - ;
array[] = lastVertexId - ; array[] = lastVertexId - ;
} indexBufferPtr = buffer.GetBufferPtr() as OneIndexBufferPtr;
} // 用临时索引渲染此三角形图元(仅渲染此三角形图元)
modernRenderer.Render4InnerPicking(arg, indexBufferPtr);
// id是拾取到的Line的Last Vertex Id
uint id = ColorCodedPicking.ReadPixel(x, y, arg.CanvasRect.Height); indexBufferPtr.Dispose(); // 对比临时索引,找到那个Line
if (id + == lastVertexId)
{ return new uint[] { id + , id, }; }
else
{ return new uint[] { id - , id, }; }
}
}

OneIndexBuffer

用glDrawElements()时,实现思路与上面一样,只不过Index参数变化一下而已。

在(CSharpGL(18)分别处理glDrawArrays()和glDrawElements()两种方式下的拾取(ColorCodedPicking)),已经能够找到目标图元的所有顶点,所以就简单了。

继续用"在一个三角形图元中找到那个你想要的Line的过程"来举例。

     class OneIndexLineInTrianglesSearcher : OneIndexLineSearcher
{
internal override uint[] Search(RenderEventArgs arg,
int x, int y,
RecognizedPrimitiveIndex lastIndexId,
OneIndexRenderer modernRenderer)
{
if (lastIndexId.IndexIdList.Count != ) { throw new ArgumentException(); }
List<uint> indexList = lastIndexId.IndexIdList;
if (indexList[] == indexList[]) { return new uint[] { indexList[], indexList[], }; }
else if (indexList[] == indexList[]) { return new uint[] { indexList[], indexList[], }; }
else if (indexList[] == indexList[]) { return new uint[] { indexList[], indexList[], }; } OneIndexBufferPtr indexBufferPtr = null;
using (var buffer = new OneIndexBuffer<uint>(DrawMode.Lines, BufferUsage.StaticDraw))
{
buffer.Alloc();
unsafe
{
var array = (uint*)buffer.FirstElement();
array[] = indexList[]; array[] = indexList[];
array[] = indexList[]; array[] = indexList[];
array[] = indexList[]; array[] = indexList[];
} indexBufferPtr = buffer.GetBufferPtr() as OneIndexBufferPtr;
} modernRenderer.Render4InnerPicking(arg, indexBufferPtr);
uint id = ColorCodedPicking.ReadPixel(x, y, arg.CanvasRect.Height); indexBufferPtr.Dispose(); if (id == indexList[])
{ return new uint[] { indexList[], indexList[], }; }
else if (id == indexList[])
{ return new uint[] { indexList[], indexList[], }; }
else if (id == indexList[])
{ return new uint[] { indexList[], indexList[], }; }
else
{ throw new Exception("This should not happen!"); }
}
}

Polygon

这里顺便提一下GL_POLYGON,这是个特别的图元,因为它的顶点数是不确定的。它产生的临时小索引就可能不再小。但神奇的是,它不再需要OneIndexBufferPtr类型的临时索引,而只需一个几乎不占空间的ZeroIndexBufferPtr。

     class ZeroIndexLineInPolygonSearcher : ZeroIndexLineSearcher
{
internal override uint[] Search(RenderEventArgs arg,
int x, int y,
uint lastVertexId, ZeroIndexRenderer modernRenderer)
{
var zeroIndexBufferPtr = modernRenderer.GetIndexBufferPtr() as ZeroIndexBufferPtr;
ZeroIndexBufferPtr indexBufferPtr = null;
// when the temp index buffer could be long, it's no longer needed.
// what a great OpenGL API design!
using (var buffer = new ZeroIndexBuffer(DrawMode.LineLoop,
zeroIndexBufferPtr.FirstVertex, zeroIndexBufferPtr.VertexCount))
{
indexBufferPtr = buffer.GetBufferPtr() as ZeroIndexBufferPtr;
}
modernRenderer.Render4InnerPicking(arg, indexBufferPtr);
uint id = ColorCodedPicking.ReadPixel(x, y, arg.CanvasRect.Height); indexBufferPtr.Dispose(); if (id == zeroIndexBufferPtr.FirstVertex)
{ return new uint[] { (uint)(zeroIndexBufferPtr.FirstVertex + zeroIndexBufferPtr.VertexCount - ), id, }; }
else
{ return new uint[] { id - , id, }; }
}
}

拾取本身

所谓拾取本身,就是:如果用GL_TRIANGLE*进行渲染,就拾取一个Triangle;如果用GL_QUAD*进行渲染,就拾取一个Quad;如果用GL_POLYGON进行渲染,就拾取一个Polygon。这都是在(CSharpGL(18)分别处理glDrawArrays()和glDrawElements()两种方式下的拾取(ColorCodedPicking))中已经实现了的功能。

整合

三种情况都解决了,下面整合进来就行了。

ZeroIndexRenderer

这是对ZeroIndexRenderer的Pick。

         public override PickedGeometry Pick(RenderEventArgs arg, uint stageVertexId,
int x, int y)
{
uint lastVertexId;
if (!this.GetLastVertexIdOfPickedGeometry(stageVertexId, out lastVertexId))
{ return null; } GeometryType geometryType = arg.PickingGeometryType; if (geometryType == GeometryType.Point)
{
DrawMode mode = this.GetIndexBufferPtr().Mode;
if (this.OnPrimitiveTest(lastVertexId, mode))
{ return PickPoint(stageVertexId, lastVertexId); }
else
{ return null; }
}
else if (geometryType == GeometryType.Line)
{
DrawMode mode = this.GetIndexBufferPtr().Mode;
GeometryType typeOfMode = mode.ToGeometryType();
if (geometryType == typeOfMode)
{ return PickWhateverItIs(stageVertexId, lastVertexId, mode, typeOfMode); }
else
{
ZeroIndexLineSearcher searcher = GetLineSearcher(mode);
if (searcher != null)// line is from triangle, quad or polygon
{ return SearchLine(arg, stageVertexId, x, y, lastVertexId, searcher); }
else if (mode == DrawMode.Points)// want a line when rendering GL_POINTS
{ return null; }
else
{ throw new Exception(string.Format("Lack of searcher for [{0}]", mode)); }
}
}
else
{
DrawMode mode = this.GetIndexBufferPtr().Mode;
GeometryType typeOfMode = mode.ToGeometryType();
if (typeOfMode == geometryType)// I want what it is
{ return PickWhateverItIs(stageVertexId, lastVertexId, mode, typeOfMode); }
else
{ return null; }
//{ throw new Exception(string.Format("Lack of searcher for [{0}]", mode)); }
}
}

public override PickedGeometry Pick(RenderEventArgs arg, uint stageVertexId, int x, int y)

OneIndexRenderer

这是对OneIndexRenderer的Pick。

         public override PickedGeometry Pick(RenderEventArgs arg, uint stageVertexId,
int x, int y)
{
uint lastVertexId;
if (!this.GetLastVertexIdOfPickedGeometry(stageVertexId, out lastVertexId))
{ return null; } GeometryType geometryType = arg.PickingGeometryType; if (geometryType == GeometryType.Point)
{
DrawMode mode = this.GetIndexBufferPtr().Mode;
if (this.OnPrimitiveTest(lastVertexId, mode))
{ return PickPoint(stageVertexId, lastVertexId); }
else
{ return null; }
}
else if (geometryType == GeometryType.Line)
{
// 找到 lastIndexId
RecognizedPrimitiveIndex lastIndexId = this.GetLastIndexIdOfPickedGeometry(
arg, lastVertexId, x, y);
if (lastIndexId == null)
{
Debug.WriteLine(
"Got lastVertexId[{0}] but no lastIndexId! Params are [{1}] [{2}] [{3}] [{4}]",
lastVertexId, arg, stageVertexId, x, y);
{ return null; }
}
else
{
// 获取pickedGeometry
DrawMode mode = this.GetIndexBufferPtr().Mode;
GeometryType typeOfMode = mode.ToGeometryType();
if (geometryType == typeOfMode)
{ return PickWhateverItIs(stageVertexId, lastIndexId, typeOfMode); }
else
{
OneIndexLineSearcher searcher = GetLineSearcher(mode);
if (searcher != null)// line is from triangle, quad or polygon
{ return SearchLine(arg, stageVertexId, x, y, lastVertexId, lastIndexId, searcher); }
else if (mode == DrawMode.Points)// want a line when rendering GL_POINTS
{ return null; }
else
{ throw new Exception(string.Format("Lack of searcher for [{0}]", mode)); }
}
}
}
else
{
// 找到 lastIndexId
RecognizedPrimitiveIndex lastIndexId = this.GetLastIndexIdOfPickedGeometry(
arg, lastVertexId, x, y);
if (lastIndexId == null)
{
Debug.WriteLine(
"Got lastVertexId[{0}] but no lastIndexId! Params are [{1}] [{2}] [{3}] [{4}]",
lastVertexId, arg, stageVertexId, x, y);
{ return null; }
}
else
{
DrawMode mode = this.GetIndexBufferPtr().Mode;
GeometryType typeOfMode = mode.ToGeometryType();
if (typeOfMode == geometryType)// I want what it is
{ return PickWhateverItIs(stageVertexId, lastIndexId, typeOfMode); }
else
{ return null; }
//{ throw new Exception(string.Format("Lack of searcher for [{0}]", mode)); }
}
}
}

public override PickedGeometry Pick(RenderEventArgs arg, uint stageVertexId, int x, int y)

总结

在完成后,我以为彻底解决了拾取问题。等完成本文后,我不再这么想了。还是谦虚点好。

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

欢迎对OpenGL有兴趣的同学关注(https://github.com/bitzhuwei/CSharpGL

CSharpGL(21)用鼠标拾取、拖拽VBO图元内的点、线或本身的更多相关文章

  1. Qt窗口添加鼠标移动拖拽事件

    1. .h文件中添加 private:    QPoint dragPosition; 2. 在cpp文件中重写鼠标点击和拖拽函数 void ShapeWidget::mousePressEvent( ...

  2. JS鼠标的拖拽原理

    拖拽功能主要是用在让用户做一些自定义的动作,比如拖动排序,弹出框拖动移动等等,效果还是蛮不错的.下面讲解一下拖拽的原理,希望可以帮助到有需要的朋友! 一.拖拽的流程动作①鼠标按下②鼠标移动③鼠标松开 ...

  3. 鼠标事件-拖拽2(不能拖出指定对象的div)

    <!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>& ...

  4. 鼠标事件-拖拽(不能拖出窗口的div)

    <!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>& ...

  5. CSharpGL(20)用unProject和Project实现鼠标拖拽图元

    CSharpGL(20)用unProject和Project实现鼠标拖拽图元 效果图 例如,你可以把Big Dipper这个模型拽成下面这个样子. 配合旋转,还可以继续拖拽成这样. 当然,能拖拽的不只 ...

  6. Arcgis for qml - 鼠标拖拽移动

    以实现鼠标拖拽文本图层为例 GitHub:ArcGIS拖拽文本 作者:狐狸家的鱼 目的是利用鼠标进行拖拽. 实现两种模式,一种是屏幕上的拖拽,第二种是地图上图层的挪动. 屏幕上的拖拽其实跟ArcGIS ...

  7. 鼠标拖拽移动Java界面组件

    默认的,Frame或者JFrame自身已经实现了鼠标拖拽标题栏移动窗口的功能. 只是,当你不满意java的JFrame样式,隐藏了标题栏和边框,又或者干脆直接使用JWindow,那你又该怎么实现鼠标拖 ...

  8. 完美实现鼠标拖拽事件,解决各种小bug,基于jquery

    鼠标拖拽事件是web中使用频率极高的事件,之前写过的代码包括网上的代码,总存在各种各样的问题,包括拖拽体验差,松开鼠标后拖拽效果仍存在以及代码冗余过大等 本次我才用jQuery实现一个尽可能高效的拖拽 ...

  9. Javascript之盒子拖拽(跟随鼠标、边界限定、轨迹回放)

    本文通过拖拽案例,实现"跟随鼠标.边界限定.轨迹回放"三大效果: 完整代码中有详尽注释,故不再进行细致讲解: 对于案例中需要注意的重点或易错点问题,会总结在最后. 效果图(仅演示左 ...

随机推荐

  1. html中如何添加提示信息

    提示:在标签中添加title属性 1.文本中如何添加提示信息? 1.1直接在标签中加title="值": 例如:<p title="爱笑,爱哭,爱生活"& ...

  2. SQL Server技术内幕笔记合集

    SQL Server技术内幕笔记合集 发这一篇文章主要是方便大家找到我的笔记入口,方便大家o(∩_∩)o Microsoft SQL Server 6.5 技术内幕 笔记http://www.cnbl ...

  3. 在SQL2008查找某数据库中的列是否存在某个值

    在SQL2008查找某数据库中的列是否存在某个值 --SQL2008查找某数据库中的列是否存在某个值 create proc spFind_Column_In_DB ( @type int,--类型: ...

  4. SQLSERVER走起微信公众帐号全新改版 全新首页

    SQLSERVER走起微信公众帐号全新改版 全新首页 今天,SQLSERVER走起微信公众帐号增加了首页功能 虽然还是订阅号,不过已经对版面做了比较大的修改,希望各位亲用得放心.用得安心O(∩_∩)O ...

  5. 【原创】免费申请SSL证书【用于HTTPS,即是把网站从HTTP改为HTTPS,加密传输数据,保护敏感数据】

    今天公司有个网站需要改用https访问,所以就用到SSL证书.由于沃通(以前我是在这里申请的)暂停了免费的SSL证书之后,其网站推荐了新的一个网站来申请证书,所以,今天因为刚好又要申请一个证书,所以, ...

  6. C#中如何创建PDF网格并插入图片

    这篇文章我将向大家演示如何以编程的方式在PDF文档中创建一个网格,并将图片插入特定的网格中. 网上有一些类似的解决方法,在这里我选择了一个免费版的PDF组件.安装控件后,创建新项目,添加安装目录下的d ...

  7. python 入门笔记

    1.pip包安装 pip install *** pip 中http和https代理设置(/etc/profile) 2.强制保存 :w !sudo tee % 3.cffi是python调用C的包 ...

  8. HIVE教程

    完整PDF下载:<HIVE简明教程> 前言 Hive是对于数据仓库进行管理和分析的工具.但是不要被“数据仓库”这个词所吓倒,数据仓库是很复杂的东西,但是如果你会SQL,就会发现Hive是那 ...

  9. 【Java每日一题】20170103

    20161230问题解析请点击今日问题下方的"[Java每日一题]20170103"查看(问题解析在公众号首发,公众号ID:weknow619) package Jan2017; ...

  10. 电信计费业务:预后融合OCS到底应该实扣还是虚扣?

    引入OCS的初衷之一是为了让计费系统能够参与到用户的通讯控制中来,也就是所谓的实时信控.用户在没有余额时,通讯就会被停止,不会造成"天价欠费 ",一方面保障用户的利益,一方面也保障 ...