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

效果图

例如,你可以把Big Dipper这个模型拽成下面这个样子。

配合旋转,还可以继续拖拽成这样。

当然,能拖拽的不只是线段。还可以拖拽三角形(如下图)、四边形。

另外,还可以单点拖拽。

2016-04-28

现在实现了高亮显示拾取、拖拽的图元的功能。

下面演示了鼠标移动到图元上时显示图元的索引值的功能。

起初会出现stitching和z-fighting的现象。例如下面选中一个三角形时,由于stitching问题,没高亮其斜边。

于是我添加了PolygonOffsetSwtich开关,解决了这个问题。

下载

CSharpGL已在GitHub开源,欢迎对OpenGL有兴趣的同学加入(https://github.com/bitzhuwei/CSharpGL)

unProject/Project

这个两个函数的执行结果完全相反。拖拽功能全靠他们了。

Project把模型坐标系上的点转换为窗口坐标系上的点。这可以通过其实现代码来验证。

         /// <summary>
/// Map the specified object coordinates (obj.x, obj.y, obj.z) into window coordinates.
/// </summary>
/// <param name="obj">The object.</param>
/// <param name="model">The model.</param>
/// <param name="proj">The proj.</param>
/// <param name="viewport">The viewport.</param>
/// <returns></returns>
public static vec3 project(vec3 obj, mat4 model, mat4 proj, vec4 viewport)
{
vec4 tmp = new vec4(obj, (1f));
tmp = model * tmp;
tmp = proj * tmp; tmp /= tmp.w;
tmp = tmp * 0.5f + new vec4(0.5f, 0.5f, 0.5f, 0.5f);
tmp[] = tmp[] * viewport[] + viewport[];
tmp[] = tmp[] * viewport[] + viewport[]; return new vec3(tmp.x, tmp.y, tmp.z);
}

通过试验发现,一个vec3经过Project后再经过unProject,会变回原来的值。这就是说,unProject把窗口坐标系上的点转换为模型坐标系上的点。

OpenGL是以窗口左下角为原点(0, 0)的。而Windows窗口是以左上角为原点的。所以用的时候要注意转换一下。

弄清楚了这两个函数,才能实现鼠标拖拽的功能。

拖拽原理

既然可以把模型空间的点转换为平面坐标系上的点,并且可以逆向操作。那么只需将要拖拽的点A通过project函数投影到屏幕上(变成a);根据鼠标在屏幕上的移动,相应的移动a,变成a',最后把a'通过unProject反射回模型空间,就是拖拽后的A'了。在VBO里,把A改为A'即可。

         /// <summary>
/// 根据<paramref name="differenceOnScreen"/>来修改指定索引处的顶点位置。
/// </summary>
/// <param name="differenceOnScreen"></param>
/// <param name="viewMatrix"></param>
/// <param name="projectionMatrix"></param>
/// <param name="viewport"></param>
/// <param name="positionIndexes"></param>
public void MovePositions(Point differenceOnScreen,
mat4 viewMatrix, mat4 projectionMatrix, vec4 viewport, uint[] positionIndexes)
{
if (positionIndexes == null) { return; }
if (positionIndexes.Length == ) { return; } GL.BindBuffer(BufferTarget.ArrayBuffer, this.positionBufferPtr.BufferId);
IntPtr pointer = GL.MapBuffer(BufferTarget.ArrayBuffer, MapBufferAccess.ReadWrite);
unsafe
{
var array = (vec3*)pointer.ToPointer();
for (int i = ; i < positionIndexes.Length; i++)
{
vec3 projected = glm.project(array[positionIndexes[i]],
viewMatrix, projectionMatrix, viewport);
vec3 newProjected = new vec3(projected.x + differenceOnScreen.X,
projected.y + differenceOnScreen.Y, projected.z);
array[positionIndexes[i]]=glm.unProject(newProjected,
viewMatrix, projectionMatrix, viewport);
}
}
GL.UnmapBuffer(BufferTarget.ArrayBuffer);
GL.BindBuffer(BufferTarget.ArrayBuffer, );
}

MouseDown

鼠标按下时,如果拾取到图元,就要为拖拽做准备。(如果想了解拾取的原理,可参考CSharpGL(18)分别处理glDrawArrays()和glDrawElements()两种方式下的拾取(ColorCodedPicking))

         private void glCanvas1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
// move vertex
PickedGeometry pickedGeometry = RunPicking(e.X, e.Y);
if (pickedGeometry != null)
{
var dragParam = new DragParam(pickedGeometry,
camera.GetProjectionMat4(),
camera.GetViewMat4(),
new Point(e.X, glCanvas1.Height - e.Y - ));
this.dragParam = dragParam;
}
}
}

