Fill Area between Lines with color - Three.js

旧巷老猫 提交于 2020-05-27 04:58:50

问题


I have series of lines, and I need to color all the area beneath or between lines with a color.

For the sake of simplicity, I have created a snippet with closed lines. In reality, i have a program that draws many lines (like a stock market graph) , and I need to color all the area below the graph. see also the screenshot.

would appreciate any help or even new approach that maybe I need to take.

<html>

<head>
    <title>My first three.js app</title>
    <style>
        body {
            margin: 0;
        }

        canvas {
            width: 100%;
            height: 100%
        }
    </style>
</head>

<body>
    <script src="https://threejs.org/build/three.min.js"></script>
    <script>
        var scene = new THREE.Scene();
        var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 500);

        var renderer = new THREE.WebGLRenderer();
        renderer.setSize(window.innerWidth, window.innerHeight);
        document.body.appendChild(renderer.domElement);
        function createLine(startX,startY,endX,endY) {
            const geometry = new THREE.Geometry();
            geometry.vertices.push(new THREE.Vector3(startX, startY, 0));
            geometry.vertices.push(new THREE.Vector3(endX, endY, 0));
            const material = new THREE.LineBasicMaterial({
                color: 0xffffff
            });
            return new THREE.Line(geometry, material);
        }
        scene.add(createLine(0,0,1,0));
        scene.add(createLine(1,0,1,1));
        scene.add(createLine(1,1,0,1));
        scene.add(createLine(0,1,0,0));

        camera.position.z = 5;

        var animate = function () {
            requestAnimationFrame(animate);
            renderer.render(scene, camera);
        };

        animate();
    </script>
</body>

</html>

回答1:


You can use THREE.Shape and THREE.ShapeGeometry to draw a filled polygon by THREE.Mesh:

function createPolygon( poly ) {
    var shape = new THREE.Shape();
    shape.moveTo( poly[0][0], poly[0][1] );
    for (var i = 1; i < poly.length; ++ i)
        shape.lineTo( poly[i][0], poly[i][1] );
    shape.lineTo( poly[0][0], poly[0][1] );

    var geometry = new THREE.ShapeGeometry( shape );
    var material = new THREE.MeshBasicMaterial( {
        color: 0x800000
    } );
    return new THREE.Mesh(geometry, material);
}

var poly = [[0,1],[0.25,0],[0.5,0.5],[0.75,0],[1,1]];
scene.add(createPolygon(poly))


See the example:

var renderer, scene, camera, controls;

init();
animate();

function createLine(startX,startY,endX,endY) {
    const geometry = new THREE.Geometry();
    geometry.vertices.push(new THREE.Vector3(startX, startY, 0));
    geometry.vertices.push(new THREE.Vector3(endX, endY, 0));
    const material = new THREE.LineBasicMaterial({
        color: 0xffffff
    });
    return new THREE.Line(geometry, material);
}

function createPolygon( poly ) {
  var shape = new THREE.Shape();
  shape.moveTo( poly[0][0], poly[0][1] );
  for (var i = 1; i < poly.length; ++ i)
    shape.lineTo( poly[i][0], poly[i][1] );
  shape.lineTo( poly[0][0], poly[0][1] );
    
  var geometry = new THREE.ShapeGeometry( shape );
    var material = new THREE.MeshBasicMaterial( {
        color: 0x800000
    } );
    return new THREE.Mesh(geometry, material);
}

function init() {

    // renderer
    renderer = new THREE.WebGLRenderer();
    renderer.setSize( window.innerWidth, window.innerHeight );
    document.body.appendChild( renderer.domElement );

    scene = new THREE.Scene();
    
    camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 1000 );
    //camera.position.set( 20, 20, 20 );
    camera.position.z = 5;
    window.onresize = resize;

    var light = new THREE.HemisphereLight( 0xeeeeee, 0x888888, 1 );
	  light.position.set( 0, 20, 0 );
	  scene.add( light );
    
    var poly = [[0,1],[0.25,0],[0.5,0.5],[0.75,0],[1,1]];
    
    scene.add(createPolygon(poly))
    
    for (var i = 0; i < poly.length-1; ++i)
    {
        var i2 = i<poly.length-1 ? i+1 : 0 
        scene.add(createLine(poly[i][0],poly[i][1],poly[i2][0],poly[i2][1]));
    }
}

