Create a curved plane surface which dynamically changes its size

Hello,
I know there are several resources on this but I am not able to get a curved plane from a set of Vector3 points. This is the screenshot of the output.
curvedPlanePoints
The ends of red and green line segments represents the boundaries of plane. and I want to make a plane connecting all those points. I can even move those points and add new points. So I want my plane size to be flexible too. Can someone help me out here?

Hi!
The first thought about it is that you have to re-create your geometry when you move or add a point.

I thought so, but isn’t there any way to add points without recreating the geometry? And can you tell me how to make a plane from the set of those points? I have tried using ShapeGeometry but I can create only 2D curved plane which acts as a projection to the figure on the grid.

.setDrawRange ( start : Integer, count : Integer ) : null
Set the .drawRange property. For non-indexed BufferGeometry, count is the number of vertices to render. For indexed BufferGeometry, count is the number of indices to render.
https://threejs.org/docs/index.html#api/en/core/BufferGeometry

This allows you to define a BufferGeometry with maximum size, but only draws the existing part. This is how I realized the construction of frames.

gLine.setDrawRange ( 0, 0 );
later then

sceneB.add( markers[ markerCount ] );
gLine.setDrawRange ( 0, markerCount + 1 );

20190216-1826-24635

The markers can also be moved. And then also the BufferGeometry (setDynamic).

.addAttribute( ‘position’, new THREE.BufferAttribute( g.points, 3 ).setDynamic( true ) );


Addendum:

I’m not quite clear:

Do only the white points define the geometry, or are all outer points of the red or green lines given?

It looks like all lines are parallel to x-z plane?

If only the white points are given, how is the curve ( midline) between them created?

Thanks for the answer. It worked out and now I have a dynamically changing geometry and about the question you asked.
Well I have created a catmullrom curve connecting the white points and all the red and green lines are the normals to the curve parallel to x-z plane.

Now I got to use the end points of the green and red line segments to make a plane connecting the points and make it into something like a road. So I want to know how to make a plane using those vertices where red indicating the right side of road and green indicating the left side of road.

If you have the coordinates of the points, it’s no problem. I’ll take a look at it in the evening. Such a road fits well into my addon Addon to create special / extended geometries

Yeah sure if it is doable by today it would be most helpful and I hope you got my question right.

I could use my approach from the
https://discourse.threejs.org/t/addon-produces-almost-infinite-many-time-varying-geometries-with-functions/262 .
https://github.com/hofk/THREEf.js/tree/master/THREEf_90
Totally simplify. An initial approach.

20190217-1806-31310

A motorway with four lanes is also possible.
20190217-1807-16830

Now you only have to generate the matching coordinates. Here is just a primitive example.


<!DOCTYPE html>
<!-- @author hofk -->
<head>
	<title> road </title>
	<meta charset="utf-8" />
</head>
<body> 	

</body>
	<script src="../js/three.min.101.js"></script>
	<script src="../js/OrbitControls.js"></script>
<script>

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 55, window.innerWidth / window.innerHeight, 0.1, 100 );
camera.position.set( 10, 4, 10 );
var renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( 0xdddddd, 1 );	
var container = document.createElement( 'div' );
document.body.appendChild( container );
container.appendChild( renderer.domElement ); 
var controls = new THREE.OrbitControls( camera, renderer.domElement );

var ls = 100; // length segments
var ws = 2; // width segments, tracks

var lss = ls + 1;
var wss = ws + 1;

var faceCount = ls * ws * 2;
var vertexCount = lss * wss;

var g = new THREE.BufferGeometry( );

g.faceIndices = new Uint32Array( faceCount * 3 );
g.vertices = new Float32Array( vertexCount * 3 );  
//g.normals = new Float32Array( vertexCount * 3 ); 
//g.uvs = new Float32Array( vertexCount * 2 );

g.setIndex( new THREE.BufferAttribute( g.faceIndices, 1 ) );	
g.addAttribute( 'position', new THREE.BufferAttribute( g.vertices, 3 ).setDynamic( true ) );
//g.addAttribute( 'normal', new THREE.BufferAttribute( g.normals, 3 ).setDynamic( true ) );
//g.addAttribute( 'uv', new THREE.BufferAttribute( g.uvs, 2 ) );

var idxCount = 0;

