Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fill Area between Lines with color - Three.js

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.

enter image description here

<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>
like image 924
LiranC Avatar asked Oct 21 '17 21:10

LiranC


3 Answers

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://cdn.jsdelivr.net/npm/[email protected]/build/three.js"></script>
like image 160
Rabbid76 Avatar answered Nov 11 '22 19:11

Rabbid76


enter image description here

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>
like image 6
prisoner849 Avatar answered Nov 11 '22 18:11

prisoner849


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.

like image 2
polyclick Avatar answered Nov 11 '22 19:11

polyclick