其中的RunPicking就是执行一次拾取操作。

         private PickedGeometry RunPicking(int x, int y)
{
this.glCanvas1_OpenGLDraw(selectedModel, null);
IColorCodedPicking pickable = this.rendererDict[this.SelectedModel];
pickable.MVP = this.camera.GetProjectionMat4() * this.camera.GetViewMat4();
PickedGeometry pickedGeometry = ColorCodedPicking.Pick(
this.camera, x, y, this.glCanvas1.Width, this.glCanvas1.Height, pickable); return pickedGeometry;
}

这里有个dragParam类型,记录了按下后的一些数据。

     class DragParam
{ public PickedGeometry pickedGeometry;
public mat4 projectionMatrix;
public mat4 viewMatrix;
public Point lastMousePositionOnScreen;
public vec4 viewport; public DragParam(PickedGeometry pickedGeometry, mat4 projectionMatrix, mat4 viewMatrix, Point lastMousePositionOnScreen)
{
this.pickedGeometry = pickedGeometry;
this.projectionMatrix = projectionMatrix;
this.viewMatrix = viewMatrix;
this.lastMousePositionOnScreen = lastMousePositionOnScreen;
var viewport = new int[]; GL.GetInteger(GetTarget.Viewport, viewport);
this.viewport = new vec4(viewport[], viewport[], viewport[], viewport[]);
}
}

MouseMove

鼠标开始移动后,就要实时更新模型顶点的位置了。

         private void glCanvas1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
// move vertex
DragParam dragParam = this.dragParam;
if (dragParam != null)
{
var current = new Point(e.X, glCanvas1.Height - e.Y - );
Point differenceOnScreen = new Point(
current.X - dragParam.lastMousePositionOnScreen.X,
current.Y - dragParam.lastMousePositionOnScreen.Y);
dragParam.lastMousePositionOnScreen = current;
this.rendererDict[this.selectedModel].MovePositions(
differenceOnScreen,
dragParam.viewMatrix, dragParam.projectionMatrix,
dragParam.viewport,
dragParam.pickedGeometry.Indexes);
}
}
}

MouseUp

鼠标抬起,清空数据,恢复状态。

         private void glCanvas1_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
// move vertex
this.dragParam = null;
}
}

总结

本文虽然简单,但是我却花了好几天才解决拖拽的问题。过程中想过试过种种奇葩的方案。最后,在弄明白了project和unProject的功能后,立即想到了现在这个方案,既简单又实用。

所以说必须戒除浮躁和急切的心理,慢慢地搞清楚每一个小问题。这才是磨刀不误砍柴工。

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

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

