# A dynamically deformable circle (+shader)

The default circle of three.js (CircleGeometry) has one vertex in the center point and the others at the edge.

If you want to animate the circle area, you need much more vertices.

CircleDynamicallyFormable

``````
// inputs
const geometry = new THREE.BufferGeometry( );
const radiusFunction = ( r, theta, t ) => r * ( 1 + 0.03 * Math.sin( theta * 18 ) * Math.cos( t ) );
const heightFunction = ( n, t ) => 0.04 * ( 1 + Math.sin( Math.PI * n * 12 ) ) * Math.cos( 0.3 * t );
const rings = 144;
const parts = 6;

``````

``````
function CircleCustom( g, r, rf, hf, 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 ).setUsage( THREE.DynamicDrawUsage) );
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 ] = hf( 0, t );

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 =  rf( r, phi, t ) * Math.cos( phi ) * ni;
y = -rf( r, phi, t ) * Math.sin( phi ) * ni;
z =  hf( ni, t );

posIdx = ( rvSum + j ) * 3;

vertices[ posIdx     ] = x;
vertices[ posIdx + 1 ] = y;
vertices[ posIdx + 2 ] = z;

}

rvSum += i * parts;

}

g.computeVertexNormals( ) ;
g.attributes.position.needsUpdate = true;

}

g.setVertices( );

}
``````
5 Likes

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

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.

``````<!DOCTYPE html>
<!-- https://discourse.threejs.org/t/a-dynamically-deformable-circle/33113 -->
<meta charset="utf-8" />
<style>
body {
overflow:hidden;
margin: 0;
}
</style>
<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);

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

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

const sPart = shader => {
#define PI 3.141592653589793
uniform float u_time;
`.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 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;

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

const circle_2 = new THREE.Mesh( geometry, material_2 );
circle_2.position.x = 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

Tried to compute normals in vertex shader: Edit fiddle - JSFiddle - Code Playground

``````const sPart = shader => {
#define PI 3.141592653589793
uniform float u_time;

vec3 getPoint(vec3 p){
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 );
return p;
}

`.replace(
`#include <defaultnormal_vertex>`,
`
vec2 e = vec2(0.001, 0.);

vec3 p0 = getPoint(position + e.yyy);
vec3 p1 = getPoint(position + e.xyy);
vec3 p2 = getPoint(position + e.yxy);

vec3 t1 = p1 - p0;
vec3 t2 = p2 - p0;

objectNormal = cross(t1, t2);

#include <defaultnormal_vertex>`
)
.replace(
`#include <begin_vertex>`,
`#include <begin_vertex>

transformed = p0;
`
);
}
``````
5 Likes

… is a strong understatement.

The solution looks clear to me and is obviously perfect. I especially like the way of determining p0,p1,p2.

I myself have only managed to apply the rather complicated codepen ( https://codepen.io/marco_fugaro/pen/xxZWPWJ from Calculating vertex normals after displacement in the vertex shader ) for my geometry in the meantime.

But I could not get the essential out of the mixture of controls-state
`const controls = initControls({ ...`
and
`vertexShader: monkeyPatch(THREE.ShaderChunk.meshphysical_vert, {...`
pull out.

In the solution I see that <defaultnormal_vertex> is the key. But how to do it, I certainly would not have thought of that. The extensive shader parts in three.module.js I do not see through.

Although I had tried to do something with
`var defaultnormal_vertex = "vec3 transformedNormal = objectNormal; ...`
but without success.

Can this way of determining vertex normals be directly integrated into three.js? Or are there reasons why this is not useful?

I have incorporated the solution @prisoner849 in the example CircleDynamicallyFormableShader

After the transfer into the shader part I got an incorrect display. A scattering error as I often do, but was not to be found. Later I noticed that prisoner849 changed the function CircleCustomShader. This for a good reason!

I copied the structure from one of my examples to spheres. There you need different orientations. I got exactly the wrong one and didn’t notice it because I always used
` side: THREE.DoubleSide` .

Actually I should learn from my mistakes, the same thing happened to me just a few months ago.

``````const setFace =  ( ) => {

faceIndices[ idxCount     ] = a;
//faceIndices[ idxCount + 1 ] = b;
//faceIndices[ idxCount + 2 ] = c;
faceIndices[ idxCount + 1 ] = c; // swapped here c and b
faceIndices[ idxCount + 2 ] = b; // otherwise, the geometry is backsided
idxCount += 3;

}
``````

I have also included the modified version in the zip file 2021 from

1 Like

The method can be used to achieve performant dynamic base geometries very easily.

`const geometry = new THREE.CylinderBufferGeometry( 0.5, 0.5, 1, 360, 10, false );`
or
`const geometry = new THREE.BoxBufferGeometry( 1.0, 1.0, 1.0, 100, 100, 100 );`

``````  vec3 getPoint( vec3 p ) {
float r = length( p.xzy );
float phi = atan( p.x, p.z );
p.xz +=  p.xz * 0.2 * r * sin( 3.0 * phi ) * ( 1.0 + cos( u_time ) ); //   4.0 * phi box
return p;
}
``````

`const geometry = new THREE.PlaneBufferGeometry( 1.0, 1.0, 100, 100 );`

`````` vec3 getPoint(vec3 p){
float lenx = length( p.x);
float leny = length( p.y);
p.z = 0.04 * ( 1.0 + sin( PI * 24.0 * lenx * leny  ) ) * cos( u_time * 0.3 );
return p;
}
``````

`const geometry = new THREE.PlaneBufferGeometry( 1.0, 1.0, 100, 100 );`

``````  float f( float d ) {
return 0.5 * ( 1.0 + sin( d ) ) * sqrt( d );
}
vec3 getPoint( vec3 p ) {
float lenx = length( p.x );
float leny = length( p.y );
p.z = ( lenx - leny ) * ( leny - lenx ) * f( lenx ) * f( leny ) * 6.0 * sin( u_time );
// p.z = ( lenx + leny ) * ( leny + lenx ) * f( lenx ) * f( leny ) * 0.6 * sin( u_time ); // try out
return p;
}
``````

`const geometry = new THREE.TorusGeometry( 1, 0.5, 64, 100 );`

``````  vec3 getPoint( vec3 p ) {
p.z =  abs( p.z ) > 0.3 ? sign( p.z ) * 0.3 : p.z; // symmetrical
return p;
}
``````

`const geometry = new THREE.BoxGeometry( 1, 1, 1, 360, 1, 1 );`

``````  vec3 getPoint( vec3 p ) {
// @author prisoner89
float r = 2.0;
float R = 3.0;
float waves = 5.0;
float angle = p.x * PI * 2.0;
float radius = p.z > 0.0 ? R : r;
float y = p.y < 0.5 ? p.y : p.y + sin( p.x * PI * 2.0 * waves ) * 0.25;
p = vec3( cos( angle ) * radius,  y, -sin( angle ) * radius );
return p;
}
``````

1 Like