Morphing between two geometries

Hi everyone! I’m trying to morph between two geometries (one is a box, and one is buffergeometry), that share the same material and mesh, but for some reason can’t manage to find a way to morph them gradually. I’m trying to find resources on how to do that - any advice on how to approach it? :pray:

1 Like

:thinking:
A buffer geometry can have many forms. It can also be a box. You should make your problem a little clearer.


Something like that?
Magic Box example 10, 15, 17
Magic Sphere example 6, 7

https://hofk.de/main/threejs/sandboxthreef/sandboxthreefR90/basic%20examples.html

1 Like

A smooth morph between two shapes requires a mapping for where each individual vertex is going, and usually you’ll want the two shapes to have exactly the same number of vertices. That’s not easy to define in code, at least when starting from two geometry objects not specifically designed for the purpose.

A better approach here would be to load the two shapes in Blender, and bake a morph target as explained in this video:

From there you can export to glTF/GLB and play back the morph target however you want.

2 Likes

Example:

morph_shapes.glb (2.8 KB)

2 Likes

This is really helpful! I suspected that I should work on the morphing in blender and then load the animation in threejs, but I wasn’t sure if I might be missing something. I also found this example three.js examples which is very similar to the example you posted but done with code. But like you said, I guess my problem is that my starting geometries are not designed for this purpose…

For this reason I have created a suitable geometry. For this I use the scheme from my Addon. Produces almost infinite many time-varying geometries with functions .

It is symmetrical and therefore suitable.

See THREEf geometries r136

With a little elementary geometry, the transformation from cube to sphere can be easily achieved.

The uv values are still missing, will be supplied later.


UPDATE:

The ready solution there Morph box sphere geometry (+shader)



<!DOCTYPE html>
<!-- https://discourse.threejs.org/t/morphing-between-two-geometries/31911 -->
<head>
  <title> MorphBoxSphere </title>
  <meta charset="utf-8" />
    <meta charset="utf-8" />
    <style>
    body {
        margin: 0; 
        overflow: hidden;
    }
    </style>
</head>
<body> </body>

<script src="../js/three.min.134.js"></script>
<script src="../js/OrbitControls.134.js"></script>

<script>

// @author hofk

const scene = new THREE.Scene();

const camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.position.set( 0,  0, 10);

const renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( 0xdedede, 1 );
document.body.appendChild( renderer.domElement );

const controls = new THREE.OrbitControls( camera, renderer.domElement );

const g = new THREE.BufferGeometry( );

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

const cs = 8; // count of segments per side

const axesHelper = new THREE.AxesHelper( r );
scene.add( axesHelper );

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 );
const positions = new Float32Array( vertexCount * 3 * 6 );   
//const uvs = new Float32Array( vertexCount * 2 * 6 ); // uvs to vertices	

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

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

//face indices

let idx = 0;
let offs = 0;

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;
            
            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;
            
            idx += 12;
            
        }
    
    }
    
    offs += hvc * cs + css; // + vertex count one side
    
}

const v = t => 0.5 * ( 1 + Math.sin( t ) );

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 );

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;
            
            cube[ idx + ia ] = a0;
            cube[ idx + ib ] = b0;
            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;
            
            diff[ idx + ia ] = a1 - a0;
            diff[ idx + ib ] = b1 - b0;
            diff[ idx + ic ] = c1 - c0;
            
            positions[ idx + ia ] = a0; 
            positions[ idx + ib ] = b0;
            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;
                
                cube[ idx + ia ] = a0;
                cube[ idx + ib ] = b0;
                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;
                
                diff[ idx + ia ] = a1 - a0;
                diff[ idx + ib ] = b1 - b0;
                diff[ idx + ic ] = c1 - c0;
                
                positions[ idx + ia ] = a0;
                positions[ idx + ib ] = b0;
                positions[ idx + ic ] = c0;
                
                idx += 3;
                
            }
            
        }
        
    }
    
}
//console.log( 'cube ', cube) /////////////////////////////////////////////////////////////////////////
//console.log( 'diff ', diff) /////////////////////////////////////////////////////////////////////////
//console.log( positions )///////////////////////////////////////////////////////////////////////

const mat = new THREE.MeshBasicMaterial( { color: 0xff00ff, wireframe: true, side: THREE.DoubleSide  } );
const mesh = new THREE.Mesh( g, mat  );
scene.add( mesh )
 
const clock = new THREE.Clock( );
let t;

animate( );

function animate( ) {
    
    t = clock.getElapsedTime( );

    requestAnimationFrame( animate );
    morph( t );
    renderer.render( scene, camera );
    
}

function morph( t ) {

    idx = 0; // reset
              // ia, ib, ic 
    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 ++ ) {
                
                positions[ idx + ia ] = cube[ idx + ia ] + v( t ) * diff[ idx + ia ]; 
                positions[ idx + ib ] = cube[ idx + ib ] + v( t ) * diff[ idx + ib ];
                positions[ idx + ic ] = cube[ idx + ic ] + v( t ) * diff[ idx + ic ];        
            
                idx += 3;
                
            }
        
            if( j < cs ) {
                
                for ( let i = 0; i < cs; i ++ ) {
                    
                    positions[ idx + ia ] = cube[ idx + ia ] + v( t ) * diff[ idx + ia ]; 
                    positions[ idx + ib ] = cube[ idx + ib ] + v( t ) * diff[ idx + ib ];
                    positions[ idx + ic ] = cube[ idx + ic ] + v( t ) * diff[ idx + ic ];        
                
                    idx += 3;
                
                }
            
            }
            
        }
        
    }
    
}

</script>    
</html>

this is super helpful! thank you

Can u tell me how to access and change the Shrinkwrap value with three.js or r3f ? Tank you :slight_smile:

EDIT

I saw your gltf-viewer is opensource on Github. Fixed it on my own