for ( var j = 0; j < ls; j ++ ) {
		
	for ( var i = 0; i < ws; i ++ ) {
		
		// 2 faces / segment,  3 vertex indices
		a =  wss * j + i;
		b1 = wss * ( j + 1 ) + i;	// right-bottom
		c1 = wss * ( j + 1 ) + 1 + i;
		b2 = wss * ( j + 1 ) + 1 + i;	// left-top
		c2 = wss * j + 1 + i;
		
		g.faceIndices[ idxCount     ] = a; // right-bottom
		g.faceIndices[ idxCount + 1 ] = b1;
		g.faceIndices[ idxCount + 2 ] = c1; 
		
		g.faceIndices[ idxCount + 3 ] = a; // left-top
		g.faceIndices[ idxCount + 4 ] = b2,
		g.faceIndices[ idxCount + 5 ] = c2; 
		
		idxCount += 6;
		
	}
		
}

// write groups for multi material
/*
//Customize for different colored tracks.
for ( var f = 0, p = 0; f < faceCount; f ++, p += 3 ) {
	
	g.addGroup( p, 3, 1 );
	
}
 
*/

var x, y, z;
var vIdx = 0; 	// vertex index
var posIdx; // position  index

for ( var j = 0; j < lss; j ++ ) {  // length
		
	for ( var i = 0; i < wss; i ++ ) { // width
		
		// calculate here the coordinates according to your wishes
		x = j / 10;
		y = Math.sin(  Math.PI * j / 100 );
		z = i - 1;
		
		xyzSet();
		
		vIdx ++;
		
	}
	
}


g.attributes.position.needsUpdate = true;
//g.attributes.normal.needsUpdate = true;

var material = new THREE.MeshBasicMaterial( { color: 0xff0000, side: THREE.DoubleSide, wireframe: true } );
var mesh = new THREE.Mesh( g, material );
scene.add( mesh );

animate();

//............................

// set vertex position
function xyzSet() {
	
	posIdx = vIdx * 3;
	
	g.vertices[ posIdx ]  = x;
	g.vertices[ posIdx + 1 ]  = y;
	g.vertices[ posIdx + 2 ]  = z;
	
}

function animate() {

	requestAnimationFrame( animate );	
	renderer.render( scene, camera );
	controls.update();
	
}
</script>

</html>

The sketch shows the scheme of vertices and faces for 3 tracks and a length of 3.
Here you can also see the bottom and top of the Addon.
20190217-1837-39104

It’s not a road, but it’s a nice tape.

	// calculate here the coordinates according to your wishes		
	tangent = curve.getTangent( j / 100 ); //  100 length segments	
	x = points[ j ].x + tangent.x;
	y = points[ j ].y + 1;
	z = points[ j ].z + tangent.z + i / 2;

20190217-2121-51372

Now there is a four-lane street in colored wireframe.

20190218-1957-52686


<!DOCTYPE html>
<!-- @author hofk -->
<head>
	<title> FourLaneRoad </title>
	<meta charset="utf-8" />
</head>
<body> 	

</body>
	<script src="../js/three.min.101.js"></script>
	<script src="../js/OrbitControls.js"></script>
<script>

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 55, window.innerWidth / window.innerHeight, 0.1, 100 );
camera.position.set( -1, 14, 24 );
var renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( 0x000000, 1 );	
var container = document.createElement( 'div' );
document.body.appendChild( container );
container.appendChild( renderer.domElement ); 
var controls = new THREE.OrbitControls( camera, renderer.domElement );

var gridHelper = new THREE.GridHelper( 100, 100 );
scene.add( gridHelper );

var ls = 200; // length segments
var ws = 4; // width segments, tracks

var lss = ls + 1;
var wss = ws + 1;

var faceCount = ls * ws * 2;
var vertexCount = lss * wss;

var g = new THREE.BufferGeometry( );

g.faceIndices = new Uint32Array( faceCount * 3 );
g.vertices = new Float32Array( vertexCount * 3 );  
//g.normals = new Float32Array( vertexCount * 3 ); 
//g.uvs = new Float32Array( vertexCount * 2 );

g.setIndex( new THREE.BufferAttribute( g.faceIndices, 1 ) );	
g.addAttribute( 'position', new THREE.BufferAttribute( g.vertices, 3 ).setDynamic( true ) );
//g.addAttribute( 'normal', new THREE.BufferAttribute( g.normals, 3 ).setDynamic( true ) );
//g.addAttribute( 'uv', new THREE.BufferAttribute( g.uvs, 2 ) );

var idxCount = 0;

