最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Incrementally display three.js TubeGeometry - Stack Overflow

programmeradmin1浏览0评论

I am able to display a THREE.TubeGeometry figure as follows

Code below, link to jsbin

<html>
<body>
<script src=".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=".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.

I am able to display a THREE.TubeGeometry figure as follows

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.

Share Improve this question edited Apr 6, 2016 at 0:50 WestLangley 105k11 gold badges287 silver badges283 bronze badges asked Apr 5, 2016 at 12:15 tubbytubby 2,1443 gold badges35 silver badges56 bronze badges
Add a comment  | 

3 Answers 3

Reset to default 17

THREE.TubeGeometry returns a THREE.BufferGeometry.

With THREE.BufferGeometry, you have access to a property drawRange that you can set to animate the drawing of 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 );

}

EDIT: For another approach, see this SO answer.

three.js r.144

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

I'm not really that familiar with three.js. But I think I can be of assistance. I have two solutions for you. Both based on the same principle: build a new TubeGeometry or rebuild the current one, around a new curve.

Solution 1 (Simple):

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

You define a new type of curve which just selects a segment out of a given curve. Usage:

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

Now you can build a new tube.

Solution 2 (Mathematics!):

You are using for your arc a quadratic bezier curve, that's awesome! This curve is a parabola. You want just a segment of that parabola and that is again a parabola, just with other bounds.

What we need is a section of the bezier curve. Let's say the curve is defined by A (start), B (direction), C (end). If we want to change the start to a point D and the end to a point F we need the point E that is the direction of the curve in D and F. So the tangents to our parabola in D and F have to intersect in E. So the following code will give us the desired result:

// Calculates the instersection point of Line3 l1 and Line3 l2.
function intersection(l1, l2) {
  var A = l1.start;
  var P = l2.closestPointToPoint(A);
  var Q = l1.closestPointToPoint(P);
  var l = P.distanceToSquared(A) / Q.distanceTo(A);
  var d = (new THREE.Vector3()).subVectors(Q, A);
  return d.multiplyScalar(l / d.length()).add(A);
}

// Calculate the tangentVector of the bezier-curve
function tangentQuadraticBezier(bezier, t) {
  var s = bezier.v0,
      m = bezier.v1,
      e = bezier.v2;
  return new THREE.Vector3(
    THREE.CurveUtils.tangentQuadraticBezier(t, s.x, m.x, e.x),
    THREE.CurveUtils.tangentQuadraticBezier(t, s.y, m.y, e.y),
    THREE.CurveUtils.tangentQuadraticBezier(t, s.z, m.z, e.z)
  );
}

// Returns a new QuadraticBezierCurve3 with the new bounds.
function sectionInQuadraticBezier(bezier, from, to) {
  var s = bezier.v0,
      m = bezier.v1,
      e = bezier.v2;

  var ns = bezier.getPoint(from),
      ne = bezier.getPoint(to);
  var nm = intersection(
    new THREE.Line3(ns, tangentQuadraticBezier(bezier, from).add(ns)),
    new THREE.Line3(ne, tangentQuadraticBezier(bezier, to).add(ne))
  );
  return new THREE.QuadraticBezierCurve3(ns, nm, ne);
}

This is a very mathematical way, but if you should need the special properties of a Bezier curve, this is the way to go.

Note: The first solution is the simplest. I am not familiar with Three.js so I wouldn't know what the most efficient way to implement the animation is. Three.js doesn't seem to use the special properties of a bezier curve so maybe solution 2 isn't that useful.

I hope you have gotten something useful out of this.

发布评论

评论列表(0)

  1. 暂无评论