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


    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把模型坐标系上的点转换为窗口坐标系上的点。这可以通过其实现代码来验证。

     1         /// <summary>
     2         /// Map the specified object coordinates (obj.x, obj.y, obj.z) into window coordinates.
     3         /// </summary>
     4         /// <param name="obj">The object.</param>
     5         /// <param name="model">The model.</param>
     6         /// <param name="proj">The proj.</param>
     7         /// <param name="viewport">The viewport.</param>
     8         /// <returns></returns>
     9         public static vec3 project(vec3 obj, mat4 model, mat4 proj, vec4 viewport)
    10         {
    11             vec4 tmp = new vec4(obj, (1f));
    12             tmp = model * tmp;
    13             tmp = proj * tmp;
    14 
    15             tmp /= tmp.w;
    16             tmp = tmp * 0.5f + new vec4(0.5f, 0.5f, 0.5f, 0.5f);
    17             tmp[0] = tmp[0] * viewport[2] + viewport[0];
    18             tmp[1] = tmp[1] * viewport[3] + viewport[1];
    19 
    20             return new vec3(tmp.x, tmp.y, tmp.z);
    21         }

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

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

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

    拖拽原理

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

     1         /// <summary>
     2         /// 根据<paramref name="differenceOnScreen"/>来修改指定索引处的顶点位置。
     3         /// </summary>
     4         /// <param name="differenceOnScreen"></param>
     5         /// <param name="viewMatrix"></param>
     6         /// <param name="projectionMatrix"></param>
     7         /// <param name="viewport"></param>
     8         /// <param name="positionIndexes"></param>
     9         public void MovePositions(Point differenceOnScreen,
    10             mat4 viewMatrix, mat4 projectionMatrix, vec4 viewport, uint[] positionIndexes)
    11         {
    12             if (positionIndexes == null) { return; }
    13             if (positionIndexes.Length == 0) { return; }
    14 
    15             GL.BindBuffer(BufferTarget.ArrayBuffer, this.positionBufferPtr.BufferId);
    16             IntPtr pointer = GL.MapBuffer(BufferTarget.ArrayBuffer, MapBufferAccess.ReadWrite);
    17             unsafe
    18             {
    19                 var array = (vec3*)pointer.ToPointer();
    20                 for (int i = 0; i < positionIndexes.Length; i++)
    21                 {
    22                     vec3 projected = glm.project(array[positionIndexes[i]],
    23                         viewMatrix, projectionMatrix, viewport);
    24                     vec3 newProjected = new vec3(projected.x + differenceOnScreen.X,
    25                         projected.y + differenceOnScreen.Y, projected.z);
    26                     array[positionIndexes[i]]=glm.unProject(newProjected,
    27                         viewMatrix, projectionMatrix, viewport);
    28                 }
    29             }
    30             GL.UnmapBuffer(BufferTarget.ArrayBuffer);
    31             GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
    32         }

    MouseDown

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

     1         private void glCanvas1_MouseDown(object sender, MouseEventArgs e)
     2         {
     3             if (e.Button == System.Windows.Forms.MouseButtons.Left)
     4             {
     5                 // move vertex
     6                 PickedGeometry pickedGeometry = RunPicking(e.X, e.Y);
     7                 if (pickedGeometry != null)
     8                 {
     9                     var dragParam = new DragParam(pickedGeometry,
    10                         camera.GetProjectionMat4(),
    11                         camera.GetViewMat4(),
    12                         new Point(e.X, glCanvas1.Height - e.Y - 1));
    13                     this.dragParam = dragParam;
    14                 }
    15             }
    16         }

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

     1         private PickedGeometry RunPicking(int x, int y)
     2         {
     3             this.glCanvas1_OpenGLDraw(selectedModel, null);
     4             IColorCodedPicking pickable = this.rendererDict[this.SelectedModel];
     5             pickable.MVP = this.camera.GetProjectionMat4() * this.camera.GetViewMat4();
     6             PickedGeometry pickedGeometry = ColorCodedPicking.Pick(
     7                 this.camera, x, y, this.glCanvas1.Width, this.glCanvas1.Height, pickable);
     8 
     9             return pickedGeometry;
    10         }

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

     1     class DragParam
     2     {
     3 
     4         public PickedGeometry pickedGeometry;
     5         public mat4 projectionMatrix;
     6         public mat4 viewMatrix;
     7         public Point lastMousePositionOnScreen;
     8         public vec4 viewport;
     9 
    10         public DragParam(PickedGeometry pickedGeometry, mat4 projectionMatrix, mat4 viewMatrix, Point lastMousePositionOnScreen)
    11         {
    12             this.pickedGeometry = pickedGeometry;
    13             this.projectionMatrix = projectionMatrix;
    14             this.viewMatrix = viewMatrix;
    15             this.lastMousePositionOnScreen = lastMousePositionOnScreen;
    16             var viewport = new int[4]; GL.GetInteger(GetTarget.Viewport, viewport);
    17             this.viewport = new vec4(viewport[0], viewport[1], viewport[2], viewport[3]);
    18         }
    19     }

    MouseMove

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

     1         private void glCanvas1_MouseMove(object sender, MouseEventArgs e)
     2         {
     3             if (e.Button == System.Windows.Forms.MouseButtons.Left)
     4             {
     5                 // move vertex
     6                 DragParam dragParam = this.dragParam;
     7                 if (dragParam != null)
     8                 {
     9                     var current = new Point(e.X, glCanvas1.Height - e.Y - 1);
    10                     Point differenceOnScreen = new Point(
    11                         current.X - dragParam.lastMousePositionOnScreen.X,
    12                         current.Y - dragParam.lastMousePositionOnScreen.Y);
    13                     dragParam.lastMousePositionOnScreen = current;
    14                     this.rendererDict[this.selectedModel].MovePositions(
    15                         differenceOnScreen,
    16                         dragParam.viewMatrix, dragParam.projectionMatrix,
    17                         dragParam.viewport,
    18                         dragParam.pickedGeometry.Indexes);
    19                 }
    20             }
    21         }

    MouseUp

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

    1         private void glCanvas1_MouseUp(object sender, MouseEventArgs e)
    2         {
    3             if (e.Button == System.Windows.Forms.MouseButtons.Left)
    4             {
    5                 // move vertex
    6                 this.dragParam = null;
    7             }
    8         }

    总结

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

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

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

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

  • 相关阅读:
    两分钟看完一道投机取巧的算法题
    浅谈什么是递归算法
    浅谈什么是图拓扑排序
    what ?1 + 2 + 3 + ⋯ + ∞ = 1/12 ?
    浅谈什么是分治算法
    有点难度,几道和「滑动窗口」有关的算法面试题
    LeetCode 第 2 号问题:两数相加
    面试官,我会写二分查找法!对,没有 bug 的那种!
    基础复习——通过SQLite优化记住密码功能
    基础复习——内容共享——通过ContentProvider封装数据——通过ContentResolver访问数据
  • 原文地址:https://www.cnblogs.com/bitzhuwei/p/CSharpGL-20-drag-primitives-in-VBO-by-mouse-using-project-and-unproject.html
Copyright © 2020-2023  润新知