Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to project a rectangle onto a Mesh/Terrain object, for use as a select marquee? (in three.js)

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;

enter image description here

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

enter image description here

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...

enter image description here

like image 566
Tom Avatar asked Jan 18 '18 15:01

Tom


1 Answers

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>
like image 135
prisoner849 Avatar answered Oct 24 '22 06:10

prisoner849