A dynamically deformable circle (+shader)

In the meantime I have created a variant with shaders. CircleDynamicallyFormableShader

I have added stats. The shader variant is more performant.

However, the MeshPhongMaterial is not yet properly supported, since the calculation of the normals in the shader is still missing.

For this I first looked at the sources
Tutorial 8 : Basic shading

and to three.js
Calculating vertex normals after displacement in the vertex shader
Before I try to implement this, I’m interested to know if there are any new findings on this since March of this year.
:thinking:


<!DOCTYPE html>
<!-- https://discourse.threejs.org/t/a-dynamically-deformable-circle/33113 -->
<head>
  <title> CircleDynamicallyFormableShader </title>
  <meta charset="utf-8" />
  <style>
    body {
        overflow:hidden;
        margin: 0;
    }
  </style>
</head>
<body> </body>

<script type="module">

// @author hofk

import * as THREE from '../jsm/three.module.135.js';
import {OrbitControls} from '../jsm/OrbitControls.135.js';
import Stats from '../jsm/stats.module.135.js';

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 55, window.innerWidth / window.innerHeight, 0.01, 10000 );
camera.position.set( 0.4, 0.3, 3.1 );
const renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( 0xdedede );
document.body.appendChild( renderer.domElement );

const controls = new OrbitControls( camera, renderer.domElement );
const stats = new Stats( );
document.body.appendChild( stats.dom );

const light = new THREE.PointLight( );
light.position.set( -1, 2, 4);
scene.add( light );;

// inputs
const geometry = new THREE.BufferGeometry( );
const radius = 0.96;
const rings = 144;
const parts = 6;

CircleCustomShader( geometry, radius, rings, parts );

const uniforms = { u_time: { value: 0.0 } }

const sPart = shader => {  
    shader.uniforms.u_time = uniforms.u_time;
    shader.uniforms.u_radius = uniforms.u_radius;
    shader.vertexShader = `
      #define PI 3.141592653589793
      uniform float u_time;
      ${shader.vertexShader}
    `.replace(
      `#include <begin_vertex>`,
      `#include <begin_vertex>
        vec3 p = transformed;
        float len = length( p.xy );
        float phi = atan( p.x, p.y );
        p.xy +=  p.xy * 0.03 * len * sin( 18.0 * phi ) * ( 1.0 + cos( u_time ) );
        p.z = 0.04 * ( 1.0 + sin( PI * 12.0 * len ) ) * cos( u_time * 0.3 );
        transformed = p;
      `
    );
  }
 
const loader = new THREE.TextureLoader( );
const tex_1 = loader.load( 'uv_grid_opengl.jpg' ); 
const tex_2 = loader.load( 'rose.png' );   


const material_0 = new THREE.MeshPhongMaterial( { onBeforeCompile: sPart, color: 0xff00ff, side: THREE.DoubleSide, wireframe: false } );
const material_1 = new THREE.MeshBasicMaterial( { onBeforeCompile: sPart, map: tex_1, side: THREE.DoubleSide, wireframe: true} );
const material_2 = new THREE.MeshBasicMaterial( { onBeforeCompile: sPart, map: tex_2, side: THREE.DoubleSide, wireframe: false } );

const circle_0 = new THREE.Mesh( geometry, material_0 );
circle_0.position.x = -2;
scene.add( circle_0 );

const circle_1 = new THREE.Mesh( geometry, material_1);
scene.add( circle_1 );

const circle_2 = new THREE.Mesh( geometry, material_2 );
circle_2.position.x = 2;
scene.add( circle_2 );


animate( );

function animate() {

    requestAnimationFrame( animate );
    uniforms.u_time.value += 0.03;
    renderer.render( scene, camera );
    stats.update( );
}

