Search code examples
javascriptuser-interfacethree.jsdat.gui

How to change the size of a 2D circle using lil gui with THREE.js?


I want to change the size of a circle as I drag a lil gui slider. I have this code, which draws a circle with a blue line in a black screen, with an ortographic camera:

    initGUI();
    let radius = 0.6;
    let vertices = [];
    for(let i = 0; i <= 360; i++){
        vertices.push(new THREE.Vector3(Math.sin(i*(Math.PI/180))*radius, Math.cos(i*(Math.PI/180))*radius, 0));
    }

    let geometry = new THREE.BufferGeometry().setFromPoints(vertices);

    let material = new THREE.LineBasicMaterial({color:"blue"})
    var lineStrip = new THREE.Line( geometry, material );

I also have this initGUI() function, which makes a slide that changes the value of controls.radius

function initGUI() {

    controls =  {   radius  : 0.6
                };

    gui.add( controls, 'radius', 0.1, 0.7).onChange(value => changeRadius(value));
    gui.open();
};

the onChange method sends the new values of the radius to the function changeRadius, which is:

function changeRadius(value) {
    radius = value;
    var vertices = [];
    for(let i = 0; i <= 360; i++){
        vertices.push(new THREE.Vector3(Math.sin(i*(Math.PI/180))*radius, Math.cos(i*(Math.PI/180))*radius, 0));
    }
    renderer.clear();
    renderer.render(scene, camera);
}

this doesn't work, though. It produces the error "renderer is not defined". I tried to pass the renderer as the function parameter, then it gave something similar to "scene and camera are not defined", then I also set scene and camera as parameters of the function and no errors were produced, but the circle didn't change its size.


Solution

  • Alternatively to what @prisoner849 suggested (meaning creating the circle with a radius of 1 and then just scale it), you can use the below code snippet to update the geometry. I guess that is what you have originally in mind.

    import * as THREE from 'three';
    
    import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
    
    let camera, scene, renderer;
    
    let lineStrip;
    
    const params = {
        radius: 0.6
    };
    
    init();
    render();
    
    function init() {
    
        camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.01, 10 );
        camera.position.z = 1;
    
        scene = new THREE.Scene();
    
        const radius = params.radius;
        const vertices = [];
    
        for ( let i = 0; i <= 360; i ++ ) {
    
            vertices.push( new THREE.Vector3( Math.sin( i * ( Math.PI / 180 ) ) * radius, Math.cos( i * ( Math.PI / 180 ) ) * radius, 0 ) );
    
        }
    
        const geometry = new THREE.BufferGeometry().setFromPoints( vertices );
        const material = new THREE.LineBasicMaterial( { color: 'blue' } );
    
        lineStrip = new THREE.Line( geometry, material );
        scene.add( lineStrip );
    
        renderer = new THREE.WebGLRenderer( { antialias: true } );
        renderer.setPixelRatio( window.devicePixelRatio );
        renderer.setSize( window.innerWidth, window.innerHeight );
        document.body.appendChild( renderer.domElement );
    
        const gui = new GUI( { width: 300 } );
    
        gui.add( params, 'radius', 0.1, 2 ).onChange( changeRadius );
        gui.open();
    
    }
    
    function render() {
    
        renderer.render( scene, camera );
    
    }
    
    function changeRadius() {
    
        const positionAttribute = lineStrip.geometry.getAttribute( 'position' );
        const radius = params.radius;
    
        for ( let i = 0; i <= 360; i ++ ) {
    
            const x = Math.sin( i * ( Math.PI / 180 ) ) * radius;
            const y = Math.cos( i * ( Math.PI / 180 ) ) * radius;
            const z = 0;
    
            positionAttribute.setXYZ( i, x, y, z );
    
        }
    
        positionAttribute.needsUpdate = true;
    
        render();
    
    }
    

    Notice how it's not sufficient to update the vertices array. You have to update the buffer attribute instead.