for ( var j = 0; j < ls; j ++ ) {
		
	for ( var i = 0; i < ws; i ++ ) {
		
		// 2 faces / segment,  3 vertex indices
		a =  wss * j + i;
		b1 = wss * ( j + 1 ) + i;		// right-bottom
		c1 = wss * ( j + 1 ) + 1 + i;
		b2 = wss * ( j + 1 ) + 1 + i;	// left-top
		c2 = wss * j + 1 + i;
		
		g.faceIndices[ idxCount     ] = a; // right-bottom
		g.faceIndices[ idxCount + 1 ] = b1;
		g.faceIndices[ idxCount + 2 ] = c1; 
		
		g.faceIndices[ idxCount + 3 ] = a; // left-top
		g.faceIndices[ idxCount + 4 ] = b2,
		g.faceIndices[ idxCount + 5 ] = c2; 
		
		g.addGroup( idxCount, 6, i ); // write groups for multi material
		
		idxCount += 6;
				
	}
		
}

var curve = new THREE.CatmullRomCurve3( [
	new THREE.Vector3( -25, 0, -25 ),
	new THREE.Vector3( -4, 2, -9 ),
	new THREE.Vector3( 4, 1, -6 ),
	new THREE.Vector3( 6, 0, 0 ),
	new THREE.Vector3( -3, 1, 1 ),
	new THREE.Vector3( -11, 0, 6 ),
	new THREE.Vector3( -12, 1, 1 ),
	new THREE.Vector3( -7, 1, -3 ),
	new THREE.Vector3( 7, 8, -9 ),
	new THREE.Vector3( 13, 2, -12 ),
] );

var points = curve.getPoints( ls );
var curveGeometry = new THREE.BufferGeometry().setFromPoints( points );

var tangent;
var normal = new THREE.Vector3( 0, 0, 0 );
var binormal = new THREE.Vector3( 0, 1, 0 );

var x, y, z;
var vIdx = 0; // vertex index
var posIdx; // position  index

for ( var j = 0; j < lss; j ++ ) {  // length
		
	for ( var i = 0; i < wss; i ++ ) { // width
		
		// calculate here the coordinates according to your wishes
		
		tangent = curve.getTangent( j / ls ); //  .. / length segments	
	
		normal.cross( tangent, binormal );

		binormal.cross( normal, tangent ); // new binormal
		
		normal.normalize().multiplyScalar( 0.25 );
	
		x = points[ j ].x + ( i - ws / 2 ) * normal.x;
		y = points[ j ].y;
		z = points[ j ].z + ( i - ws / 2 ) * normal.z;
		
		xyzSet();
		
		vIdx ++;
		
	}
	
}

g.attributes.position.needsUpdate = true;
//g.attributes.normal.needsUpdate = true;

var material = [
	
	new THREE.MeshBasicMaterial( { color: 0x00ff00, side: THREE.DoubleSide, wireframe: true } ),
	new THREE.MeshBasicMaterial( { color: 0xff0000, side: THREE.DoubleSide, wireframe: true } ),
	new THREE.MeshBasicMaterial( { color: 0x0000ff, side: THREE.DoubleSide, wireframe: true } ),
	new THREE.MeshBasicMaterial( { color: 0xff00ff, side: THREE.DoubleSide, wireframe: true } ),
	
];

var mesh = new THREE.Mesh( g, material );
scene.add( mesh );

var curveMaterial = new THREE.LineBasicMaterial( { color : 0xffffff } );
var curveLine = new THREE.Line( curveGeometry, curveMaterial );
scene.add( curveLine );

animate();

//............................

// set vertex position
function xyzSet() {
	
	posIdx = vIdx * 3;
	
	g.vertices[ posIdx ]  = x;
	g.vertices[ posIdx + 1 ]  = y;
	g.vertices[ posIdx + 2 ]  = z;
	
}

function animate() {

	requestAnimationFrame( animate );	
	renderer.render( scene, camera );
	controls.update();
	
}
</script>

</html>
3 Likes

Thank you very much for such a great explanation, it really helped me a lot. I got a road now but I am struggling with creating a line(full line and dashed) in between having some width. setting a scale doesn’t work and line width doesn’t work. Do I have to use a similar plane making technique with shorter width?

@hofk
This is cool! :+1: Would be greate to have it amongst official examples. Time to time, people on SO ask about such thing :slight_smile:

How about this?
With black and white material.

20190219-1719-33478

var ls = 200; // length segments
var ws = 5; // width segments, tracks, streaks, stripes

var d = [ -0.6, -0.58, -0.01, 0.01, 0.58, 0.6 ];

for ( var j = 0; j < lss; j ++ ) {  // length
		
	for ( var i = 0; i < wss; i ++ ) { // width
		
		// calculate here the coordinates according to your wishes
		
		tangent = curve.getTangent( j / ls ); //  .. / length segments	
	
		normal.cross( tangent, binormal );

		binormal.cross( normal, tangent ); // new binormal
		
		normal.normalize();

		x = points[ j ].x + d[ i ] * normal.x;
		y = points[ j ].y;
		z = points[ j ].z + d[ i ] * normal.z;
		
		xyzSet();
		
		vIdx ++;
		
	}
	
}
2 Likes

:wink: Just another option.

use

var ls = 200; // length segments
var ws = 3; // width segments 

enable

g.uvs = new Float32Array( vertexCount * 2 );

g.addAttribute( 'uv', new THREE.BufferAttribute( g.uvs, 2 ) );

calculate

var uvIdxCount = 0;

for ( var j = 0; j < lss; j ++ ) {
		
	for ( var i = 0; i < wss; i ++ ) {

		g.uvs[ uvIdxCount     ] = j / ls;
		g.uvs[ uvIdxCount + 1 ] = i / ws;
		
		uvIdxCount += 2;
		
	}
	
}	

use

var d = [ -0.52, -0.5, 0.5, 0.52 ];

use

normal.normalize();

x = points[ j ].x + d[ i ] * normal.x;
y = points[ j ].y; 
z = points[ j ].z + d[ i ] * normal.z;

material

tex = new THREE.TextureLoader().load( 'RoadMarking.png' );
tex.wrapS = THREE.RepeatWrapping;
tex.wrapT = THREE.RepeatWrapping;
tex.repeat.set( 100, 1 );

var material = [
	
	new THREE.MeshBasicMaterial( { color: 0xffffff, side: THREE.DoubleSide} ),
	new THREE.MeshBasicMaterial( { map: tex, side: THREE.DoubleSide} ),
	new THREE.MeshBasicMaterial( { color: 0xffffff, side: THREE.DoubleSide} ),
	
];

RoadMarking.png painted fast, I am not an artist

RoadMarking

result

20190219-2028-54390

1 Like

Really appreciate the help but I have done all those … I should be dynamically able to change the width of those lane markings and using texture is not an option. I was able to make a pure line with the curved plane with small width but don’t know how to make a dashed line with changable width.

By the way, I really can’t thank you enough for helping me this much and getting me this far.

You can take a separate strip for the centerline and a simple texture.
20190220-0841-12142

Now you can make the stripes dynamic by changing the values in
d = [ -0.62, -0.6, -0.02, 0.02, 0.6, 0.62]; ( initial).

You can see how this works in my addons, for example.

But there’s another problem. If the dots are not equidistant, there are different line lengths white.

var curve = new THREE.CatmullRomCurve3( [
	new THREE.Vector3( -25, 0.2, -25 ),
	new THREE.Vector3( -24, 0.2, -24 ),
	new THREE.Vector3( -4, 2, -9 ),

20190220-0845-48114

20190220-0848-01276

One must extend the calculation, e.g. for the uv’s use the possibilities of CatmullRomCurve3 / Curve.
.getLength .getSpacedPoints etc.

This is gonna be a nice racetrack :motorway: , I think.
:traffic_light: :red_car: :blue_car: :minibus: :construction:

I need to take a closer look at this.


addendum: I looked. It’s quite simple.

The uv calculation shifted backwards and changed:

var len = curve.getLength( );
var lenList = curve.getLengths ( ls );

var uvIdxCount = 0;

for ( var j = 0; j < lss; j ++ ) {
		
	for ( var i = 0; i < wss; i ++ ) {

		//g.uvs[ uvIdxCount     ] = j / ls;
		//g.uvs[ uvIdxCount + 1 ] = i / ws;
		
		g.uvs[ uvIdxCount     ] = lenList[ j ] / len;
		g.uvs[ uvIdxCount + 1 ] = i / ws;
		uvIdxCount += 2;
		
	}
	
}

20190220-1831-56794

3 Likes

There’s a bug fix.
Also in the addon THREEg.

Since only the components x and z of the normal are used, but the y component is not always 0, it resulted in different road widths.

It’s better this way.

 	normal.y = 0;
	
	normal.normalize();

        lxz = Math.sqrt( normal.x * normal.x + normal.z * normal.z );
			
		nx = normal.x / lxz;
		nz = normal.z / lxz;
			
		for ( var i = 0; i < g.wss; i ++ ) { // width	
			
			x = g.points[ j ].x + g.td[ i ] * nx;
			y = g.points[ j ].y; 
			z = g.points[ j ].z + g.td[ i ] * nz;

:slightly_smiling_face:

The road upstairs is just an area. The normal is horizontal.
If you do it vertically, you get a wall.

But also only one surface.

That’s why I combined both things and got a wall or a street with a substructure.
I’ll add it soon on Github: https://github.com/hofk/THREEg.js :slightly_smiling_face: soon is now :slightly_smiling_face:

Run around the wall.

1 Like