End Caps of TubeGeometry

I have an arbitrary tubeGeometry made from a curved path.
How can I add End Caps to it.
Don’t want hollow tube.

It’s not possible to apply end caps, you can only close a tube like demonstrated in this example:

https://threejs.org/examples/webgl_geometry_extrude_splines

Check/uncheck the “closed” checkbox to see the different.

Do you mean something like that? (example 0.4 * Math.sin( Math.PI * 2 * v ))

You can do things like that with my addon THREEf. Addon. Produces almost infinite many time-varying geometries with functions

hofk (Klaus Hoffmeister) · GitHub

examples https://hofk.de/main/threejs/sandboxthreef/examplesTHREEf%20r90.html

https://hofk.de/main/threejs/sandboxthreef/formLibrary.html
e.g.

//0011 curved cylinder  @author hofk 
 withBottom:	true,
 moveX:		function ( u, v, t ) { return Math.exp( -200 * v * v * v * v ) }

curved%20_cylinder

How to find the correct mathematical formula

How to get Starting and Ending Cap Vertices (Blue) to make a shape (like Red) out of them and use the shape as End Caps?

tube_endcap_01

Not so difficult, actually.
You know amount of radial segments, you know amount of tubular segments, so you can get information about vertices of start and end segments:
TubePoints
https://jsfiddle.net/prisoner849/6jp3snvq/

Another thing you’ll need are points for start and end of the curve.
Something like:

var curve = _your_curve_;
var pointStart = curve.getPoint(0);
var pointEnd = curve.getPoint(1);

TubePointsWithCenters
https://jsfiddle.net/prisoner849/25mLpg3r/

The last thing is to set .index for geometries so you’ll have faces:
TubePointsWithCentersAndEndCaps
https://jsfiddle.net/prisoner849/yueLpdb2/

Or you can set index without setting that mid point, using just points of the tube’s ends :slight_smile: Creativity is up to you :blush:

2 Likes

Your code makes new meshes for end caps. how to make end caps part of the tube?

The code creates geometries. I use meshes just to visualize the result.
Use THREE.BufferGeometryUtils.mergeBufferGeometries() to make caps part of the tube.

I extended it with a convex geometry to cover the tip

is it possible and would you know how to close the gap between theta start and theta length if it is not a complete cylinder by any chance?

heres a visual reference to what i mean…

image

where the orange outlines would make up “solid” faces?

@Lawrence3DPK
Somehow your question is similar to these topic and post: Group And Animate Meshes - #8 by prisoner849

Agreed it is, i used the image of the cylinder for simplicity sake, i actually intend to try and use the same principle for a sphere with less phi length, like so…

image

which i guess has the same principle but i can only imagine a lot more difficult to work out, do you know if someone has achived this before?

1 Like

I have done something like this in my Addon. Produces almost infinite many time-varying geometries with functions - #24 by hofk.

2021-04-08 20.07.07

However, the code is not easy to keep track of.

yeah, was just looking into the link you sent, thanks for the link man, gonna give it a shot but honestly it doesn’t look easy aha.

The thing itself is not so complicated, the problem is the embedding in the complex addon.

I myself have problems when I want to extract a piece from one of my addons for a simple thing.

Look for wedges.

To build such a sphere, you need one sphere and two half-circles.
Example: https://jsfiddle.net/prisoner849/cLqrghdm/

Picture:

Code:

let r = 2.5;
let wS = 16;
let hS = 8;
let phiStart = Math.PI;
let phiLength = Math.PI * 1.35;

let gSphere = new THREE.SphereGeometry(r, wS, hS, phiStart, phiLength);
let gCircle1 = new THREE.CircleGeometry(r, hS, Math.PI * 1.5, Math.PI);
gCircle1.rotateY(Math.PI + phiStart);
let gCircle2 = new THREE.CircleGeometry(r, hS, Math.PI * 0.5, Math.PI);
gCircle2.rotateY(phiStart + phiLength);
let g = BufferGeometryUtils.mergeBufferGeometries([gSphere, gCircle1, gCircle2]);

