How to make colored Vertices for each iteration of subdivision

67 Views Asked by At

I have an application that has various standard geometries (for example, box geometry https://threejs.org/docs/#api/en/geometries/BoxGeometry) and the application applies up to 5 levels of subdivision on the original geometry. I am struggling to figure out how to color code the vertices to visualize the difference between iterations/levels of subdivision. Generally, I want the original vertices of the original geometry (iteration 0, no subdivision applied) to be one color (for example purple), then in the next iteration (iteration 1, 1 level of subdivision), I want to preserve the purple vertices and the new vertices created from the subdivision should be another color (for example red). Is there a way to do this?

UPDATE: I figured out how to get colored points on each vertex, however, there are still some issues. For example, the box geometry points are all perfect, but the original ("normalgeometry") for all of the other shapes such as cylinder, there appear to be extra vertices that I cannot figure out why they are being placed or how to get rid of it. I also still can't figure out how to differentiate the different subdivision levels. Any help is greatly appreciated.

enter image description here

enter image description here code:

import * as THREE from 'three';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import LoopSubdivision from './LoopSubdivision.js';
import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';

let renderer, scene, camera;
let standardMat;
let meshNormal, meshSmooth;
let wireNormal, wireSmooth;
let wireMaterial;
let coloredPoints;



// Create a gray color
var grayColor = new THREE.Color(0.5, 0.5, 0.5); // base color

var color0 = new THREE.Color(1, 0, 0); // Red
var color1 = new THREE.Color(0, 1, 0); // Green
var color2 = new THREE.Color(0, 0, 1); // Blue
var color3 = new THREE.Color(1, 1, 0); // Yellow
var color4 = new THREE.Color(1, 0, 1); // Pink
var color5 = new THREE.Color(0, 1, 1); // Cyan

var colors = [
    color0.toArray(),
    color1.toArray(),
    color2.toArray(),
    color3.toArray(),
    color4.toArray(),
    color5.toArray()
];

// Define colors for each iteration
var colorsByIteration = [
    [color0.toArray()],
    [color0.toArray(), color1.toArray()],
    [color0.toArray(), color1.toArray(), color2.toArray()],
    [color0.toArray(), color1.toArray(), color2.toArray(), color3.toArray()],
    [color0.toArray(), color1.toArray(), color2.toArray(), color3.toArray(), color4.toArray()],
    [color0.toArray(), color1.toArray(), color2.toArray(), color3.toArray(), color4.toArray(), color5.toArray()]
];

const params = {
    geometry: 'Box',
    iterations: 3,
    split: true,
    wireframe: true
};

init();

function init() {
    const version = parseInt(THREE.REVISION.replace(/\D/g, ''));

    renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    if (version && version > 151) renderer.outputColorSpace = THREE.LinearSRGBColorSpace;
    document.body.appendChild(renderer.domElement);

    scene = new THREE.Scene();

    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight);
    camera.position.set(0, 0.7, 2.1);

    const controls = new OrbitControls(camera, renderer.domElement);
    controls.addEventListener('change', render);
    controls.rotateSpeed = 0.5;
    controls.minZoom = 1;
    controls.target.set(0, 0, 0);
    controls.update();

    const light = new THREE.PointLight(0xff0000, 1, 100);
    light.position.set(2.5, 7.5, 15);
    scene.add(light);

    // standardMat = new THREE.MeshBasicMaterial(); // rainbow color
    // standardMat.vertexColors = THREE.VertexColors;
    standardMat = new THREE.MeshBasicMaterial({ color: grayColor });

    meshNormal = new THREE.Mesh(new THREE.BufferGeometry(), standardMat);
    meshSmooth = new THREE.Mesh(new THREE.BufferGeometry(), standardMat);
    meshNormal.position.set(-0.7, 0, 0);
    meshSmooth.position.set(0.7, 0, 0);
    scene.add(meshNormal, meshSmooth);

    wireMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff, depthTest: true, wireframe: true });
    wireNormal = new THREE.Mesh(new THREE.BufferGeometry(), wireMaterial);
    wireSmooth = new THREE.Mesh(new THREE.BufferGeometry(), wireMaterial);
    wireNormal.visible = false;
    wireSmooth.visible = false;
    wireNormal.position.copy(meshNormal.position);
    wireSmooth.position.copy(meshSmooth.position);
    scene.add(wireNormal, wireSmooth);

    updateMeshes();

    window.addEventListener('resize', onWindowResize);

    const geomTypes = ['Box', 'Capsule', 'Circle', 'Cylinder', 'Dodecahedron', 'Icosahedron', 'Octahedron', 'Plane', 'Ring', 'Sphere', 'Tetrahedron', 'Torus'];//, 'Upload'];

    const gui = new GUI();

    const folder1 = gui.addFolder('Subdivide Params');
    const geomController = folder1.add(params, 'geometry', geomTypes).onFinishChange(() => {
        const geom = params.geometry.toLowerCase();
        params.split = geom === 'box' || geom === 'ring' || geom === 'plane';
        refreshDisplay();
    });

    folder1.add(params, 'iterations').min(0).max(5).step(1).onFinishChange(updateMeshes);
    const splitController = folder1.add(params, 'split').onFinishChange(updateMeshes);

    const folder2 = gui.addFolder('Material');
    folder2.add(params, 'wireframe').onFinishChange(updateWireframe);

    function refreshDisplay() {
        geomController.updateDisplay();
        splitController.updateDisplay();
        updateMeshes();
    }
}