function resize() {
    
    var aspect = window.innerWidth / window.innerHeight;
    renderer.setSize(window.innerWidth, window.innerHeight);
    camera.aspect = aspect;
    camera.updateProjectionMatrix();
}

function animate() {
    requestAnimationFrame( animate );
    renderer.render( scene, camera );
}
body {
    margin: 0;
}

canvas {
    width: 100%;
    height: 100%
}
<script src="https://threejs.org/build/three.min.js"></script>



回答2:


To fill it with colour, you have to use a geometry with faces (THREE.Face3()). To have it, you need to trialngulate a shape or to use an existing geometry, which fits to your needs, with some changes of its vertices.

Below, there is a very rough concept, using an existing geometry (THREE.PlaneGeometry()), which colours the area below the graph. But keep in mind, that such approach is okay to use, when you need to show only positive or only negative values in one graph, otherwise, the result will look strange (or better to say, ugly).

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 2, 10);
var renderer = new THREE.WebGLRenderer({
  antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

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

var points = [],
  pointCount = 21;
var plane, line;

scene.add(new THREE.GridHelper( pointCount - 1, pointCount -1 ));

for (let i = 0; i < pointCount; i++) {
  points.push({
    initValue: THREE.Math.randFloat(-1, 1),
    amplitude: THREE.Math.randFloat(1, 2),
    speed: THREE.Math.randFloat(.5, 2)
  });
}

createGraph( pointCount );

function createGraph( pointCount ) {
  var planeGeom = new THREE.PlaneGeometry(pointCount - 1, 1, pointCount - 1, 1);
  planeGeom.translate(0, .5, 0);

  plane = new THREE.Mesh(planeGeom, new THREE.MeshBasicMaterial({
    color: "red",
    wireframe: false,
    side: THREE.DoubleSide,
    transparent: true,
    opacity: .75
  }));
  scene.add(plane);

  var lineGeom = new THREE.Geometry();
  for (let i = 0; i < plane.geometry.parameters.widthSegments + 1; i++) {
    lineGeom.vertices.push(planeGeom.vertices[i]); // share the upper points of the plane
  }
  line = new THREE.Line(lineGeom, new THREE.LineBasicMaterial({
    color: "aqua"
  }));
  plane.add(line);
}


var time = 0;

render();

function render() {
  time = Date.now() * .001;
  requestAnimationFrame(render);
  points.forEach( ( p, idx ) => {
    plane.geometry.vertices[idx].y = 2.5 + Math.sin( (time + p.initValue) * p.speed) * p.amplitude;  // the trick is that indices of upper vertices are from 0 to N consequently in row (r87),
    // thus we can assign the data from the `points` array to their Y-coordinates to form the graph
  });
  plane.geometry.verticesNeedUpdate = true; // the most important thing when you change coordiantes of vertices
  line.geometry.verticesNeedUpdate = true; // the most important thing when you change coordiantes of vertices
  renderer.render(scene, camera);
}
body {
  overflow: hidden;
  margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>



回答3:


You need to create 1 big shape taking into account the whole graph you want to fill. So, just like in your image but then include the axis.

Then you can fill the shape with any kind of material.

Here are some examples on the threejs website:

https://threejs.org/examples/?q=shape#webgl_geometry_extrude_shapes2 https://threejs.org/examples/?q=shape#webgl_geometry_shapes

You probably wont need to extrude like in those examples.



来源:https://stackoverflow.com/questions/46868061/fill-area-between-lines-with-color-three-js

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!