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.

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

gLine.setDrawRange ( 0, markerCount + 1 );

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

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

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?

1 Like

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://github.com/hofk/THREEf.js/tree/master/THREEf_90
Totally simplify. An initial approach.

A motorway with four lanes is also possible.

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

<!DOCTYPE html>
<!-- @author hofk -->
<meta charset="utf-8" />
<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 ) {

}

*/

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

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.

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;

1 Like

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

<!DOCTYPE html>
<!-- @author hofk -->
<meta charset="utf-8" />
<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 );

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

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

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! Would be greate to have it amongst official examples. Time to time, people on SO ask about such thing

With black and white material.

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

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.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

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.

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 , I think.

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.

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;

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. soon is now

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

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