let m = new THREE.MeshPhongMaterial({color: 0x007fff, flatShading: true});

let o = new THREE.Mesh(g, m);
scene.add(o);
1 Like

@prisoner849 Ahh man!! no way!! that’s really cool of you! i was going off of your example from yesterday and going the ultra long route by creating BufferGeometry from points, i got 2 faces together last night and it was proving to be a very long task…

    let sg = new THREE.BufferGeometry().setFromPoints(
    [
    new THREE.Vector3(0, cH, 0),
    new THREE.Vector3(-1+cH*0.8125,1-cH*0.015625, 0),//.applyAxisAngle(new THREE.Vector3(0, 1, 0), perc1 * Math.PI /180),
    new THREE.Vector3(0, 0, 0),
    new THREE.Vector3(-1+cH*0.8125,1-cH*0.015625, 0),//.applyAxisAngle(new THREE.Vector3(0, 1, 0), perc1 * Math.PI /180),
    new THREE.Vector3(-1+cH*0.609375,1-cH*0.078125, 0),//.applyAxisAngle(new THREE.Vector3(0, 1, 0), perc1 * Math.PI /180),
    new THREE.Vector3(0, 0, 0)
    // new THREE.Vector3(0, cH * 0.3125, 0.1875),
    // new THREE.Vector3(0, cH * 0.25, 0.25),
    // new THREE.Vector3(0, cH * 0.1875, 0.3125),
    // new THREE.Vector3(0, cH * 0.125, 0.375),
    // new THREE.Vector3(0, cH * 0.0625, 0.4375),
    // new THREE.Vector3(0, cH * 0, ch * 0.5),
    // new THREE.Vector3(0, cH * -0.0625, 0.4375),
    // new THREE.Vector3(0, cH * -0.125, 0.375),
    // new THREE.Vector3(0, cH * -0.1875, 0.3125),
    // new THREE.Vector3(0, cH * -0.25, 0.25),
    // new THREE.Vector3(0, cH * -0.3125, 0.1875),
    // new THREE.Vector3(0, cH * -0.375, 0.125),
    // new THREE.Vector3(0, cH * -0.4375, 0.0625),
    //new THREE.Vector3(0, cH * -0.5, 0)
  ]
);
sg.addGroup(0, 3, 0);
sg.setIndex([0, 1, 2, 3, 4, 5]);
sg.setAttribute("uv", new THREE.Float32BufferAttribute([
    0, 1, 0, 0, 1, 0
], 2));
sg.computeVertexNormals();

as you can see i barely got to creating points for the third face and ran into quite a complexity of number calculations…

the approach you’ve provided should be a little simpler aha!! i will give it a go and let you know!!

Thanks so much man!!

@prisoner849 Worked a charm!! you’re work is amazing so i really appreciate your help with such a simple mundane task!!

1 Like

@Lawrence3DPK you’re welcome :beers:

2 Likes

Hi everyone,

I know this is an old question but I am struggling with this for a while now and need some advice or suggestion to get unstuck. I am using r160 and I am a beginner at three.js.

I implemented end caps for a tube geometry using a solution posted by @prisoner849 in a jsfiddle back in September '19, and it’s working nicely. Now, I want to merge the end caps into the tube geometry using BufferGeometryUtils.mergeGeometries(), but I’m encountering an error:

BufferGeometryUtils.js:160 THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index 1. Make sure all geometries have the same number of attributes.
mergeGeometries @ BufferGeometryUtils.js:160
addObject @ example.html:119
(anonymous) @ example.html:150
three.module.js:11634 Uncaught TypeError: Cannot read properties of null (reading 'morphAttributes')
    at Mesh.updateMorphTargets (three.module.js:11634:36)
    at new Mesh (three.module.js:11603:8)
    at addObject (example.html:125:30)
    at example.html:150:5

