Search code examples
javascriptthree.jsvisibilitypoint

Hiding point by index in three.js THREE.Points object


I have got the following issue with an object containing many points (created with THREE.Points). An example would be https://threejs.org/examples/?q=points#webgl_interactive_points. Now, I am trying to hide a specific point if I click on it. Using the ´intersect-method I'm able to enlarge these or change their color: using particles.geometry.attributes.size.array[INDEX]and setting.needsUpdate`. However, I complete invisibility is not possible. The goal is to hide the points by the INDEX it has in the 'buffer geometry'. Below you can find a code snippet of the example.

var vertices = new THREE.BoxGeometry( 200, 200, 200, 16, 16, 16 ).vertices;

var positions = new Float32Array( vertices.length * 3 );
var colors = new Float32Array( vertices.length * 3 );
var sizes = new Float32Array( vertices.length );

var vertex;
var color = new THREE.Color();

for ( var i = 0, l = vertices.length; i < l; i ++ ) {

    vertex = vertices[ i ];
    vertex.toArray( positions, i * 3 );

    color.setHSL( 0.01 + 0.1 * ( i / l ), 1.0, 0.5 );
    color.toArray( colors, i * 3 );

    sizes[ i ] = PARTICLE_SIZE * 0.5;

}

var geometry = new THREE.BufferGeometry();
geometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
geometry.setAttribute( 'customColor', new THREE.BufferAttribute( colors, 3 ) );
geometry.setAttribute( 'size', new THREE.BufferAttribute( sizes, 1 ) );

//

var material = new THREE.ShaderMaterial( {

    uniforms: {
        color: { value: new THREE.Color( 0xffffff ) },
        pointTexture: { value: new THREE.TextureLoader().load( "textures/sprites/disc.png" ) }
    },
    vertexShader: document.getElementById( 'vertexshader' ).textContent,
    fragmentShader: document.getElementById( 'fragmentshader' ).textContent,

    alphaTest: 0.9

} );

//

particles = new THREE.Points( geometry, material );
scene.add( particles );

Changing the 'size' on hover works as the following:

// function render() {

particles.rotation.x += 0.0005;
particles.rotation.y += 0.001;

var geometry = particles.geometry;
var attributes = geometry.attributes;

raycaster.setFromCamera( mouse, camera );

intersects = raycaster.intersectObject( particles );

if ( intersects.length > 0 ) {

    if ( INTERSECTED != intersects[ 0 ].index ) {

        attributes.size.array[ INTERSECTED ] = PARTICLE_SIZE;

        INTERSECTED = intersects[ 0 ].index;

        attributes.size.array[ INTERSECTED ] = PARTICLE_SIZE * 1.25;
        attributes.size.needsUpdate = true;

    }

I have looked at shaders (in my opinion way too complex for this task) and trying to create transparent material. Is there something overlooked?

Much thanks in advance.


Solution

  • As an option, using of an additional buffer attribute for visibility and processing it in shaders, does the trick:

    body {
      overflow: hidden;
      margin: 0;
    }
    <script type="module">
    import * as THREE from "https://threejs.org/build/three.module.js";
    
    let scene = new THREE.Scene();
    let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 100);
    camera.position.set(0, 0, 10);
    let renderer = new THREE.WebGLRenderer();
    renderer.setClearColor(0x444444);
    renderer.setSize(innerWidth, innerHeight);
    
    document.body.appendChild(renderer.domElement);
    
    let g = new THREE.PlaneBufferGeometry(10, 10, 10, 10);
    g.index = null;
    let pos = g.getAttribute("position");
    let colors = [];
    let color = new THREE.Color();
    let sizes = [];
    let visibility = [];
    for(let i = 0; i < pos.count; i++){
      color.set( Math.random() * 0x888888 + 0x888888);
      colors.push(color.r, color.g, color.b);
      sizes.push( Math.random() * 0.5 + 0.5);
      visibility.push(1);
    }
    g.setAttribute("color", new THREE.Float32BufferAttribute(colors, 3));
    g.setAttribute("sizes", new THREE.Float32BufferAttribute(sizes, 1));
    g.setAttribute("visibility", new THREE.Float32BufferAttribute(visibility, 1));
    
    let m = new THREE.PointsMaterial({
      vertexColors: true,
      onBeforeCompile: function(shader){
        shader.vertexShader = `
          attribute float sizes;
          attribute float visibility;
          varying float vVisible;
        ${shader.vertexShader}`
        .replace(
          `gl_PointSize = size;`,
          `gl_PointSize = size * sizes;
            vVisible = visibility;
          `
        );
        shader.fragmentShader = `
          varying float vVisible;
        ${shader.fragmentShader}`
        .replace(
          `#include <clipping_planes_fragment>`,
          `
            if (vVisible < 0.5) discard;
          #include <clipping_planes_fragment>`
        )
      }
    });
    
    let p = new THREE.Points(g, m);
    scene.add(p);
    
    let oldIndex = 0;
    setInterval(function(){
      let newIndex = THREE.Math.randInt(0, pos.count - 1);
      g.attributes.visibility.setX(oldIndex, 1);
      g.attributes.visibility.setX(newIndex, 0);
      oldIndex = newIndex;
      g.attributes.visibility.needsUpdate = true;
    }, 500);
    
    renderer.setAnimationLoop(animate);
    function animate(){
      renderer.render(scene, camera);
    }
    
    </script>