[SOLVED] How can I colour points differently in three.js using dat.gui depending on their distance from the origin?

Issue

I’m currently working on a 3D Pi Estimator built in three.js, for which the source code can be seen at https://jsfiddle.net/fny94e7w/.

let num_points = 5000;
            let in_sphere = 0;
            let out_sphere = 0;
            let geometry_point = new THREE.SphereGeometry( 0.1, 3, 2 );
            for (let i = 0; i < num_points; i++) {
                let material_point = new THREE.MeshBasicMaterial( { color: 0xffffff } ); // Default
                let point = new THREE.Mesh( geometry_point, material_point );
                let point_pos_x = (Math.random() - 0.5) * 20;
                let point_pos_y = (Math.random() - 0.5) * 20;
                let point_pos_z = (Math.random() - 0.5) * 20;
                if (Math.pow(point_pos_x, 2) + Math.pow(point_pos_y, 2) + Math.pow(point_pos_z, 2) <= 100) {
                    point.material.color.setHex( 0x42c5f5 );
                    in_sphere++;
                }
                else {
                    point.material.color.setHex( 0xffffff );
                    out_sphere++;
                }
                point.position.set(point_pos_x,
                                   point_pos_y,
                                   point_pos_z);
                scene.add( point );
            }

As you can see from this snippet, each point is coloured differently depending on whether it lies within a sphere centred at the origin. I am looking for a solution on how I can make a GUI using dat.gui, such that I can interactively customise the colour of points that are outside and inside the sphere.

I am also unsure as to how or if I could produce this with InstancedMesh which could lead to a significant boost in performance (this simulation struggles with more than ~15000 points) – I don’t know how to make this with InstancedMesh because of the different dot colours depending on position, and would appreciate if there was a solution to this which in turn could make it easier to solve the issue with dat.gui.

Solution

Here is the modification of your code, that uses InstancedMesh.

body {
  overflow: hidden;
  margin: 0;
}

#info {
  position: absolute;
  top: 10px;
  width: 100%;
  text-align: center;
  z-index: 100;
  display: block;
  color: white;
  font-family: 'Courier New', monospace;
  s
}

#lil-gui {
  left: 10px;
  top: 70px;
}
      
<div id="info">3d pi estimator <br> dots in sphere: 0 • dots outside sphere: • pi estimate: </div>
<script type="module">
import * as THREE from 'https://cdn.skypack.dev/[email protected]';
import { OrbitControls } from 'https://cdn.skypack.dev/[email protected]/examples/jsm/controls/OrbitControls.js';
import { GUI } from "https://cdn.skypack.dev/[email protected]/examples/jsm/libs/lil-gui.module.min.js";


const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );

const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );

const controls = new OrbitControls( camera, renderer.domElement );
controls.autoRotate = true;
camera.position.z = 30;
camera.position.y = 15;
controls.update();

const geometry_cube = new THREE.BoxGeometry( 20, 20, 20 );
const edges_cube = new THREE.EdgesGeometry( geometry_cube );
const cube = new THREE.LineSegments( edges_cube, new THREE.LineBasicMaterial( { color: 0xffffff } ) );
scene.add( cube );

const geometry_sphere = new THREE.SphereGeometry( 10, 32, 16 );
const material_sphere = new THREE.MeshBasicMaterial( { color: 0xffffff } );
const sphere = new THREE.Mesh( geometry_sphere, material_sphere );
sphere.material.transparent = true;
sphere.material.opacity = 0.5;
scene.add( sphere );

let num_points = 30000;
let in_sphere = 0;
let out_sphere = 0;
let geometry_point = new THREE.SphereGeometry( 0.1, 3, 2 );
let colorController = {
  color1: "#ffffff",
  color2: "#42c5f5"
};
let uniforms = {
  color1: {value: new THREE.Color(colorController.color1)},
  color2: {value: new THREE.Color(colorController.color2)}
};
let material_point = new THREE.MeshBasicMaterial( { 
  color: 0xffffff,
  onBeforeCompile: shader => {
    shader.uniforms.color1 = uniforms.color1;
    shader.uniforms.color2 = uniforms.color2;
    shader.vertexShader = `
      attribute float colorIdx;
      varying float vColorIdx;
      ${shader.vertexShader}
    `.replace(
      `#include <begin_vertex>`,
      `#include <begin_vertex>
        vColorIdx = colorIdx;
      `
    );
    //console.log(shader.vertexShader);
    shader.fragmentShader = `
      uniform vec3 color1;
      uniform vec3 color2;
      varying float vColorIdx;
      ${shader.fragmentShader}
    `.replace(
      `vec4 diffuseColor = vec4( diffuse, opacity );`,
      `
      vec3 finalColor = mix(color1, color2, vColorIdx);
      finalColor *= diffuse;
      vec4 diffuseColor = vec4( finalColor, opacity );
      `
    );
    //console.log(shader.fragmentShader);
  }
} );
let point = new THREE.InstancedMesh( geometry_point, material_point, num_points );
let dummy = new THREE.Object3D();
let colorIdx = [];
for (let i = 0; i < num_points; i++) {
    dummy.position.random().subScalar(0.5).multiplyScalar(20);
    dummy.updateMatrix();
    point.setMatrixAt(i, dummy.matrix);
    if (dummy.position.length() <= 10) {
        colorIdx.push(1);
        in_sphere++;
    }
    else {
        colorIdx.push(0);
        out_sphere++;
    }
}
geometry_point.setAttribute("colorIdx", new THREE.InstancedBufferAttribute(new Float32Array(colorIdx), 1));
scene.add(point);

let gui = new GUI({autoplace:false});
gui.domElement.id = "lil-gui"
gui.addColor(colorController, "color1").onChange(val => {uniforms.color1.value.set(val)});
gui.addColor(colorController, "color2").onChange(val => {uniforms.color2.value.set(val)});

document.getElementById("info").innerHTML = "3d pi estimator <br>" + "dots in sphere: " + in_sphere + " • dots outside sphere: " + out_sphere + " • pi estimate: " + ((6 * in_sphere) / (in_sphere + out_sphere)); 

function animate() {
    requestAnimationFrame( animate );
    controls.update(); // Required because of auto-rotation
    renderer.render( scene, camera );
};

animate();
</script>

Answered By – prisoner849

Answer Checked By – Marie Seifert (BugsFixing Admin)

Leave a Reply

Your email address will not be published. Required fields are marked *