【问题标题】:Threejs - How to pick all objects in area?Threejs - 如何选择区域内的所有对象?
【发布时间】:2013-12-08 19:08:19
【问题描述】:

我正在使用 Three.js,我想知道如何获取给定区域中的所有对象?

例如,获取在绿色方块中找到的所有对象:

解决方案:

        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;
        }

线engine.getObjectsByGroup(inGroup) : engine.getRegisteredEntities(); 只返回一个实体数组,然后我遍历这些实体:

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

只有具有“threeRenderable”属性(对象)的实体是可见的,因此,我忽略那些没有它的实体:

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

然后我将实体的克隆网格与 PickingGeometry 合并:

THREE.GeometryUtils.merge( pickingGeometry, cloneMesh);

最后,我将 PickingGeometry 添加到 PickingScene:

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

然后我读取选定区域的颜色,并返回一个 ID 数组。

您可以查看我当时写的Node.js game engine

【问题讨论】:

  • 你现在使用什么样的选择?请参阅 GPU 拾取示例。您可以使用颜色 ID 渲染通道并使用 readPixels 读取给定区域中的所有颜色信息,从而返回该区域中的所有对象颜色 ID。这将为您提供所需的信息。不确定光线投射器的其他可能性......顺便说一句:你在做什么看起来很酷;)
  • 我不确定 GPU 拾取对这里有什么帮助。在该示例中,每个立方体都以不同的颜色呈现 - 这样,使用 readPixel (返回给定区域中的所有像素/颜色),您可以“知道”用户单击了哪个立方体。而我需要的是返回给定区域中的对象列表。
  • 是的,正如您所说,readPixels 返回给定区域中的所有像素/颜色。给定区域是您的矩形选择。颜色是您的 objectID。使用 readPixels 输出,您可以评估颜色并返回此给定区域中的对象列表,从而准确产生您想要实现的目标?您只需要将所有对象 ID 渲染到帧缓冲区/纹理中。
  • @GuyGood , 1. 如何将我的所有对象渲染到 frameBuffer + 将它们的颜色设置为 ID? 2. 我尝试这样做,但得到了很多结果:var gl = canvas.getContext("webgl", {preserveDrawingBuffer: true}); var pixelBuffer = new Uint8Array( width * height ); // here 100 gl.readPixels( posX, posY, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixelBuffer ); 现在返回了一个包含很多值(如宽度*高度)的数组,并且并非所有结果都是唯一的。请指教。
  • 好吧,请参阅 gpu 示例,了解如何设置 colorID。同样为了更好的视觉效果,使用 gpu-example 并从第一个 render-call 中删除 rendertarget 并取消注释一些内容,这样您就可以看到带有 colorIDs 的图片的样子。如果你从 readPixels 得到一个宽度*高度的数组,这正是你想要的。 (100 次调用?1 应该足以得到这个数组)当然不是所有的值都是唯一的。区域中具有绿色 colorID 的 1 个对象将在该区域中具有 x 个像素。但是你只需要关心第一个外观,因为那时你知道obj在里面。

标签: three.js


【解决方案1】:

我想实现这样的东西,我选择了一种非常不同的方法 - 可能更糟糕,我真的不知道 - 但更容易做 IMO,所以我把它放在这里以防有人需要它。

基本上,我只使用了 2 次光线投射来了解选择矩形的第一个和最后一个点,投影在我的地平面上,然后遍历我的对象以了解哪些对象在其中。

一些非常基本的代码:

   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)
        */
      }
    }

这是我的选择功能:

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;
}

一些警告:据我所知,这种技术仅在您不关心 Y 位置并且您的选择是基本矩形时才可用。

我的 2c

【讨论】:

  • 不知selectCharactersInZone(begins, ends)的实现;您是否在迭代所有对象,并仅返回在所选区域中找到的对象?
  • 我使用这个函数来查找所选区域中的对象,所以是的,我正在迭代所有 selectectable 对象——在我的例子中是几个字符——所以它很快足够的。在我看来,迭代大量对象并使用足够简单的“查找”测试取决于您。我已经用 selectCharactersInZone(begins, ends) 函数编辑了答案。
猜你喜欢
  • 1970-01-01
  • 2023-03-26
  • 1970-01-01
  • 2015-04-24
  • 2021-10-21
  • 2019-09-02
  • 1970-01-01
  • 2012-08-23
  • 1970-01-01
相关资源
最近更新 更多