CSharpGL(20)用unProject和Project实现鼠标拖拽图元的更多相关文章

  1. 【狼】unity 鼠标拖拽物体实现任意角度自旋转

    主要涉及函数 Input.GetAxis(“Mouse x”) 可取得鼠标横向(x轴)移动增量 Input.GetAxis(“Mouse y”) 可取得鼠标竖向(y轴)移动增量 通过勾股定理获取拖拽长 ...

  2. JavaScript鼠标拖拽特效及相关问题总结

    #div1{width:200px;height:200px;background:red;position:absolute;} #div2{width:200px;height:200px;bac ...

  3. 一款基于jQuery的支持鼠标拖拽滑动焦点图

    记得之前我们分享过一款jQuery全屏广告图片焦点图,图片切换效果还不错.今天我们要分享另外一款jQuery焦点图插件,它的特点是支持鼠标拖拽滑动,所以在移动设备上使用更加方便,你只要用手指滑动屏幕即 ...

  4. NGUI对象跟随鼠标拖拽移动

    public Camera WNGUICamera; Vector3 _WoldPosition;//指针的初始位置 // Vector3 _WoldAng; Vector3 WscreenSpace ...

  5. 鼠标拖拽定位和DOM各种尺寸详解

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

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

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

  7. html5的鼠标拖拽

    鼠标拖拽 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF- ...

  8. javascript鼠标拖拽的那些事情

    <html> <head> <title>javascript鼠标拖拽的那些事情</title> <meta http-equiv="C ...

  9. 设置zedgraph鼠标拖拽和局部放大属性(转帖)

    说一下几个属性的意义和具体应用: (1)鼠标拖拽显示区域 PanModifierKeys ->> Gets or sets a value that determines which mo ...

随机推荐

  1. .NET Core & ASP.NET Core 1.0在Redhat峰会上正式发布

    众所周知,Red Hat和微软正在努力使.NET Core成为Red Hat企业版Linux (RHEL)系统上的一流开发平台选项.这个团队已经一起工作好几个月了,RHEL对.NET有许多需求.今天在 ...

  2. Entity Framework 6 Recipes 2nd Edition 译 -> 目录 -持续更新

    因为看了<Entity Framework 6 Recipes 2nd Edition>这本书前面8章的翻译,感谢china_fucan. 从第九章开始,我是边看边译的,没有通读,加之英语 ...

  3. ExtJS 4.2 业务开发(三)数据添加和修改

    接上面的船舶管理业务,这里介绍添加和修改操作. 目录 1. 添加操作 2. 修改操作 3. 在线演示 1. 添加操作 1.1 创建AddShipWindow.js 在业务中的view目录下创建一个Ad ...

  4. OpenSceneGraph in ActiveX by ActiveQt

    OpenSceneGraph in ActiveX by ActiveQt eryar@163.com Abstract. Qt’s ActiveX and COM support allows Qt ...

  5. PHP之用户验证和标签推荐的简单使用

    本篇主要是讲解一些最简单的验证知识 效果图 bookmark_fns.php <?php require_once('output_fns.php'); require_once('db_fns ...

  6. 内存映射文件MemoryMappedFile使用

    参考资料: http://blog.csdn.net/bitfan/article/details/4438458 所谓内存映射文件,其实就是在内存中开辟出一块存放数据的专用区域,这区域往往与硬盘上特 ...

  7. Spring获取ApplicationContext

    在Spring+Struts+Hibernate中,有时需要使用到Spring上下文.项目启动时,会自动根据applicationContext配置文件初始化上下文,可以使用ApplicationCo ...

  8. Java中常用集合操作

    一.Map 名值对存储的. 常用派生类HashMap类 添加: put(key,value)往集合里添加数据 删除: clear()删除所有 remove(key)清除单个,根据k来找 获取: siz ...

  9. 【从零开始学BPM,Day3】自定义表单开发

    [课程主题] 主题:5天,一起从零开始学习BPM [课程形式] 1.为期5天的短任务学习 2.每天观看一个视频,视频学习时间自由安排. [第三天课程] 1.课程概要 Step 1 软件下载:H3 BP ...

  10. Java 进阶 hello world! - 中级程序员之路

    Java 进阶 hello world! - 中级程序员之路 Java是一种跨平台的语言,号称:"一次编写,到处运行",在世界编程语言排行榜中稳居第二名(TIOBE index). ...