经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 软件/图像 » WebGL » 查看文章
WebGL模型拾取——射线法二
来源:cnblogs  作者:ccentry  时间:2018/11/19 9:09:30  对本文有异议

  这篇文章是对射线法raycaster的补充,上一篇文章主要讲的是raycaster射线法拾取模型的原理,而这篇文章着重讲使用射线法要注意的地方。首先我们来看下图。

  我来解释一下上图中的originTriangle,这就是Triangle2三角形第一次绘制在空间中的位置,而Triangle2当前的位置是经过一系列空间变换而来的(这些位置姿态变换大多是由用户鼠标交互产生),变换矩阵就是transformMatrix。这下就引出了本文第一个重点,那就是做raycaster的时候要保证线段碰撞模型的时候一定是模型当前所处的空间位置,即已经做过transformMatrix空间(姿态,位置)变换,否则线段如果和模型之前的初始化位置求交显然没有交点,就拾取失败了。这就是做raycaster要注意的第一个重点,即射线一定要和空间变换后的模型求交。

  接下来我们再看一张图,请看下图。

  我们看到,射线和模型有2个交点,P2_0和P2_1,分别交四面体的前面于P2_0和交四面体的后面于P2_1。这就是我们要着重关注的本文第二个重点,即raycaster射线拾取模型过程中射线与单个模型有多个交点的问题。处理这个问题其实有很多办法,这里我们采用最简单的方式,就是距离相机(人眼)位置近者胜出的策略。计算交点的算法上一篇文章已经提到,这里不再赘述,但要说明的是,我们计算的每一个面在数据结构中都有自身模型父节点geometry,如果像上图一个四面体的geometry和射线产生了多个面相交,那我们就认为鼠标选中的是该模型geometry离相机(camera)(人眼)最近的交面上的交点。

  对于上面2点的叙述,配合部分代码展示,是geometry空间变换的,代码如下。

  1. Object.assign(CubeSection.prototype, {
  2. //重载,每一帧同步数据
  3. sync: function () {
  4. if(this._mode === "face") {//根据剖切模式管理鼠标拖拽逻辑
  5. if (this._selectFace) {
  6. let camera = this._viewer.getMainCamera();
  7. let last = this._mousePoints.getLast();
  8. let lastX = camera.getNormalizedX(last[0]);
  9. let lastY = camera.getNormalizedY(last[1]);
  10. let current = this._mousePoints.getCurrent();
  11. let currentX = camera.getNormalizedX(current[0]);
  12. let currentY = camera.getNormalizedY(current[1]);
  13. this._mousePoints.sync();
  14. let deltaX = currentX - lastX;
  15. let deltaY = currentY - lastY;
  16. if (Math.abs(deltaX) < Algorithm.EPSILON && Math.abs(deltaY) < Algorithm.EPSILON)
  17. return;
  18. //如果面被选中,并且有移动量,需要进行剖切面移动处理
  19. let start = Vec3.MemoryPool.alloc();
  20. let end = Vec3.MemoryPool.alloc();
  21. camera.computeScreenToWorldNearFar(lastX, lastY, start, end, true);
  22. //获取起点与平面的交点
  23. let plane = this._cubeClip.getClipPlane(this._selectFace.getName());
  24. let planePt1 = Vec3.MemoryPool.alloc();
  25. if (Plane.intersectLine(planePt1, start, end, plane)) {
  26. //将模型交点再转换到屏幕坐标上,主要为了获取z值给终点
  27. let temppt = Vec3.MemoryPool.alloc();
  28. camera.computeWorldToScreen(planePt1, temppt);
  29. Vec3.set(temppt, currentX, currentY, temppt[2]);
  30. let planePt2 = Vec3.MemoryPool.alloc();
  31. camera.computeScreenToWorld(temppt, planePt2);
  32. Vec3.sub(temppt, planePt2, planePt1);
  33. let dist = Vec3.dot(plane, temppt);
  34. this.move(dist);
  35. Vec3.MemoryPool.free(planePt2);
  36. Vec3.MemoryPool.free(temppt);
  37. }
  38. Vec3.MemoryPool.free(planePt1);
  39. Vec3.MemoryPool.free(start);
  40. Vec3.MemoryPool.free(end);
  41. }
  42. } else if(this._mode === "translate"){
  43. if(this._selectAxis) {
  44. let camera = this._viewer.getMainCamera();
  45. let last = this._mousePoints.getLast();//前一帧鼠标的XY坐标
  46. let lastX = camera.getNormalizedX(last[0]);
  47. let lastY = camera.getNormalizedY(last[1]);
  48. let current = this._mousePoints.getCurrent();//目前帧鼠标的XY坐标
  49. let currentX = camera.getNormalizedX(current[0]);
  50. let currentY = camera.getNormalizedY(current[1]);
  51. this._mousePoints.sync();//继续下一帧同步鼠标XY坐标
  52. let deltaX = currentX - lastX;//X偏移量
  53. let deltaY = currentY - lastY;//Y偏移量
  54. if (Math.abs(deltaX) < Algorithm.EPSILON && Math.abs(deltaY) < Algorithm.EPSILON) {
  55. //如果XY偏移量都为零,就直接返回,什么操作都不做
  56. return;
  57. }
  58. //坐标系轴被选中,并且有偏移量,就要移动整个包围盒子
  59. let start = Vec3.MemoryPool.alloc();
  60. let end = Vec3.MemoryPool.alloc();
  61. //把屏幕上的XY坐标换算到视棱台near,far截面上的XY坐标
  62. camera.computeScreenToWorldNearFar(lastX, lastY, start, end, true);
  63. //当前pick的坐标轴
  64. let axis = this._selectAxis;
  65. //near-far线段截axis坐标轴的交点
  66. let intersectPoint1 = Vec3.MemoryPool.alloc();
  67. //射线碰撞
  68. let intersections = this._drawActor.linesegmentIntersect(start, end);//对场景中的所有物体进行线段碰撞检测
  69. //遍历intersections列表,按照离相机从远到近排列
  70. for (let i = 0; i < intersections.length; i++) {
  71. let geometry = intersections[i].getDrawable().getGeometry();
  72. if (geometry && new String(geometry._name).substring(0, 4) === "axis") {
  73. intersectPoint1 = intersections[i]._point;//获取到near-far线段和坐标轴的交点
  74. break;
  75. }
  76. }
  77. //将near-far和坐标轴的交点再转换到屏幕坐标上,主要为了获取z值给终点
  78. let screenPoint = Vec3.MemoryPool.alloc();
  79. camera.computeWorldToScreen(intersectPoint1, screenPoint);
  80. //screePoint(currentX, currentY, screenPoint.z)
  81. Vec3.set(screenPoint, currentX, currentY, screenPoint[2]);
  82. //鼠标移动的第二个场景坐标系里的点坐标
  83. let intersectPoint2 = Vec3.MemoryPool.alloc();
  84. //把屏幕归一化坐标转化为场景世界坐标
  85. camera.computeScreenToWorld(screenPoint, intersectPoint2);
  86. Vec3.sub(screenPoint, intersectPoint2, intersectPoint1);
  87. let dist = 0;
  88. if(this._selectAxis._name === "axisX"){
  89. dist = screenPoint[0];
  90. }else if(this._selectAxis._name === "axisY"){
  91. dist = screenPoint[1];
  92. }else if(this._selectAxis._name === "axisZ"){
  93. dist = screenPoint[2];
  94. }
  95. this.move(dist);
  96. //析构向量
  97. Vec3.MemoryPool.free(intersectPoint1);
  98. Vec3.MemoryPool.free(intersectPoint2);
  99. Vec3.MemoryPool.free(screenPoint);
  100. Vec3.MemoryPool.free(start);
  101. Vec3.MemoryPool.free(end);
  102. }
  103. } else if(this._mode === "rotate"){
  104. } else if(this._mode === "scale"){
  105. }
  106. },
  1. updateTransform: function () {
  2. let mat = this._cubeRoot.getMatrix();
  3. //重新计算坐标系模型的_matrix
  4. this._coordinateSection.update(this._clipBox, this._scale, this._translate, this._rotate, this._scaleMatrix, this._translateMatrix, this._rotateMatrix, mat);
  5. Mat4.fromScaling(this._scaleMatrix, this._scale);
  6. Mat4.fromTranslation(this._translateMatrix, this._translate);
  7. Mat4.fromQuat(this._rotateMatrix, this._rotate);
  8. Mat4.mul(mat, this._translateMatrix, this._rotateMatrix);
  9. Mat4.mul(mat, mat, this._scaleMatrix);
  10. //剖切面数据的变换
  11. this._cubeClip.resetClipPlane();
  12. this._cubeClip.transformClipPlane(mat);
  13. //包围盒子更新
  14. this._clipBox.setMaxValue(0.5, 0.5, 0.5);
  15. this._clipBox.setMinValue(-0.5, -0.5, -0.5);
  16. this._clipBox.transformMat4(mat);
  17. },

接下来是选取离相机近的交点,代码如下

  1. //拾取物体,根据当前剖切模式选择intersections列表中的碰撞对象
  2. pick: function (x, y) {
  3. let camera = this._viewer.getMainCamera();
  4. let start = Vec3.MemoryPool.alloc();
  5. let end = Vec3.MemoryPool.alloc();
  6. camera.computeScreenToWorldNearFar(x, y, start, end);
  7. let intersections = this._drawActor.linesegmentIntersect(start, end);
  8. let l = intersections.length;
  9. if (l !== 0) {
  10. switch(this._mode){
  11. case "face" : {//面剖切
  12. let intersection = intersections[0];//LineSegmentIntersection
  13. let geometry = intersection.getDrawable().getGeometry();
  14. if (geometry) {
  15. this._selectFace = geometry;
  16. this._selectFace.setStateSet(this._selectState);
  17. return true;
  18. }
  19. }
  20. case "translate" : {//平移剖切
  21. //遍历intersections列表,按照离相机从远到近排列
  22. for(var i=0; i<l; i++){
  23. let geometry = intersections[i].getDrawable().getGeometry();
  24. if(geometry && new String(geometry._name).substring(0, 4) === "axis"){
  25. this._selectAxis = geometry;
  26. this._selectAxis.setStateSet(this._selectStateAxis);
  27. break;
  28. }
  29. }
  30. return true;
  31. }
  32. case "rotate" : {//旋转剖切
  33. //遍历intersections列表,按照离相机从远到近排列
  34. for(var i=0; i<l; i++){
  35. let geometry = intersections[i].getDrawable().getGeometry();
  36. if(geometry && new String(geometry._name).substring(0, 4) === "face"){
  37. this._selectAxisFace = geometry;
  38. this._selectAxisFace.setStateSet(this._selectStateAxisFace);
  39. break;
  40. }
  41. }
  42. return true;
  43. }
  44. case "scale" : {//缩放剖切
  45. //遍历intersections列表,按照离相机从远到近排列
  46. for(var i=0; i<l; i++){
  47. let geometry = intersections[i].getDrawable().getGeometry();
  48. if(geometry && new String(geometry._name).substring(0, 4) === "axis"){
  49. this._selectAxis = geometry;
  50. this._selectAxis.setStateSet(this._selectStateAxis);
  51. break;
  52. }
  53. }
  54. return true;
  55. }
  56. }
  57. }
  58. return false;
  59. },

其中intersections[]交点列表是按照离相机由远到近距离排序的,intersection[i]交点离相机距离比intersection[i+1]交点离相机距离要近。这就是我们采取的离相机近交点胜出原则。

  好了,以上就是raycaster射线拾取模型要注意的地方,如有错误,希望读者斧正,欢迎诸位同学留言。如需转载本文,请注明出处:https://www.cnblogs.com/ccentry/p/9977490.html                       

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号