# Morph box sphere geometry (+shader)

From a question Morphing between two geometries I made a dynamic geometry.

A box is morphed to a sphere and back by any function.

Try it out MorphBoxSphere

``````// g  BufferGeometry, r radius, cs count of segments per side, v  norph function, result 0 .. 1

function CubeSphereGeometry( g, r, cs, v ) {

// const sd = r * 1 / Math.sqrt( 2 ); // 1/2 square diagonal
const ss = r * 1 / Math.sqrt( 3 ); // 1/2 square side

const css = cs + 1;
const hvc = css + cs ;   // height vertex count
const vertexCount = css * css + cs * cs;
const faceCount = cs * cs * 4;

let a0, b0, c0, a1, b1, c1, le;

const indices = new Uint32Array( faceCount * 3 * 6 );
g.positions = new Float32Array( vertexCount * 3 * 6 );
uvs = new Float32Array( vertexCount * 2 * 6 ); // uvs to positions

g.setIndex( new THREE.BufferAttribute( indices, 1 ) );
g.setAttribute( 'position', new THREE.BufferAttribute( g.positions, 3 ).setUsage( THREE.DynamicDrawUsage) );
g.setAttribute( 'uv', new THREE.BufferAttribute( uvs, 2 ) );

g.cube = new Float32Array( vertexCount * 3 * 6 ); // basis positions plane,cube
g.diff = new Float32Array( vertexCount * 3 * 6 ); // difference positions cube sphere

let idx = 0;
let offs = 0;

makeIndices( );

idx = 0; // reset

// ia, ib, ic, sign
makePositions( 0,  1,  2,  1 );
makePositions( 0,  1,  2, -1 );
makePositions( 1,  2,  0,  1 );
makePositions( 1,  2,  0, -1 );
makePositions( 2,  0,  1,  1 );
makePositions( 2,  0,  1, -1 );

idx = 0; // reset

// ia, ib, o1, o2      ia, ib: index    o1, o2: orientation
makeUVs( 0,  1,  1,  1 );
makeUVs( 0,  1, -1,  1 );
makeUVs( 1,  0,  1, -1 );
makeUVs( 1,  0,  1,  1 );
makeUVs( 1,  0,  1, -1 );
makeUVs( 1,  0,  1,  1 );

function makeIndices( ) {

let materialIndex = 0;
let startIdx = 0;
let sign = 1;

for ( let k = 0; k < 6; k ++ ) {

for ( let j = 0; j < cs; j ++ ) {

for ( let i = 0; i < cs; i ++ ) {

// 4 faces / segment
a  = offs + hvc * j + css + i;

b1 = offs + hvc * j + i;                // bottom
c1 = offs + hvc * ( j + 1 ) + i;

b2 = offs + hvc * j + 1 + i;            // left
c2 = offs + hvc * j + i;

b3 = offs + hvc * ( j + 1 ) + i;        // right
c3 = offs + hvc * ( j + 1 ) + 1 + i;

b4 = offs + hvc * ( j + 1 ) + 1 + i;    // top
c4 = offs + hvc * j + 1 + i;

if ( sign === 1 ) {

indices[ idx      ] = a;    // bottom
indices[ idx +  1 ] = b1;
indices[ idx +  2 ] = c1;

indices[ idx +  3 ] = a;    // left
indices[ idx +  4 ] = b2,
indices[ idx +  5 ] = c2;

indices[ idx +  6 ] = a;    // right
indices[ idx +  7 ] = b3;
indices[ idx +  8 ] = c3;

indices[ idx +  9 ] = a;    // top
indices[ idx + 10 ] = b4,
indices[ idx + 11 ] = c4

}

if ( sign === -1 ) {

indices[ idx      ] = a;    // bottom
indices[ idx +  1 ] = c1;
indices[ idx +  2 ] = b1;

indices[ idx +  3 ] = a;    // left
indices[ idx +  4 ] = c2,
indices[ idx +  5 ] = b2;

indices[ idx +  6 ] = a;    // right
indices[ idx +  7 ] = c3;
indices[ idx +  8 ] = b3;

indices[ idx +  9 ] = a;    // top
indices[ idx + 10 ] = c4,
indices[ idx + 11 ] = b4

}

idx += 12;

}

}

g.addGroup ( startIdx,  idx - startIdx, materialIndex );
materialIndex ++;
startIdx = idx;

offs += hvc * cs + css; // + vertex count one side

sign = -sign;

}

}

function makePositions( ia, ib, ic, sign ) {

for ( let j = 0; j < css; j ++ ) {

for ( let i = 0; i < css; i ++ ) {

a0 = -ss + 2 * ss * j / cs;
b0 = -ss + 2 * ss * i / cs;
c0 = sign * ss;

g.cube[ idx + ia ] = a0;
g.cube[ idx + ib ] = b0;
g.cube[ idx + ic ] = c0;

le = Math.sqrt( a0 * a0 + b0 * b0 + c0 * c0 );

a1 = r * a0 / le;
b1 = r * b0 / le;
c1 = r * c0 / le;

g.diff[ idx + ia ] = a1 - a0;
g.diff[ idx + ib ] = b1 - b0;
g.diff[ idx + ic ] = c1 - c0;

g.positions[ idx + ia ] = a0;
g.positions[ idx + ib ] = b0;
g.positions[ idx + ic ] = c0;

idx += 3;

}

if( j < cs ) {

for ( let i = 0; i < cs; i ++ ) {

a0 = -ss + 2 * ss * ( j + 0.5 ) / cs;
b0 = -ss + 2 * ss * ( i + 0.5 ) / cs;
c0 = sign * ss;

g.cube[ idx + ia ] = a0;
g.cube[ idx + ib ] = b0;
g.cube[ idx + ic ] = c0;

le = Math.sqrt( a0 * a0 + b0 * b0 + c0 * c0 );

a1 = r * a0 / le;
b1 = r * b0 / le;
c1 = r * c0 / le;

g.diff[ idx + ia ] = a1 - a0;
g.diff[ idx + ib ] = b1 - b0;
g.diff[ idx + ic ] = c1 - c0;

g.positions[ idx + ia ] = a0;
g.positions[ idx + ib ] = b0;
g.positions[ idx + ic ] = c0;

idx += 3;

}

}

}

}

function makeUVs( ia, ib, o1, o2 ) {

for ( let j = 0; j < css; j ++ ) {

for ( let i = 0; i < css; i ++ ) {

uvs[ idx + ia ] = o1 === 1 ? j / cs : 1 - j / cs;
uvs[ idx + ib ] = o2 === 1 ? i / cs : 1 - i / cs;

idx += 2;

}

if( j < cs ) {

for ( let i = 0; i < cs; i ++ ) {

uvs[ idx + ia ] = o1 === 1 ? ( j + 0.5 ) / cs : 1 - ( j + 0.5 ) / cs;
uvs[ idx + ib ] = o2 === 1 ? ( i + 0.5 ) / cs : 1 - ( i + 0.5 ) / cs;

idx += 2;

}

}

}

}

g.morph = function morph( t ) {

idx = 0; // reset
// ia, ib, ic   ia, ib, ic : index
setPositions( 0,  1,  2 );
setPositions( 0,  1,  2 );
setPositions( 1,  2,  0 );
setPositions( 1,  2,  0 );
setPositions( 2,  0,  1 );
setPositions( 2,  0,  1 );

g.attributes.position.needsUpdate = true;// to change the positions of the vertices

function setPositions( ia, ib, ic ) {

for ( let j = 0; j < css; j ++ ) {

for ( let i = 0; i < css; i ++ ) {

g.positions[ idx + ia ] = g.cube[ idx + ia ] + v( t ) * g.diff[ idx + ia ];
g.positions[ idx + ib ] = g.cube[ idx + ib ] + v( t ) * g.diff[ idx + ib ];
g.positions[ idx + ic ] = g.cube[ idx + ic ] + v( t ) * g.diff[ idx + ic ];

idx += 3;

}

if( j < cs ) {

for ( let i = 0; i < cs; i ++ ) {

g.positions[ idx + ia ] = g.cube[ idx + ia ] + v( t ) * g.diff[ idx + ia ];
g.positions[ idx + ib ] = g.cube[ idx + ib ] + v( t ) * g.diff[ idx + ib ];
g.positions[ idx + ic ] = g.cube[ idx + ic ] + v( t ) * g.diff[ idx + ic ];

idx += 3;

}

}

}

}

}

}
``````
6 Likes

