Create a curved plane surface which dynamically changes its size

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

1 Like

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

1 Like

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 ++;
		
	}
	
}
3 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

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 ),

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

4 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: GitHub - hofk/THREEg.js: three.js addon to create special or extended geometries. The addon generates indexed or non indexed BufferGeometries. :slightly_smiling_face: soon is now :slightly_smiling_face:

Run around the wall.

2 Likes

Hi @hofk

Great work! If I want to create a road intersection, how can I do that? Could you give me some clues? Thanks :slight_smile:

This is very similar for streets and walls, the street just has no / hardly any height.

There is just one topic where this is discussed. There are several possibilities.

Wall building in a level-editor see page 15

1 Like

Constructively, this can be designed to match the sketch Create a curved plane surface which dynamically changes its size (page 8, left column only) like this


Only one triangle is necessary in addition to the triangles of the three streets.
The angle can be used for simple calculation of the coordinates of the vertices in the area of the branch.

2 Likes

Hi @hofk,

Thanks for your suggestion and I will have a try.

In addition, I’d like to ask another question that you mentioned above, page 16.

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.

I followed your code but it didn’t work. I still got the different road widths if observing them carefully.

    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.crossVectors( tangent, binormal ); 
            binormal.crossVectors( normal, tangent ); // new binormal

            normal.y = 0;

            normal.normalize();

            var lxz = Math.sqrt(normal.x*normal.x + normal.z*normal.z);
            var nx = normal.x / lxz;
            var nz = normal.z / lxz;

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

            xyzSet();
            vIdx ++;
        }
    }

My result is:

I have a hunch. Please use a wireframe material so we can see the construction.

Hi @hofk

How about this?

My suspicions were confirmed.

If you draw a circle with three.js and choose a small number for the segments, you get a triangle, square, …hexagon etc.

If you use too few length segments for the road, the road is no longer consistent.

My example with 35 and 5002020-06-11 19.56.13

2020-06-11 19.58.13

This test reveals yet another problem.

If you make the width of the side strips too large in relation to the strongest curvature, this will not work.

But these “faults” lie in the nature of the road construction.

In case of natural serpentines in the mountains, this section is also specially designed.

I hope I could help. :slightly_smiling_face:

Hi @hofk,

Yeah, I got it and thank you for your patience and explanation. Love you :hearts: