Show a tube geometry in three.js step by step

I have successfully rendered a THREE.TubeGeometry figure, which can be viewed https://i.sstatic.net/XpszN.png

Below is the code and a link to view it on JSBin

<html>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r75/three.js"></script>

<script>
    // Global variables
    var renderer;
    var scene;
    var camera;
    var geometry;

    var control;

    var count = 0;
    var animationTracker;

    init();
    drawSpline();

    function init()
    {
        // Creating a scene to hold all elements
        scene = new THREE.Scene();

        // Setting up a camera for perspective viewing
        camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

        // Creating a WebGL render with lightgray background
        renderer = new THREE.WebGLRenderer();
        renderer.setClearColor('lightgray', 1.0);
        renderer.setSize(window.innerWidth, window.innerHeight);

        // Positioning the camera
        camera.position.x = 0;
        camera.position.y = 40;
        camera.position.z = 40;
        camera.lookAt(scene.position);

        // Adding renderer output to HTML element
        document.body.appendChild(renderer.domElement);
    }

    function drawSpline(numPoints)
    {
        var numPoints = 100;
        var start = new THREE.Vector3(-5, 0, 20);
        var middle = new THREE.Vector3(0, 35, 0);
        var end = new THREE.Vector3(5, 0, -20);

        var curveQuad = new THREE.QuadraticBezierCurve3(start, middle, end);

        var tube = new THREE.TubeGeometry(curveQuad, numPoints, 0.5, 20, false);
        var mesh = new THREE.Mesh(tube, new THREE.MeshNormalMaterial({
            opacity: 0.9,
            transparent: true
        }));

        scene.add(mesh);
        renderer.render(scene, camera);
    }
</script>
</body>
</html>

To enhance the display by incrementally loading as an arc, I decided to animate the process of drawing the arc line by line. You can see this in action on JSBin

Here's the additional code snippet:

<!DOCTYPE html>
<html>
<head>
    <title>Incremental Spline Curve</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r75/three.js"></script>
    <style>
        body {
            margin: 0;
            overflow: hidden;
        }
    </style>
</head>
<script>

    // Global variables
    var renderer;
    var scene;
    var camera;
    var splineGeometry;

    var count = 0;
    var animationTracker;

    var sphere;
    var light;

    function init() {

        // Creating a scene
        scene = new THREE.Scene();

        // Setting up a camera
        camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

        // Creating a WebGL renderer
        renderer = new THREE.WebGLRenderer();
        renderer.setClearColor( 'lightblue', 1 );
        renderer.setSize(window.innerWidth, window.innerHeight);

        // Positioning the camera
        camera.position.x = 0;
        camera.position.y = 40;
        camera.position.z = 40;
        camera.lookAt(scene.position);

        // Adding renderer output to HTML
        document.body.appendChild(renderer.domElement);

        sphere = new THREE.Mesh(new THREE.SphereGeometry(0.8,31,31), new THREE.MeshLambertMaterial({
            color: 'yellow',
        }));

        light = new THREE.DirectionalLight('white', 1);
        light.position.set(0,10,10).normalize();

        getSplineData();
    }

    function getSplineData() {
        var curve = new THREE.CubicBezierCurve3(
                new THREE.Vector3( -5, 0, 10 ),
                new THREE.Vector3(0, 20, 0 ),
                new THREE.Vector3(0, 20, 0 ),
                new THREE.Vector3( 2, 0, -25 )
        );

        splineGeometry = new THREE.Geometry();
        splineGeometry.vertices = curve.getPoints( 50 );

        animate();
    }

    function animate() {
        if(count == 50)
        {
            cancelAnimationFrame(animationTracker);
            return;
        }

        drawLine();

        renderer.render(scene, camera);

        count += 1;
        animationTracker = requestAnimationFrame(animate);
    }

    function drawLine() {
        var lineGeometry = new THREE.Geometry();
        var lineMaterial = new THREE.LineBasicMaterial({
            color: 0x0000ff
        });
        
        lineGeometry.vertices.push(
                splineGeometry.vertices[count],
                splineGeometry.vertices[count+1]
        );

        var line = new THREE.Line( lineGeometry, lineMaterial );
        scene.add( line );
    }

    window.onload = init;

</script>
<body>
</body>
</html>

The drawback of my current approach is that I lose out on some effects possible in TubeGeometry such as thickness and transparency. If you have any suggestions on achieving smooth incremental loading for TubeGeometry, please let me know.

Answer №1

THREE.TubeGeometry is used to generate a THREE.BufferGeometry.