function CircleCustomShader( g, r, rings, parts ) {
    
    const vertexCount = 1 + parts / 2 * rings * ( rings + 1 ) ;
    
    let idxCount = 0;
    let faceCount = parts * rings * rings;
    
    const faceIndices = new Uint32Array( faceCount * 3 );
    const vertices = new Float32Array( vertexCount * 3 );
    const uvs = new Float32Array( vertexCount * 2 );
       
    g.setIndex( new THREE.BufferAttribute( faceIndices, 1 ) );
    g.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
    g.setAttribute( 'uv', new THREE.BufferAttribute( uvs, 2 ) );
    
    const setFace =  ( ) => {
        
        faceIndices[ idxCount     ] = a;
        faceIndices[ idxCount + 1 ] = b;
        faceIndices[ idxCount + 2 ] = c;
        
        idxCount += 3;
        
    }
    
    let posIdx, uvIdx;
    
    let a = 0; // vertex 0: center
    let b = 1;
    let c = 2;
    
    for ( let j = 0; j < parts; j ++ ) { // around center
        
        setFace( );	
        b ++;
        if ( b < parts ) { c ++; } else { c = 1; }
        
    }
    
    let rvSum = 1; // only vertex 0
    
    for ( let i = 1; i < rings; i ++ ) {
        
        for ( let q = 0; q < parts; q ++ ) {
            
            for ( let j = 0; j < i + 1 ; j ++ ) {  
                
                if ( j === 0 ) {
                    
                    //  first face in part 
                    
                    a = rvSum;
                    b = a + parts * i + q;
                    c = b + 1;
                    
                    setFace();
                    
                } else {
                     
                    //  two faces / vertex
                    
                    a = j + rvSum; 
                    b = a - 1; 
                    c = a + parts * i + q;
                    if ( q === ( parts - 1 ) && j === i ) a = a - parts * i; // connect to first vertex of circle
                    
                    setFace();
                    
                    // a  from first face 
                    b = c; // from first face
                    c = b + 1;
                    
                    if ( q === ( parts - 1 ) && j === i ) c = c - parts * ( i + 1 ); // connect to first vertex of next circle
                    
                    setFace();
                    
                }
                
            }
            
            rvSum += i;
            
        }
        
    }   
    
    uvs[ 0 ] = 0.5;
    uvs[ 1 ] = 0.5;
    
    let u, v;
    
    rvSum = 1;  // without center
    
    for ( let i = 0; i <= rings; i ++ ) {
        
        const ni = i / rings;
        
        for ( let j = 0; j < i * parts; j ++ ) {
            
            const phi = Math.PI * 2 * j / ( i * parts );
            
            u = 0.5 * ( 1 + ni * Math.cos( phi ) );
            v = 1 - 0.5 * ( 1 + ni * Math.sin( phi ) );
            
            uvIdx  = ( rvSum + j ) * 2;
            
            uvs[ uvIdx     ] = u;
            uvs[ uvIdx + 1 ] = v;
            
        }
        
        rvSum += i * parts;
        
    }
    
    g.setVertices = ( t ) => {
    
        let x, y, z, posidx;
        
        vertices[ 0 ] = 0;
        vertices[ 1 ] = 0;
        vertices[ 2 ] = 0;
        
        rvSum = 1; // without center
        
        for ( let i = 0; i <= rings; i ++ ) {
        
            const ni = i / rings;
            
            for ( let j = 0; j < i * parts; j ++ ) {
                
                const phi = Math.PI * 2 * j / ( i * parts );
                
                x =  r * Math.cos( phi ) * ni;
                y = -r * Math.sin( phi ) * ni;
                z =  0; //Math.sin( Math.PI * 2 * ni );
                    
                posIdx = ( rvSum + j ) * 3;
                
                vertices[ posIdx     ] = x;
                vertices[ posIdx + 1 ] = y;
                vertices[ posIdx + 2 ] = z;
                
            }
            
            rvSum += i * parts;
            
        }
        
        g.computeVertexNormals( );
        
    }
    
    g.setVertices( );

}

</script>
</html>
2 Likes