I like the grid Built the same here: https://jsfiddle.net/prisoner849/05gvwt1j/

oh wow!! this is so awesome!!

Since I’ve recently started looking a bit more closely at shaders, I created this dynamic geometry in that way as well. I used THREE.BoxBufferGeometry to do this.

Essential is
`const a = 2 / Math.sqrt( 3 ); // means r === 1, important for: normalize( transformed ) - transformed`

` transformed += vec3( 0.5 * ( 1.0 + sin( u_time ) ) * ( normalize( transformed ) - transformed ) );`

Create any radii with `mesh.scale.set( , , ); ` .

For a better comparison I changed the original version to modules and added stats.
With a large number of vertices, the shader version is significantly more performant.
However, a different grid is also used.

``````<!DOCTYPE html>
<!-- https://discourse.threejs.org/t/morph-box-sphere-geometry/31986 -->
<meta charset="utf-8" />
<style>
body { 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( 60, window.innerWidth / window.innerHeight, 0.1, 100 );
camera.position.set( 0, 0, 10 );
const renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( 0xdedede, 1 );
const container = document.createElement( 'div' );
document.body.appendChild( container );
container.appendChild( renderer.domElement );

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

scene.add( new THREE.AxesHelper( 2 ) );
//scene.add( new THREE.GridHelper( 2, 20 ) );

const a = 2 / Math.sqrt( 3 ); // means r === 1, important for: normalize( transformed ) - transformed

const g = new THREE.BoxBufferGeometry( a, a, a, 36, 36, 36 );

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

const sPart = shader => {
uniform float u_time;
`.replace(
`#include <begin_vertex>`,
`#include <begin_vertex>

transformed += vec3( 0.5 * ( 1.0 + sin(  u_time  ) ) * ( normalize( transformed ) - transformed ) );
`
);
}

