How to update a TubeGeometry with a new CubicBezierCurve3?

Hi… I’ve tried to create a 3JS TubeGeometry that has a CubicBezier3 curve, and then update it (in every frame) to a new CubicBezier3 curve without success, and I’ve also searched for recent applicable code, also without success. Can anyone help?… thank you! Martin

Do you mean something like this?

From the Collection of examples from discourse.threejs.org

DynamicTubeGeometryCaps

Hi Klaus… yes!… exactly what I need! I looked through the code for quite a long time, and found that it’s too complex for me. I’m still learning. Is there a to get a simple solution… a tube, and change its shape tow ar three times? Maybe there isn’t such a thing. I’m writing a physics simulation- I’ve written several that have been well received, and I’m learning new skills with each one. Thank you for your help. The generosity of this forum is tremendous. Martin.

I can remove everything that is not essential to the core of the matter. And then comment on the remaining lines in detail. But it will take some time—it’s the pre-Christmas period. :slightly_smiling_face:

That’s exactly what the program does. Instead of calling the internal three.js TubeGeometry, copy my
function DynamicTubeGeometry( radius, radialSegments, heightSegments, withTop, withBottom )
into your program and then call it to define a specific tube with your parameters. Then you create and modify the tube using the method .morph( points )

Your problem is obviously that, as a beginner, you tried to fully understand the creation of DynamicTubeGeometry and it overwhelmed you - that’s normal. But you don’t have to understand the construction completely, you just have to use it for your requirements with the appropriate parameters.

Have you looked at the definition of the built-in TubeGeometry from three.js? three.js/src/geometries/TubeGeometry.js at a58e9ecf225b50e4a28a934442e854878bc2a959 · mrdoob/three.js · GitHub and there it uses computeFrenetFrames three.js/src/extras/core/Curve.js at a58e9ecf225b50e4a28a934442e854878bc2a959 · mrdoob/three.js · GitHub (from line 338 )

With the correct parameters, you can use them just as easily as my definition without fully understanding their content.

Three.js itself only contains simple basic geometries.
To better understand the construction, you can use my helper from the collection.

NumberingHelperExamples

