28

This is what I'd like to achieve (a modifiable polygon where the red circles are vertices) and I'd like to build the polygon dynamically.

enter image description here

When initiating the geometry as

var geometry = new THREE.Geometry();

geometry.vertices.push(point);
geometry.vertices.push(point);

var line = new THREE.Line(geometry, new THREE.LineBasicMaterial({}));

it works well until the second click, it builds a straight line between 1 and 2 but does not add a third line when it's pushed to the array. WebGL seems to require buffered points.

When I predefine vertices like this I can draw two lines (third click)

var geometry = new THREE.Geometry();

for (var i = 0; i < 4; i++) {
    geometry.vertices.push(point);
}

var line = new THREE.Line(geometry, new THREE.LineBasicMaterial({}));

but this is not a good solution as I don't know how many vertices does the user want to add and it's pointless to assign it a big number as I have to loop it multiple times.

Is there any way around it?

Wilt
  • 41,477
  • 12
  • 152
  • 203
user3960875
  • 965
  • 1
  • 13
  • 24

3 Answers3

48

You can animate a line -- or increase the number of points rendered -- very easily using BufferGeometry and the setDrawRange() method. You do need to set a maximum number of points, however.

const MAX_POINTS = 500;

// geometry
const geometry = new THREE.BufferGeometry();

// attributes
const positions = new Float32Array( MAX_POINTS * 3 ); // 3 vertices per point
geometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );

// drawcalls
drawCount = 2; // draw the first 2 points, only
geometry.setDrawRange( 0, drawCount );

// material
const material = new THREE.LineBasicMaterial( { color: 0xff0000 } );

// line
line = new THREE.Line( geometry,  material );
scene.add( line );

If you want to change the number of points rendered after the first render, do this:

line.geometry.setDrawRange( 0, newValue );

If you want to change the position data values after the first render, you set the needsUpdate flag like so:

line.geometry.attributes.position.needsUpdate = true; // required after the first render

Here is a fiddle showing an animated line which you can adapt to your use case.

three.js r.147

WestLangley
  • 102,557
  • 10
  • 276
  • 276
  • `MAX_POINTS = 500;` means that if the user creates more than 500 points it breaks down, correct? Best case scenario would be that I do not have to predefine how many points I want to add. Although your solution is still better than mine since `BufferGeometry` is more memory efficient and you use less draw calls. – user3960875 Jul 15 '15 at 05:47
  • This is how to do what you want within the framework of three.js. – WestLangley Jul 15 '15 at 14:21
  • @WestLangley Can this be adapted to use the LineDashedMaterial?, I want to do something simiar but with dashes, I will open a question if feasible. – Neil Dec 17 '15 at 11:05
  • @Neil Yes. You would have to add an additional geometry attribute `lineDistance` to accommodate `LineDashedMaterial`. – WestLangley Dec 19 '15 at 23:43
  • why didn't you just paste the link to the original post? https://threejs.org/docs/#manual/en/introduction/How-to-update-things – Drout Nov 05 '20 at 15:06
  • I wouldn't allocate 500 points up-front, then worry if it was enough ... Instead, I'd do it in smaller BufferGeometry chunks, say 100 points each. Once you use-up all 100, you add another BufferGeometry for another 100 points and another Line segment. This way, you can keep adding new points without any limits. – Pete Nov 15 '21 at 00:54
  • How does this code example answer the question? The OP was looking for user-interaction. The code only shows how to to RENDER a line; not draw & edit one. Asking for a friend!! –  Sep 02 '22 at 16:50
12

Draw a line in real time

Here an updated fiddle where I optimized the code from user3325025 his example; In this case there is absolutely no need to update all the points of the line on render. Update is only needed onMouseMove (updating end of line) and onMouseDown (drawing new point):

// update line
function updateLine() {
  positions[count * 3 - 3] = mouse.x;
  positions[count * 3 - 2] = mouse.y;
  positions[count * 3 - 1] = mouse.z;
  line.geometry.attributes.position.needsUpdate = true;
}

// mouse move handler
function onMouseMove(event) {
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
  mouse.z = 0;
  mouse.unproject(camera);
  if( count !== 0 ){
    updateLine();
  }
}

// add point
function addPoint(event){
  positions[count * 3 + 0] = mouse.x;
  positions[count * 3 + 1] = mouse.y;
  positions[count * 3 + 2] = mouse.z;
  count++;
  line.geometry.setDrawRange(0, count);
  updateLine();
}
Wilt
  • 41,477
  • 12
  • 152
  • 203
8

I updated the fiddle with mouse events and a vector array if you want to scribble freehand.

https://jsfiddle.net/w67tzfhx/40/

function onMouseDown(evt) {

    if(evt.which == 3) return;


    var x = ( event.clientX / window.innerWidth ) * 2 - 1;
    var y =  - ( event.clientY / window.innerHeight ) * 2 + 1;

    // do not register if right mouse button is pressed.

    var vNow = new THREE.Vector3(x, y, 0);
    vNow.unproject(camera);
    console.log(vNow.x + " " + vNow.y+  " " + vNow.z); 
    splineArray.push(vNow);

    document.addEventListener("mousemove",onMouseMove,false);
    document.addEventListener("mouseup",onMouseUp,false);
}
user3325025
  • 654
  • 7
  • 10