Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Incrementally display three.js TubeGeometry

I am able to display a THREE.TubeGeometry figure as followsenter image description here

Code below, link to 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()
    {
        // create a scene, that will hold all our elements such as objects, cameras and lights.
        scene = new THREE.Scene();

        // create a camera, which defines where we're looking at.
        camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

        // create a render, sets the background color and the size
        renderer = new THREE.WebGLRenderer();
        renderer.setClearColor('lightgray', 1.0);
        renderer.setSize(window.innerWidth, window.innerHeight);

        // position and point the camera to the center of the scene
        camera.position.x = 0;
        camera.position.y = 40;
        camera.position.z = 40;
        camera.lookAt(scene.position);

        // add the output of the renderer to the html element
        document.body.appendChild(renderer.domElement);
    }

    function drawSpline(numPoints)
    {
        var numPoints = 100;
//        var start = new THREE.Vector3(-5, 0, 20);
        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>

However, I would like to display incrementally, as in, like an arc that is loading, such that it starts as the start point, draws incrementally and finally looks the below arc upon completion.

I have been putting in some effort, and was able to do this by storing all the points/coordinates covered by the arc, and drawing lines between the consecutive coordinates, such that I get the 'arc loading incrementally' feel. However, is there a better way to achieve this? This is the link to jsbin

Adding the code here as well

<!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 control;

    var count = 0;
    var animationTracker;

//    var sphereCamera;
    var sphere;
    var light;

    function init() {

        // create a scene, that will hold all our elements such as objects, cameras and lights.
        scene = new THREE.Scene();

        // create a camera, which defines where we're looking at.
        camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

        // create a render, sets the background color and the size
        renderer = new THREE.WebGLRenderer();
//        renderer.setClearColor(0x000000, 1.0);
        renderer.setClearColor( 0xffffff, 1 );
        renderer.setSize(window.innerWidth, window.innerHeight);

        // position and point the camera to the center of the scene
        camera.position.x = 0;
        camera.position.y = 40;
        camera.position.z = 40;
        camera.lookAt(scene.position);

        // add the output of the renderer to the html element
        document.body.appendChild(renderer.domElement);

//        //init for sphere
//        sphereCamera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
//        sphereCamera.position.y = -400;
//        sphereCamera.position.z = 400;
//        sphereCamera.rotation.x = .70;

        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,-400,400).normalize();
        light.position.set(0,10,10).normalize();

        //get points covered by Spline
        getSplineData();
    }

    //save points in geometry.vertices
    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();
    }

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

        //add line to the scene
        drawLine();

        renderer.render(scene, camera);
  //      renderer.render(scene, sphereCamera);

        count += 1;
//        camera.position.z -= 0.25;
//        camera.position.y -= 0.25;
        animationTracker = requestAnimationFrame(animate);
    }

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

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

    // calls the init function when the window is done loading.
    window.onload = init;

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

Drawback : The drawback of doing it the above way is that, end of the day, I'm drawing a line between consecutive points, and so I lose out on a lot of the effects possible in TubeGeometry such as, thickness, transparency etc.

Please suggest me an alternative way to get a smooth incremental load for the TubeGeometry.

like image 698
tubby Avatar asked Apr 05 '16 12:04

tubby


2 Answers

THREE.TubeGeometry returns a THREE.Geometry. By converting the geometry to

THREE.BufferGeometry, you have access to a property drawRange that you can set to animate the drawing of the mesh:

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

...

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

// to buffer goemetry
geometry = new THREE.BufferGeometry().fromGeometry( geometry );
nMax = geometry.attributes.position.count;

...

function animate() {

    requestAnimationFrame( animate );

    nEnd = ( nEnd + nStep ) % nMax;

    mesh.geometry.setDrawRange( 0, nEnd );

    renderer.render( scene, camera );

}

fiddle: http://jsfiddle.net/k73pxyL2/

EDIT: For another approach, see this SO answer.

three.js r.75

like image 144
WestLangley Avatar answered Sep 30 '22 13:09

WestLangley


Normally you would be able to use the method .getPointAt() to "get a vector for point at relative position in curve according to arc length" to get a point at a certain percentage of the length of the curve.

So normally if you want to draw 70% of the curve and a full curve is drawn in 100 segments. Then you could do:

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;
}

But I think this is not working for your curveQuad since the getPointAt method is not implemented for this type. A work around is to get a 100 points for your curve in an array like this:

points = curve.getPoints(100);

And then you can do almost the same:

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 your curvePath holds the line segments you want to use for drawing the tube:

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

Here a fiddle with a demonstration on how to use this dynamically

like image 32
Wilt Avatar answered Sep 30 '22 14:09

Wilt