I have made a number of more complex geometries available on my GitHub (hofk (Klaus Hoffmeister) · GitHub.

And here is the minimalist function with comments for copying.



<!DOCTYPE html>
<!-- https://discourse.threejs.org/t/how-to-update-tubegeometry-geometry-based-in-linecurve-modifications/40854/14 -->
<!-- see also  https://hofk.de/main/discourse.threejs/2022/DynamicTubeGeometry/DynamicTubeGeometry.html  -  ... /40854/8 -->
<head>
	<title> DynamicTubeGeometryCaps </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.180.js";   // updated to r180
import { OrbitControls } from "../jsm/OrbitControls.180.js"; // updated to r180
 
const scene = new THREE.Scene( );
const camera = new THREE.PerspectiveCamera( 55, innerWidth / innerHeight, 0.01, 1000 );
camera.position.set( -10, 8, 16 );
const renderer = new THREE.WebGLRenderer( );
renderer.setSize( innerWidth, innerHeight );
renderer.setClearColor( 0xdedede );
document.body.appendChild(renderer.domElement);

const light = new THREE.AmbientLight( 0x404040, 1.2 ); // soft white light
scene.add( light );

const pointLight1 = new THREE.PointLight( 0xffffff, 1.1 );
pointLight1.position.set( -2, 10, 5 );
scene.add( pointLight1 );

new  OrbitControls( camera, renderer.domElement );

scene.add( new THREE.GridHelper( 20, 20 ) );
 
const materials = [
  new THREE.MeshBasicMaterial( { side: THREE.FrontSide, map: new THREE.TextureLoader( ).load( 'uv_grid_opengl.jpg' ), wireframe: true } ),
  new THREE.MeshBasicMaterial( { side: THREE.DoubleSide, map: new THREE.TextureLoader( ).load( 'sunflower.png' ) } ),
  new THREE.MeshBasicMaterial( { side: THREE.FrontSide, map: new THREE.TextureLoader( ).load( 'uv_grid_opengl.jpg' ), wireframe: false } ),
]

//                               radius, radialSegments, heightSegments, withTop, withBottom 
const ctGeo = DynamicTubeGeometry( 0.4, 8, 36, true, true ); // a custom geometry (tube with caps)
const ctMesh =  new THREE.Mesh( ctGeo, materials );
scene.add ( ctMesh );

// Some points to create a 3D curve.
let points = [ 
    new THREE.Vector3( -5, 0, 1 ),
    new THREE.Vector3( -2, 2, 0 ),
    new THREE.Vector3(  0, 3, 2 ), 
    new THREE.Vector3(  3, 1, 3 )
];

console.log('points: ', points ); 

ctGeo.morph( points ); //Call the geometry method that sets the positions of the geometry based on the 3D curve.
 
let t = 0;

if ( t === 0 ) console.log ('ctGeo positions ', ctGeo.attributes.position.array ); // start positions
animate( );

// ---------------------------------

function animate( ) {
     
	requestAnimationFrame( animate );
    
    t += 0.05;
    //Move the support points of the curve depending on the time.
    points[ 0 ].y = 0.2 * Math.sin( t );
    points[ 1 ].z = 0.4 * Math.cos( t );
    points[ 3 ].z = 0.2 * Math.cos( t );
    
    ctGeo.morph( points ); // recalculate the positions of the geometry
	renderer.render( scene, camera );
	
}

/** 
  A custom geometry, since the simple TubeGeometry from three.js 
* ( see https://threejs.org/docs/?q=tube#TubeGeometry,
*       source code:  https://github.com/mrdoob/three.js/blob/a58e9ecf225b50e4a28a934442e854878bc2a959/src/geometries/TubeGeometry.js )
*  is not particularly suitable for this purpose.
*/

function DynamicTubeGeometry( radius, radialSegments, heightSegments, withTop, withBottom ) {

	const g = new THREE.BufferGeometry( );
    
    g.radius = radius;
    g.heightSegments = heightSegments;
    g.radialSegments = radialSegments;
    
	let indices = [];
	let uvs = [];
	
	let index = 0;
	let indexArray = [];
	let groupStart = 0; 
	
	let groupCount = 0;  //  for multi material support (material groups)
    
	// Calculation of indices and UV values
	for ( let y = 0; y <= heightSegments; y ++ ) {
		
		let indexRow = [];
		
		let v = y / heightSegments;

		for ( let x = 0; x <= radialSegments; x ++ ) {
			
			uvs.push( x / radialSegments, 1 - v );
			indexRow.push( index ++ );
			
		}
        
		indexArray.push( indexRow );
		
	}
	
	let a, b, c, d;
	// This is similar but differs from the three.js definition. 
	for ( let i = 0; i < radialSegments; i ++ ) {
		
		for ( let j = 0; j < heightSegments; j ++ ) {
			
			a = indexArray[ j ][ i ];
			b = indexArray[ j + 1 ] [ i ];
			c = indexArray[ j + 1 ][ i + 1 ];
			d = indexArray[ j ] [ i + 1 ];
			
			indices.push( a, b, d );
			indices.push( b, c, d );
			
			groupCount += 6;
			
		}
		
	}
	
	g.addGroup( groupStart, groupCount, 0 );
	
	groupStart += groupCount;
	
	let verticesCount = ( radialSegments + 1 ) * ( heightSegments + 1 )
	
    let centerIndexTop, centerIndexBottom;
    
    
    // The three.js version does not have caps.
	if ( withTop ) {
		
		let groupCount = 0;
		
		uvs.push( 0.5, 0.5 );
		
		centerIndexTop = index;
        const c = centerIndexTop;
        
		for ( let x = 1; x <= radialSegments; x ++ ) {
            
			const i = centerIndexTop + x;

            indices.push( i, i + 1, c );

			groupCount += 3;
            
            index ++;
			
		}
        
		g.addGroup( groupStart, groupCount, 1 );  // 1 top material
		
		groupStart += groupCount;
		
		verticesCount += radialSegments + 2; // with center
        
        for ( let x = 0; x <= radialSegments; x ++ ) {
            
            uvs.push( 0.5 * ( 1 + Math.sin( x / radialSegments * Math.PI * 2 ) ) );
            uvs.push( 1 - 0.5 * ( 1 + Math.cos( x / radialSegments * Math.PI * 2 ) ) );
             
		}
        
        index ++; // for center top
        
	}
       
	if ( withBottom ) {
		
		let groupCount = 0;
		
		uvs.push( 0.5, 0.5 );

		centerIndexBottom = ++ index; 
        
		const c = centerIndexBottom;
        
		for ( let x = 1; x <= radialSegments; x ++ ) {
			
			const i = centerIndexBottom + x;
            
            indices.push( i + 1, i, c );
       
			groupCount += 3;

           index ++;
           
		}
		
		g.addGroup( groupStart, groupCount, 2 );  // 2 bottom material
		
		groupStart += groupCount;
		
		verticesCount += radialSegments + 2; // with center
        
        for ( let x = 0; x <= radialSegments; x ++ ) {
            
            uvs.push( 1 - 0.5 * ( 1 + Math.sin( x / radialSegments * Math.PI * 2 ) ) );
            uvs.push( 1 - 0.5 * ( 1 + Math.cos( x / radialSegments * Math.PI * 2 ) ) );
            
        }
        
	}    
  
	g.setIndex( new THREE.BufferAttribute( new Uint32Array( indices ), 1 ) );
	g.setAttribute( 'position', new THREE.BufferAttribute( new Float32Array( verticesCount * 3 ), 3 ) );
	g.setAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( uvs ), 2 ) );
    // Up to this point, only the general structure of the vertices and triangles has been created using the indices,
    // no 3D positions have been set yet.
    
    //...............................................................................................................
    
    /**
    * With this method, the tube can first be shaped and later updated by recalculating the Catmull-Rom curve.
    * The vertices are rearranged in a circle around the curve.
    * A similar calculation is performed in the three.js version with ... .computeFrenetFrames( tubularSegments,  ...
    * and some detail functions, see e.g. const N = frames.normals[ i ]; const B = frames.binormals[ i ];
    */
    g.morph = function( points ) { // sets the coordinate of all vertices
        
        g.pts = new THREE.CatmullRomCurve3( points , false ).getSpacedPoints( g.heightSegments ); // new center points
             
        // tangent( direction),  normal, binormal, shape in space
        
        let v3a = new THREE.Vector3( ); 
        let v3b = new THREE.Vector3( );
        
        let tangent = new THREE.Vector3( );	
        let normal = new THREE.Vector3( 0, 0, -1 ); // first normal to after ... 
        let binormal = new THREE.Vector3( );
        
        let idx = 0;
        
        for( let i = 0; i <= g.heightSegments; i ++ ) {
            
            if ( i === 0 ) tangent.subVectors( g.pts[ 1 ], g.pts[ 0 ] );
            if ( i > 0 && i < g.heightSegments ) tangent.subVectors( g.pts[ i + 1 ], g.pts[ i - 1 ] );
            if ( i === g.heightSegments ) tangent.subVectors( g.pts[ i ], g.pts[ i - 1 ] );
            
            binormal.crossVectors( normal, tangent );
            normal.crossVectors( tangent, binormal );
            
            binormal.normalize( );
            normal.normalize( );
                
            for( let j = 0; j <= g.radialSegments; j ++ ) {
            
                // circle in space
                v3a.addVectors( binormal.clone( ).multiplyScalar( Math.sin( Math.PI * 2 * j / g.radialSegments ) ), normal.clone( ).multiplyScalar(  Math.cos( Math.PI * 2 * j / g.radialSegments ) ) );
                
                v3a.multiplyScalar( g.radius );
                
                v3b.addVectors( g.pts[ i ], v3a );
                
                g.attributes.position.setXYZ( idx ++, v3b.x, v3b.y, v3b.z );
                
            }
            
        }
        
        idx --; // idx = ( g.radialSegments + 1 ) * ( g.heightSegments + 1 ) - 1; // last index torso
        
        const lastIndexTorso = idx;
        
        if( withTop ) {
            
            let x, y, z;
            
            g.attributes.position.setXYZ( ++ idx, g.pts[ 0 ].x, g.pts[ 0 ].y, g.pts[ 0 ].z ); // center top
            
            for( let j = 0; j <= g.radialSegments ; j ++ ) {
                
                x = g.attributes.position.getX( j );
                y = g.attributes.position.getY( j );
                z = g.attributes.position.getZ( j );
                
                g.attributes.position.setXYZ( ++ idx, x, y, z );
                
            }
            
        }
        
        if( withBottom ) {
            
            let x, y, z;
            
            g.attributes.position.setXYZ( ++ idx, g.pts[ g.heightSegments ].x, g.pts[ g.heightSegments ].y, g.pts[ g.heightSegments ].z ); // center bottom
            
            centerIndexBottom = idx;
            
            const idxBtm = lastIndexTorso - g.radialSegments;
            
            for( let j = 0; j <= g.radialSegments ; j ++ ) {
                
                x = g.attributes.position.getX( idxBtm + j );
                y = g.attributes.position.getY( idxBtm + j );
                z = g.attributes.position.getZ( idxBtm + j );
                 
                g.attributes.position.setXYZ( ++ idx, x, y, z );
                
            }
            
        }
        
        g.attributes.position.needsUpdate = true;
        g.computeVertexNormals( );
        
    }
     
    return g;
	
}
 
</script>
 
</html>