const uvMaterial = new THREE.MeshBasicMaterial( { onBeforeCompile: sPart, map: uvMap,  wireframe: false } );
const uvMesh = new THREE.Mesh( g, uvMaterial );
uvMesh.position.x = 4;
uvMesh.scale.set( 2, 2, 2 );

const basicMaterial = new THREE.MeshBasicMaterial( { onBeforeCompile: sPart, color: 0xff00ff, wireframe: true, side: THREE.DoubleSide } );
const basicMesh = new THREE.Mesh( g, basicMaterial );
basicMesh.scale.set( 2, 2, 2 );

const diceMaps = [
]

let diceMaterial = [];

for ( let i = 0; i < 6; i ++ ) {

diceMaterial.push( new THREE.MeshBasicMaterial( { onBeforeCompile: sPart, map: diceMaps[ i ], wireframe: false } ) );

}

const diceMesh = new THREE.Mesh( g, diceMaterial );
diceMesh.position.x = -4;
diceMesh.scale.set( 2, 2, 2 );

animate();

function animate( ) {

requestAnimationFrame( animate );
uniforms.u_time.value += 0.2;
renderer.render( scene, camera );

stats.update( );

}

</script>
</html>
``````
1 Like

I used mixing between positions of box and computed positions of sphere: Edit fiddle - JSFiddle - Code Playground
So, basically, it’s just:

