Threejs - How to pick all objects in area?

雨燕双飞 提交于 2019-12-20 10:58:07

问题


I'm using Three.js and I wonder how to get all objects in a given area?

For example, get all objects that found in the green-square:

Solution:

        getEntitiesInSelection: function(x, z, width, height, inGroup) {
            var self = this,
                entitiesMap = [],
                color = 0,
                colors = [],
                ids = [],
                pickingGeometry = new THREE.Geometry(),
                pickingMaterial = new THREE.MeshBasicMaterial( { vertexColors: THREE.VertexColors } ),
                pickingScene = new THREE.Scene(),
                pickingTexture = new THREE.WebGLRenderTarget( this._renderer.domElement.width, this._renderer.domElement.height),
                cloneMesh,
                entities = inGroup ?
                    engine.getObjectsByGroup(inGroup) : engine.getRegisteredEntities();

            pickingTexture.generateMipmaps = false;

            //Go over each entity, change its color into its ID
            _.forEach(entities, function(entity) {
                if(undefined == entity.threeRenderable) {
                    return ;
                }

                //Clone entity
                cloneMesh = entity.threeRenderable.mesh().clone();
                cloneMesh.material = entity.threeRenderable.mesh().material.clone();
                cloneMesh.material.map = null;
                cloneMesh.material.vertexColors = THREE.VertexColors;
                cloneMesh.geometry = entity.threeRenderable.mesh().geometry.clone();
                cloneMesh.position.copy( entity.threeRenderable.mesh().position );
                cloneMesh.rotation.copy( entity.threeRenderable.mesh().rotation );
                cloneMesh.scale.copy( entity.threeRenderable.mesh().scale );

                //Cancel shadow
                cloneMesh.castShadow = false;
                cloneMesh.receiveShadow  = false;

                //Set color as entity ID
                entitiesMap[color] = entity.id();
                self._applyVertexColors(cloneMesh.geometry, new THREE.Color( color ) );
                color++;

                THREE.GeometryUtils.merge( pickingGeometry,  cloneMesh);
            });

            pickingScene.add( new THREE.Mesh( pickingGeometry, pickingMaterial ) );

            //render the picking scene off-screen
            this._renderer.render(pickingScene, this._objs[this._mainCamera], pickingTexture );
            var gl = this._renderer.getContext();

            //read the pixel under the mouse from the texture
            var pixelBuffer = new Uint8Array( 4 * width * height );
            gl.readPixels( x, this._renderer.domElement.height - z, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixelBuffer );

            //Convert RGB in the selected area back to color
            for(var i=0; i<pixelBuffer.length; i+=4) {
                if( 0 == pixelBuffer[i] && 0 == pixelBuffer[i+1] && 0 == pixelBuffer[i+2] && 0 == pixelBuffer[i+3] ) {
                    continue;
                }

                color = ( pixelBuffer[i] << 16 ) | ( pixelBuffer[i+1] << 8 ) | ( pixelBuffer[i+2] );
                colors.push(color);
            }
            colors = _.unique(colors);

            //Convert colors to ids
            _.forEach(colors, function(color) {
                ids.push(entitiesMap[color]);
            });

            return ids;
        }

The line engine.getObjectsByGroup(inGroup) : engine.getRegisteredEntities(); just return an array of entities, which in turn, I iterate over the entities:

 _.forEach(entities, function(entity) { ...

Only entities that have the 'threeRenderable' property (object) are visible, therefore, I ignore those that doesn't have it:

if(undefined == entity.threeRenderable) {
      return ;
}

then I merge the entity's cloned mesh with with the pickingGeometry:

THREE.GeometryUtils.merge( pickingGeometry, cloneMesh);

eventually, I add the pickingGeometry to the pickingScene:

 pickingScene.add( new THREE.Mesh( pickingGeometry, pickingMaterial ) );

Then I read the colors of the selected area, and return an array of IDs.

You can checkout the Node.js game engine I wrote back then.


回答1:


I've wanted to implement something like this and I choose a very different method - maybe much worse, I don't really know - but much easier to do IMO, so I put it here in case someone wants it.

Basically, I used only 2 raycasts to know the first and last points of the selection rectangle, projected on my ground plane, and iterate over my objects to know which ones are in.

Some very basic code:

   function onDocumentMouseDown(event) {
      // usual Raycaster stuff ...

      // get the ground intersection
      var intersects = raycaster.intersectObject(ground);

      GlobalGroundSelection = {
        screen: { x: event.clientX, y: event.clientY },
        ground: intersects[0].point
      };
    }

   function onDocumentMouseUp(event) {
      // ends a ground selection
      if (GlobalGroundSelection) {
        // usual Raycaster stuff ...

        // get the ground intersection
        var intersects = raycaster.intersectObjects(ground);

        var selection = {
          begins: GlobalGroundSelection.ground,
          ends: intersects[0].point
        };

        GlobalGroundSelection = null;
        selectCharactersInZone(selection.begins, selection.ends);
      }
    }

    function onDocumentMouseMove(event) {

      if (GlobalGroundSelection) {
        // in a selection, draw a rectangle
        var p1 = GlobalGroundSelection.screen,
            p2 = { x: event.clientX, y: event.clientY };

        /* with these coordinates
          left: p1.x > p2.x ? p2.x : p1.x,
          top: p1.y > p2.y ? p2.y : p1.y,
          width: Math.abs(p1.x - p2.x),
          height: Math.abs(p1.y - p2.y)
        */
      }
    }

Here is my select function:

function selectCharactersInZone (start, end) {

  var selected = _.filter( SELECTABLE_OBJECTS , function(object) {
    // warning: this ignore the Y elevation value
    var itsin = object.position.x > start.x
            && object.position.z > start.z 
            && object.position.x < end.x
            && object.position.z < end.z;

    return itsin;
  });

  return selected;
}

Some warnings: as far as I know, this technique is only usable when you don't care about Y positions AND your selection is a basic rectangle.

My 2c



来源:https://stackoverflow.com/questions/20169665/threejs-how-to-pick-all-objects-in-area

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!