It seems there’s an issue with the number of attributes, and I am currently clueless about it. I have attached my current code below:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Three.js Example</title>
    <style>
        body { margin: 0; }
    </style>
        <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>
        <script type="importmap">
          {
            "imports": {
              "three": "https://unpkg.com/three@v0.160.0/build/three.module.js",
              "three/addons/": "https://unpkg.com/three@v0.160.0/examples/jsm/"
            }
          }
        </script>
</head>
<body>

<script type="module">
    import * as THREE from 'three';
    import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
    import { TransformControls } from 'three/addons/controls/TransformControls.js';
    import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';

    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
    const renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

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

        function addObject(position) {

            var curve = new THREE.CatmullRomCurve3([
                new THREE.Vector3(0.4, 0.4, 0),
                new THREE.Vector3(0, 0.4, 0),
                new THREE.Vector3(0, 0, 0)
            ]);

            var geom = new THREE.TubeGeometry(curve, 10, 0.1, 10, false);
            var mat = new THREE.MeshLambertMaterial({ color: Math.random() * 0xffffff });
            const object = new THREE.Mesh(geom, mat);

            var pos = geom.attributes.position;
            var startPoints = [];
            startPoints.push(curve.getPoint(0));
            for (let i = 0; i <= geom.parameters.radialSegments; i++) {

                startPoints.push(new THREE.Vector3().fromBufferAttribute(pos, i));

            }

            var pointsStartGeom = new THREE.BufferGeometry().setFromPoints(startPoints);
            var psgPos = pointsStartGeom.attributes.position;
            var indexStart = [];
            for (let i = 1; i < psgPos.count - 1; i++) {
                indexStart.push(0, i, i + 1);
            }
            pointsStartGeom.setIndex(indexStart);

            var shapeStart = new THREE.Mesh(pointsStartGeom, mat); 

            var endPoints = [];
            endPoints.push(curve.getPoint(1));
            for (let i = (geom.parameters.radialSegments + 1) * geom.parameters.tubularSegments; i < pos.count; i++) {

                endPoints.push(new THREE.Vector3().fromBufferAttribute(pos, i));

            }

            var pointsEndGeom = new THREE.BufferGeometry().setFromPoints(endPoints);
            var pegPos = pointsEndGeom.attributes.position;
            var indexEnd = [];
            for (let i = 1; i < pegPos.count - 1; i++) {
                indexEnd.push(0, i + 1, i);
            }
            pointsEndGeom.setIndex(indexEnd);

            var shapeEnd = new THREE.Mesh(pointsEndGeom, mat); 

            object.position.y = 0.0;

            if (position) {
                object.position.copy(position);
            } else {

                object.position.x = Math.random() * 10 - 5;
                object.position.z = Math.random() * 10 - 5;
            }

            shapeStart.position.copy(object.position);

            shapeEnd.position.copy(object.position);

            const mergedGeometry = BufferGeometryUtils.mergeGeometries([object.geometry, shapeStart.geometry, shapeEnd.geometry]);

            const tubeMaterial = new THREE.MeshLambertMaterial({ color: Math.random() * 0xffffff });

            const tubeMesh = new THREE.Mesh(mergedGeometry, tubeMaterial);

            tubeMesh.castShadow = true;
            tubeMesh.receiveShadow = true;
            scene.add(tubeMesh);
            return tubeMesh;
        }

    const light = new THREE.PointLight(0xFFFFFF, 1, 100);
    light.position.set(0, 5, 5);
    scene.add(light);

    camera.position.z = 5;

    function animate() {
        requestAnimationFrame(animate);
        controls.update();
        renderer.render(scene, camera);
    }

    addObject(new THREE.Vector3(0, 0, 0));

    animate();
</script>

</body>
</html>

Thank you in advance for any help or guidance!