如何将矩形投影到网格/地形对象上,用作选择选取框?(在三个.js 中) [英] How to project a rectangle onto a Mesh/Terrain object, for use as a select marquee? (in three.js)

查看:16
本文介绍了如何将矩形投影到网格/地形对象上,用作选择选取框?(在三个.js 中)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个使用

然而,我的目标更像是这样;

我想知道我是否错过了使用 Core Three.js 功能执行此操作的一些明显方法.

总是有一种蛮力方法,即围绕矩形的周长间隔地投射光线,并创建一系列线段来近似投影的矩形,但我想知道是否有原生方法.

(我这周才刚开始看three.js,所以我可能错过了一些明显的东西......虽然我花了最后一天的时间进行实验,但运气不佳)

更新

根据@prisoner849 的建议,我

解决方案

在我发表评论后,我的想法和 Don McCurdy 一模一样(干杯,Don :)).在 https://www.shadertoy.com 上快速搜索给了我那个着色器 https://www.shadertoy.com/view/XlsBRB(看看 FabriceNeyret2 的精彩评论).所以我只是针对这个非常粗略的概念调整了片段着色器.

var scene = new THREE.Scene();var camera = new THREE.PerspectiveCamera(60, window.innerWidth/window.innerHeight, 1, 1000);相机位置设置(0、5、10);var renderer = new THREE.WebGLRenderer({抗锯齿:真});renderer.setSize(window.innerWidth, window.innerHeight);document.body.appendChild(renderer.domElement);var controls = new THREE.OrbitControls(camera, renderer.domElement);var geom = new THREE.PlaneGeometry(20, 20, 10, 10);geom.vertices.forEach(v => {v.z = THREE.Math.randFloat(-1, 1);});geom.rotateX(-Math.PI * .5);geom.computeFaceNormals();geom.computeVertexNormals();var 制服 = {中央: {价值:新的THREE.Vector3()},尺寸: {值:新三.Vector2(1, 1)},线半宽:{值:0.1}}var matShader = new THREE.ShaderMaterial({制服:制服,顶点着色器:vertShader,片段着色器:fragShader});var matWire = new THREE.MeshBasicMaterial({颜色:灰色",线框:真实});var obj = THREE.SceneUtils.createMultiMaterialObject(geom, [matShader, matWire]);场景添加(对象);var gui = new dat.GUI();gui.add(uniforms.size.value, "x", .5, 5.0).name("size.x");gui.add(uniforms.size.value, "y", .5, 5.0).name("size.y");gui.add(uniforms.lineHalfWidth, "value", .05, 2.0).name("line half-width");var raycaster = new THREE.Raycaster();var mouse = new THREE.Vector2();var 相交 = [];var point = new THREE.Vector3();window.addEventListener("mousemove", function(event) {mouse.x = (event.clientX/window.innerWidth) * 2 - 1;mouse.y = -(event.clientY/window.innerHeight) * 2 + 1;raycaster.setFromCamera(鼠标,相机);相交 = raycaster.intersectObject(obj, true);if (intersects.length === 0) 返回;obj.worldToLocal(point.copy(intersects[0].point));制服.center.value.copy(point);}, 错误的);使成为();函数渲染(){请求动画帧(渲染);renderer.render(场景,相机);}

body {溢出:隐藏;边距:0;}

<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/94/three.min.js"></script><script src="https://cdn.rawgit.com/mrdoob/three.js/0949e59f/examples/js/controls/OrbitControls.js"></script><script src="https://cdn.rawgit.com/mrdoob/three.js/0949e59f/examples/js/utils/SceneUtils.js"></script><script src="https://cdn.rawgit.com/mrdoob/three.js/0949e59f/examples/js/libs/dat.gui.min.js"></script><脚本>var vertShader = `不同的 vec2 vPos;无效主(){vPos = 位置.xz;gl_Position = 投影矩阵 *模型视图矩阵 *vec4(位置,1.0);}`;var fragShader = `统一的 vec3 中心;统一的 vec2 大小;统一浮动线半宽;不同的 vec2 vPos;无效主(){vec2 Ro = 尺寸 * .5;vec2 Uo = abs( vPos - center.xz ) - Ro;vec3 c = mix(vec3(1.), vec3(1.,0.,0.), float(abs(max(Uo.x,Uo.y)) < lineHalfWidth));gl_FragColor = vec4(c, 1.);}`;</script>

I have a terrain which was generated using the THREE.Terrain library. I'd like to be able to click and drag out a marquee and select objects that are on the surface of the Terrain Mesh.

Currently I am detecting the start and end of the drag, and drawing out a rectangle in the global XZ plane, but I'd prefer it to be flush with the surface.

Currently it looks like this;

However what I am aiming for is something more like this;

I am wondering if I have missed some obvious way of doing that with the Core three.js features.

There is always the brute force method of casting rays at intervals around the perimeter of the rectangle, and creating a series of line segments to approximate the projected rectangle, but I was wondering if there was a native method.

(I only just started looking at three.js this week, so I might have missed something obvious... though I've spent the last day experimenting, and haven't had much luck)

Update

Based on @prisoner849's suggestion, I mashed up his code with the Terrain demo and that seems to be working pretty well.

    varying vec2 vPos;

    void main() {
      vec2 Ro = size * .5;
      vec2 Uo = abs( vPos - center.xz ) - Ro;

      vec3 c = mix(vec3(1.), vec3(1.,0.,0.), float(enabled && (abs(max(Uo.x,Uo.y)) < lineHalfWidth)  ));

      gl_FragColor = vec4(c, float(enabled && (abs(max(Uo.x,Uo.y)) < lineHalfWidth)  ));
    }

  `;

The code needs a massive clean up, and the marquees need to be rotated to match the camera perspective, and it would be nice to have ctrl-click to add to selection set, etc, etc.

But in principle the fragment shader worked well...

解决方案

Right after I posted my comment, I had a thought exactly like Don McCurdy (cheers, Don :) ). Quick search on https://www.shadertoy.com gave me that shader https://www.shadertoy.com/view/XlsBRB (look at the awesome comments from FabriceNeyret2 there). So I just adapted that fragment shader for this very rough concept.

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 5, 10);
var renderer = new THREE.WebGLRenderer({
  antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

var controls = new THREE.OrbitControls(camera, renderer.domElement);

var geom = new THREE.PlaneGeometry(20, 20, 10, 10);
geom.vertices.forEach(v => {
  v.z = THREE.Math.randFloat(-1, 1);
});
geom.rotateX(-Math.PI * .5);
geom.computeFaceNormals();
geom.computeVertexNormals();

var uniforms = {
  center: {
    value: new THREE.Vector3()
  },
  size: {
    value: new THREE.Vector2(1, 1)
  },
  lineHalfWidth: {
    value: 0.1
  }
}

var matShader = new THREE.ShaderMaterial({
  uniforms: uniforms,
  vertexShader: vertShader,
  fragmentShader: fragShader
});

var matWire = new THREE.MeshBasicMaterial({
  color: "gray",
  wireframe: true
});

var obj = THREE.SceneUtils.createMultiMaterialObject(geom, [matShader, matWire]);

scene.add(obj);

var gui = new dat.GUI();
gui.add(uniforms.size.value, "x", .5, 5.0).name("size.x");
gui.add(uniforms.size.value, "y", .5, 5.0).name("size.y");
gui.add(uniforms.lineHalfWidth, "value", .05, 2.0).name("line half-width");

var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var intersects = [];
var point = new THREE.Vector3();

window.addEventListener("mousemove", function(event) {
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
  raycaster.setFromCamera(mouse, camera);
  intersects = raycaster.intersectObject(obj, true);
  if (intersects.length === 0) return;
  obj.worldToLocal(point.copy(intersects[0].point));
  uniforms.center.value.copy(point);

}, false);


render();

function render() {
  requestAnimationFrame(render);
  renderer.render(scene, camera);
}

body {
  overflow: hidden;
  margin: 0;
}

<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/94/three.min.js"></script>
<script src="https://cdn.rawgit.com/mrdoob/three.js/0949e59f/examples/js/controls/OrbitControls.js"></script>
<script src="https://cdn.rawgit.com/mrdoob/three.js/0949e59f/examples/js/utils/SceneUtils.js"></script>
<script src="https://cdn.rawgit.com/mrdoob/three.js/0949e59f/examples/js/libs/dat.gui.min.js"></script>
<script>
  var vertShader = `
    varying vec2 vPos;
    void main() {
      vPos = position.xz;
      gl_Position = projectionMatrix *
                    modelViewMatrix *
                    vec4(position,1.0);
    }
  `;

  var fragShader = `
    uniform vec3 center;
    uniform vec2 size;
    uniform float lineHalfWidth;
    
    varying vec2 vPos;
    
    void main() {
      vec2 Ro = size * .5;
      vec2 Uo = abs( vPos - center.xz ) - Ro;
      
      vec3 c = mix(vec3(1.), vec3(1.,0.,0.), float(abs(max(Uo.x,Uo.y)) < lineHalfWidth));
      
      gl_FragColor = vec4(c, 1.  );
    }
    
  `;
</script>

这篇关于如何将矩形投影到网格/地形对象上,用作选择选取框?(在三个.js 中)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