function getGeometry() {

    switch ( params.geometry.toLowerCase() ) {

        case 'box':
            return new THREE.BoxGeometry();

        case 'capsule':
            return new THREE.CapsuleGeometry( 0.5, 0.5, 3, 5 );

        case 'circle':
            return new THREE.CircleGeometry( 0.6, 10 );

        case 'cylinder':
            return new THREE.CylinderGeometry( 0.5, 0.5, 1, 5, 4 );

        case 'dodecahedron':
            return new THREE.DodecahedronGeometry( 0.6 );

        case 'icosahedron':
            return new THREE.IcosahedronGeometry( 0.6 );

        case 'octahedron':
            return new THREE.OctahedronGeometry( 0.7 );

        case 'plane':
            return new THREE.PlaneGeometry();

        case 'ring':
            return new THREE.RingGeometry( 0.3, 0.6, 10 );

        case 'sphere':
            return new THREE.SphereGeometry( 0.6, 8, 4 );

        case 'tetrahedron':
            return new THREE.TetrahedronGeometry( 0.8 );

        case 'torus':
            return new THREE.TorusGeometry( 0.48, 0.24, 4, 6 );

        // case 'Upload':
        //     // when user wants to upload their own obj file
    }

}





function createColoredPoints(geometry, color, offset) {
    const positions = geometry.attributes.position;

    if (!positions) {
        console.error('Invalid geometry for colored points.');
        return null;
    }

    const pointsGeometry = new THREE.BufferGeometry();
    pointsGeometry.setAttribute('position', positions);

    const pointsMaterial = new THREE.PointsMaterial({ color: color, size: 0.05 }); // Adjust the size as needed
    const points = new THREE.Points(pointsGeometry, pointsMaterial);

    // Center the points on the geometry and apply the offset
    const boundingBox = new THREE.Box3().setFromBufferAttribute(positions);
    const center = new THREE.Vector3();
    boundingBox.getCenter(center);
    const adjustedOffset = offset.clone().add(center); // Adjusted offset based on the bounding box center
    points.position.copy(adjustedOffset);

    return points;
}


function updateMeshes() {
    const normalGeometry = getGeometry();
    const smoothGeometry = LoopSubdivision.modify(normalGeometry, params.iterations, params);

    meshNormal.geometry.dispose();
    meshSmooth.geometry.dispose();
    meshNormal.geometry = normalGeometry;
    meshSmooth.geometry = smoothGeometry;

    wireNormal.geometry.dispose();
    wireSmooth.geometry.dispose();
    wireNormal.geometry = normalGeometry.clone();
    wireSmooth.geometry = smoothGeometry.clone();

    // Create colored points for the normal and smooth geometries and add to the scene
    if (coloredPoints) {
        scene.remove(coloredPoints);
    }

    const coloredPointsNormal = createColoredPoints(normalGeometry, color0, new THREE.Vector3(-0.7, 0, 0));
    const coloredPointsSmooth = createColoredPoints(smoothGeometry, color0, new THREE.Vector3(0.7, 0, 0));

    // Check if coloredPoints are defined before adding them to the scene
    if (coloredPointsNormal) {
        scene.add(coloredPointsNormal);
    }

    if (coloredPointsSmooth) {
        scene.add(coloredPointsSmooth);
    }

    // Ensure wireframe visibility is updated
    updateWireframe();

    // Update the material if needed
    updateMaterial();

    // Render the scene
    render();
}




function disposeMaterial(material) {
    const materials = Array.isArray(material) ? material : [material];
    for (let i = 0; i < materials.length; i++) {
        if (materials[i].dispose) materials[i].dispose();
    }
}



function updateMaterial() {
    disposeMaterial(meshNormal.material);
    disposeMaterial(meshSmooth.material);

    meshNormal.material = meshSmooth.material = standardMat;

    render();
}



function updateWireframe() {
    wireNormal.visible = wireSmooth.visible = params.wireframe;
    render();
}

function onWindowResize() {
    renderer.setSize(window.innerWidth, window.innerHeight);
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    render();
}

function render() {
    renderer.render(scene, camera);
}


1

There are 1 best solutions below

1
prisoner849 On

I'm not sure what behaviour you want to achieve.

So, here is a wild shot: enter image description here

body{
  overflow: hidden;
  margin: 0;
}
<script type="importmap">
  {
    "imports": {
      "three": "https://unpkg.com/[email protected]/build/three.module.js",
      "three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
    }
  }
</script>
<script type="module">
import * as THREE from "three";
import {OrbitControls} from "three/addons/controls/OrbitControls.js";

let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
camera.position.set(0, 0, 10);
let renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);

window.addEventListener("resize", event => {
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(innerWidth, innerHeight);
});

let controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

let colors = ["#f00", "#0f0", "#00f", "#ff0", "#0ff", "#f0f"].map(c => new THREE.Color(c));
console.log(colors);

for(let i = 0; i < colors.length; i++){
  
  let mesh = new THREE.Mesh(
    new THREE.IcosahedronGeometry(1, i),
    new THREE.MeshBasicMaterial({color: "gray", wireframe: true})
  );
  setPointsColor(mesh, i);
  
  mesh.position.x = (-(colors.length - 1) * 0.5 + i) * 3;
  
  scene.add(mesh);

}

renderer.setAnimationLoop(() => {
  controls.update();
  renderer.render(scene, camera);
})

function setPointsColor(mesh, colorIndex){
  let g = mesh.geometry;
  let p = new THREE.Points(g, new THREE.PointsMaterial({size: 0.2, color: colors[colorIndex]}));
  mesh.add(p);
}
</script>