When working with THREE.BufferGeometry, you can utilize the drawRange property for animating the mesh:

let nEnd = 0, nMax, nStep = 90; // 30 faces * 3 vertices/face

...

const geometry = new THREE.TubeGeometry( path, pathSegments, tubeRadius, radiusSegments, closed );

nMax = geometry.attributes.position.count;

...

function animate() {

    requestAnimationFrame( animate );

    nEnd = ( nEnd + nStep ) % nMax;

    mesh.geometry.setDrawRange( 0, nEnd );

    renderer.render( scene, camera );

}

UPDATE: For an alternative method, refer to this SO answer.

three.js version r.144

Answer №2

Typically, the method .getPointAt() is used to retrieve a vector representing a point at a specific position along a curve based on its arc length.

If you wish to draw 70% of a curve composed of 100 segments, you can follow this approach:

var percentage = 70;
var curvePath = new THREE.CurvePath();

var end, start = curveQuad.getPointAt(0);

for(var i = 1; i < percentage; i++){
    end = curveQuad.getPointAt(percentage / 100);
    lineCurve = new THREE.LineCurve(start, end);
    curvePath.add(lineCurve);
    start = end;
}

However, if the `curveQuad` does not support the `getPointAt` method, you can obtain an array of 100 points for your curve as follows:

points = curve.getPoints(100);

Then, you can adapt the previous code snippet like so:

var percentage = 70;
var curvePath = new THREE.CurvePath();

var end, start = points[0];

for(var i = 1; i < percentage; i++){
    end = points[percentage];
    lineCurve = new THREE.LineCurve(start, end);
    curvePath.add(lineCurve);
    start = end;
}

Now, `curvePath` contains the desired line segments for creating the tube geometry:

// draw the geometry
var radius = 5, radiusSegments = 8, closed = false;
var geometry = new THREE.TubeGeometry(curvePath, percentage, radius, radiusSegments, closed);

For a dynamic demonstration of using this technique, check out this JSFiddle.

Answer №3

My knowledge of three.js is limited, but I believe I can offer some help. I have devised two solutions for your issue, both revolving around the concept of constructing a new TubeGeometry or reconstructing the existing one around a different curve.

Simple Solution (Solution 1):

var CurveSection = THREE.Curve.create(function(base, from, to) {
  this.base = base;
  this.from = from;
  this.to = to;
}, function(t) {
  return this.base.getPoint((1 - t) * this.from + t * this.to);
});

This solution involves defining a new type of curve that selects a specific segment from a given curve. Usage:

var curve = new CurveSection(yourCurve, 0, .76); // Where .76 represents the percentage

You can now proceed to create a new tube using this approach.

Mathematical Solution (Solution 2):

If you are utilizing a quadratic bezier curve for your arc, which is fantastic! This curve forms a parabola. To extract a segment of this parabolic curve with different boundaries, certain calculations need to be made.

The following code snippet provides the desired outcome by determining the intersection point of the tangents at the start and end points:

// Function to calculate the instersection point of Line3 l1 and Line3 l2.
function intersection(l1, l2) {
  // Implementation details...
}

// Function to compute the tangentVector of the bezier-curve
function tangentQuadraticBezier(bezier, t) {
  // Implementation details...
}

// Returns a new QuadraticBezierCurve3 with adjusted bounds.
function sectionInQuadraticBezier(bezier, from, to) {
  // Implementation details...
}

While this method may seem complex, it utilizes mathematical principles to manipulate the bezier curve effectively. However, if you do not require the unique properties of a Bezier curve, the simpler Solution 1 may suffice.

Note: As I am not well-versed in Three.js, I cannot provide insight into the most efficient method for implementing the animation. Considering that Three.js does not fully leverage the distinctive characteristics of a bezier curve, Solution 2 may not be as beneficial.

I trust that you will find these solutions helpful in addressing your challenge.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Modify a variable within the service using multiple controllers

