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?
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. ![]()
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.
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>