``````        vec3 boxPos = position;
vec3 spherePos = normalize(position) * radius;
transformed = mix(boxPos, spherePos, aVal); // aVal is a uniform, in range 0..1
``````

Interesting
`let radius =... // sphere of the same volume`

I had the goal of inflating the cube. Then the 8 corners stay in place.

Ah, then the radius is `Math.sqrt(3) * size * 0.5`, a half or the cube’s diagonal

This is the formula.

Maybe you can just get this into the shader and then you can choose the radius as you like.

Pass the cube’s size in a uniform and then compute the radius in shaders:

``````uniform float cubeSize;
...
float radius = sqrt(3.) * cubeSize * 0.5;
vec3 spherePos = normalize(position) * radius;
``````

But, I think, it would be better to compute the radius once on js side and then pass it in a uniform.

``````const r = 1.5; // radius of the sphere
const a = r * 2 / Math.sqrt( 3 ); //  size of the box, corners on sphere

const g = new THREE.BoxBufferGeometry( a, a, a, 36, 36, 36 );
``````

``````    transformed /= u_radius;
transformed += vec3( 0.5 * ( 1.0 + sin(  u_time  ) ) * ( normalize( transformed ) - transformed ) );
``````

``````<!DOCTYPE html>
<!-- https://discourse.threejs.org/t/morph-box-sphere-geometry/31986/10 -->
<meta charset="utf-8" />
<style>
body { 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( 60, window.innerWidth / window.innerHeight, 0.1, 100 );
camera.position.set( 0, 0, 7 );
const renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( 0xdedede, 1 );
const container = document.createElement( 'div' );
document.body.appendChild( container );
container.appendChild( renderer.domElement );

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

scene.add( new THREE.AxesHelper( 1.5 ) );

const r = 1.5; // radius of the sphere
const a = r * 2 / Math.sqrt( 3 ); //  size of the box, corners on sphere

const g = new THREE.BoxBufferGeometry( a, a, a, 36, 36, 36 );

const uniforms = { u_time: { value: 0.0 }, u_radius: { value: r } }

const sPart = shader => {
uniform float u_time;
`.replace(
`#include <begin_vertex>`,
`#include <begin_vertex>
transformed += vec3( 0.5 * ( 1.0 + sin(  u_time  ) ) * ( normalize( transformed ) - transformed ) );
`
);
}

const uvMaterial = new THREE.MeshBasicMaterial( { onBeforeCompile: sPart, map: uvMap, wireframe: false } );
const uvMesh = new THREE.Mesh( g, uvMaterial );
uvMesh.position.x = 3;

const basicMaterial = new THREE.MeshBasicMaterial( { onBeforeCompile: sPart, color: 0xff00ff, wireframe: true, side: THREE.DoubleSide } );
const basicMesh = new THREE.Mesh( g, basicMaterial );

const diceMaps = [
]

let diceMaterial = [];

for ( let i = 0; i < 6; i ++ ) {

diceMaterial.push( new THREE.MeshBasicMaterial( { onBeforeCompile: sPart, map: diceMaps[ i ], wireframe: false } ) );

}

const diceMesh = new THREE.Mesh( g, diceMaterial );
diceMesh.position.x = -3;

animate();

function animate( ) {

requestAnimationFrame( animate );
uniforms.u_time.value += 0.02;
renderer.render( scene, camera );

stats.update();
}

</script>
</html>
``````
1 Like

Since I haven’t been working with shaders for very long, I only noticed now that you can optimize.

The onBeforeCompile parts are identical, you can bring them into a function. Code changed above.

``````
const sPart = shader => {
uniform float u_time;
`.replace(
`#include <begin_vertex>`,
`#include <begin_vertex>

transformed += vec3( 0.5 * ( 1.0 + sin(  u_time  ) ) * ( normalize( transformed ) - transformed ) );
`
);
}
``````

and

``````const sPart = shader => {