Seeking a more efficient way to accomplish the following: This is my service: dashboardApp.service('MessagesService', function () { var message = ""; this.update = function (new_message) { message = new_message; }; this ...

I successfully crafted a key frame animation in Blender with no use of bones, but now I am unsure how to implement it in Three.js. While I am familiar with working with bones, this

This model is used to demonstrate boneless animation. I need advice on the appropriate format for exporting it, as I have encountered difficulties trying to export this specific animation as a .json file. https://i.sstatic.net/Dt2Yq.png ...

Fluid container and confined container

Can someone explain the guidelines for when to use container versus container fluid in web development? What are some best practices and common pitfalls to avoid when using these two options? I would appreciate a clear explanation of the differences betwe ...

The modal is functioning perfectly with the initial row in the table

Is there anyone who can help me fix this issue? I've spent a lot of time trying different solutions, but none of them seem to work. I'm using a tailwindcss template for the modal and JavaScript to show or hide it. I'm having trouble findin ...

What is the best way to send a large amount of data to a view in Angular without the need for persistence?

One way to retrieve data in a view is by utilizing a service or making an HTTP call to a database. Data can also be accessed through parameters in the URL or via the query string. However, what if you need to pass a large amount of non-persistent data in ...

Connect directly to the DOM without using an event

A jQuery function is included in my code: $('#big-bad-bold-button') .on('click', aloha.ui.command(aloha.ui.commands.bold)); This function binds the click event. Is there a way to achieve the same binding without requiring a click? ...

Easily resolving conflicts by removing `package-lock.json`

Whenever I work in a team environment, I often encounter merge conflicts with the package-lock.json file. My usual solution is to simply delete the file and generate it again using npm install. So far, I haven't noticed any negative effects from this ...

What is preventing me from accessing the $sceProvider?

Struggling to implement a filter using $sceProvider to decode HTML tags. Here's my current code structure: myApp.filter('decodeHtml', function($sce) { return function(item) { return $sce.trustAsHtml(item); }; However, upon integrating ...

Instead of using a v-if condition, add a condition directly in the Vue attribute

Apologies for the unclear title, as I am unsure of what to name it with regards to my current issue, I am attempting to create a component layout using vuetify grid. I have a clear idea of how to do this conventionally, like so: <template> <v-fl ...

Invoke actions when clicking outside of components

Currently, I have a HeaderSubmenu component that is designed to show/hide a drop-down menu when a specific button is clicked. However, I am now trying to implement a solution where if the user clicks anywhere else in the application other than on this drop ...

Unable to successfully send form data from JavaScript to PHP

I'm currently working on a form in HTML, and I want to implement a JavaScript verification before submitting the data to PHP. However, despite assigning names to input tags and specifying an action attribute in the form tag, the connection to the PHP ...

Why isn't my classList .add method working properly?

I've created a video slider background on my website, but I'm having trouble with the Javacript for video slider navigation. When I click on the button to change the video, nothing happens. The console is showing an error message that says "Canno ...

Steps for implementing AJAX to display a success function and update database results in real-time

I'm struggling with allowing my AJAX call to send data to my PHP file and update the page without a reload. I need the success message to display after approving a user, but their name doesn't move on the page until I refresh. The goal is to app ...

JavaScript For Each loops are really starting to frustrate me

My issue seems to be straightforward. I am attempting to update a list of objects called localData with another list of objects that I received after making an AJAX request (referred to as data). The process involves using two loops, however, when I atte ...

What is the method to retrieve the control name of a dynamic Angular field?

I am currently working on a feature where I need to dynamically add new controls to my formBuilder. /* templatesTypes = ["string1", "string2", "string3","string4"] */ /* templates = [{templateType: "string1", ...}, {templateType: "string2"}, ...]*/ this. ...

What is the best way to choose a single li element?

I am working on creating a reservation system using HTML, CSS, and JS. I want to customize the color of the border for each list item (li). However, I want only one li to be selected at a time. When I click on a different li, it should remove the selection ...

How to trigger a re-rendering or compilation of a ng-repeated div within an AngularJS directive

I am working with a div that utilizes ng-repeat to display items. Initially, I have a few items in an array. My goal is to ensure that the first item renders before the subsequent items. This is necessary due to the dependency on the height of each div. ...

The impact of the Javascript submit is limited to the initial div within the PHP while loop

I am currently working on a feature that allows users to like a status. Once liked, the thumbs-up icon should change to "liked!" However, I am facing an issue with using this feature in a while loop. When I like one status (let's say the 2nd row), an ...

The functionality of AngularJS ng-disable is failing to work on an anchor tag

I am currently facing a challenge in my AngularJS project where I need to disable an icon within an ng-repeat scenario based on a condition. Specifically, I want to check if the owner is null and then disable the icon accordingly. However, despite verifyin ...

Ensuring the "X" Button of a jQueryUI Dialog Remains Visible on the Screen

Take a look at this CodePen example: https://codepen.io/ChrisVomRhein/pen/yzBwaZ Upon clicking the button, a dialog containing a large amount of text opens. The following code snippet is responsible for positioning the dialog relative to the button: $( " ...