How to create 3D tube geometry from 2D shape

Hi i have 2d shapes that i want to turn into a 3d tube

I have this shape which should look something like this:

const testShape = new THREE.Shape();

testShape.autoClose = false;

const x = 0;
const y = 0;
const radius = 10;
const width = 50;
const height = 50;

testShape.moveTo(x, y + radius);
testShape.lineTo(x, y + height - radius);
testShape.quadraticCurveTo(x, y + height, x + radius, y + height);
testShape.lineTo(x + width - radius, y + height);
testShape.quadraticCurveTo(x + width, y + height, x + width, y + height - radius);
testShape.lineTo(x + width, y + radius);
testShape.quadraticCurveTo(x + width, y, x + width - radius, y);
testShape.lineTo(x + radius, y);

How do i turn this into a 3D tube that is built along its path?

I tried getting the points along the shape and converting them to Vector3D but as you can see the shapes has these weird pinch points

const arcPoints = testShape.getPoints();
        const lines: THREE.LineCurve3[] = [];

        for (let i = 0; i < arcPoints.length; i += 2)
        {
            const pointA = arcPoints[i];
            const pointB = arcPoints[i + 1] || pointA;

            lines.push(
                new THREE.LineCurve3(
                    new THREE.Vector3(pointA.x, pointA.y, 0),
                    new THREE.Vector3(pointB.x, pointB.y, 0),
                ),
            );
        }

        const CurvePath = new THREE.CurvePath();

        CurvePath.curves.push(...lines);

        const extrudeSettings = {
            steps: 200,
            bevelEnabled: false,
            extrudePath: CurvePath
        }

        const pts1 = [];
        const count = 300;

        // create a circle of points
        for (let i = 0; i < count; i++)
        {
            const l = 1;
            const a = 2 * i / count * Math.PI;

            pts1.push(new THREE.Vector2(Math.cos(a) * l, Math.sin(a) * l));
        }

        // extruded shape
        const shape = new THREE.Shape(pts1);
        const geometry = new THREE.ExtrudeBufferGeometry(shape, extrudeSettings);
        const mesh = new THREE.Mesh(geometry, new THREE.MeshPhongMaterial({ color: 0xdedede, shininess: 150 }));

Try it with TubeGeometry :https://jsfiddle.net/uaghx80y/

Sidenote: When using TubeGeometry, you always have to input a 3D path. If the path is defined in 2D , you have to convert the data to 3D first.

To convert a 2D curve into a 3D curve, rather than defining a 3D curve as a list of lines, it would be better to define a curve that dynamically samples from the 2D curve.

This should be more precise, as it will avoid quantizing the curve (before constructing the tube geometry), and more efficient, as it will involve less object instantiation.

I’ve created a sample implementation based on one of the built in Curve classes.

Demo: Edit fiddle - JSFiddle - Code Playground

import { Curve, Vector2, Vector3 } from 'three';

/**
 * A 3D curve that is defined by a 2D curve.
 *
 * ```js
 * const baseCurve = new THREE.EllipseCurve( 0, 0, 10, 5, 0, Math.PI * 2, false, 0 );
 * const curve = new THREE.PlanarCurve3( baseCurve );
 *
 * const points = curve.getPoints( 50 );
 * const geometry = new THREE.BufferGeometry().setFromPoints( points );
 *
 * const material = new THREE.LineBasicMaterial( { color: 0xff0000 } );
 *
 * // Create the final object to add to the scene
 * const object = new THREE.Line( geometry, material );
 * ```
 *
 * @augments Curve
 */
class PlanarCurve3 extends Curve {

	/**
	 * Constructs a 3D curve from a 2D curve.
	 * 
	 * TODO: Add a parameter to specify the plane.
	 *
	 * @param {Curve<Vector2>} curve2d - A 2D curve.
	 */
	constructor(curve2d) {

		super();

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isPlanarCurve3 = true;

		this.type = 'PlanarCurve3';

		/**
		 * The 2D curve.
		 *
		 * @type {Curve<Vector2>}
		 * @default 
		 */
		this.curve2d = curve2d;

	}

	/**
	 * Returns a point on the curve.
	 *
	 * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`.
	 * @param {Vector2} [optionalTarget] - The optional target vector the result is written to.
	 * @return {Vector2} The position on the curve.
	 */
	getPoint(t, optionalTarget = new Vector3()) {

		const point = optionalTarget;

		const v2 = this.curve2d.getPoint(t);

		return point.set(v2.x, v2.y, 0);

	}

	copy(source) {

		super.copy(source);

		this.curve2d = source.curve2d;

		return this;

	}

	toJSON() {

		const data = super.toJSON();

		data.curve2d = this.curve2d.toJSON();

		return data;

	}

	fromJSON(json) {

		super.fromJSON(json);

		this.curve2d.fromJSON(json.curve2d); // TODO: does this need a copy?

		return this;

	}

}

export { PlanarCurve3 };