Simple Rectangular Geometry Extrusion (Anyone?)

I am struggling to do something which should be quite easy to do, but I have been unable to find any example which addresses this scenario. Essentially all I want to do is extrude profiles along a rectangular path:

This is the profile:

This is what I expect the end result to be:

What I actually get is this:
image

I would appreciate it if someone could look at the code below and tell me what I am doing wrong:
'------------------------------------------------------------------------------------------------------------------------------------------

<script>

		var container;

		var camera, scene, renderer, controls;

		init();
		animate();

		function init() {
            //SCENE SETUP
			renderer = new THREE.WebGLRenderer();
			renderer.setPixelRatio( window.devicePixelRatio );
			renderer.setSize( window.innerWidth, window.innerHeight );
			document.body.appendChild( renderer.domElement );

			scene = new THREE.Scene();
			scene.background = new THREE.Color( 0x222222 );

			camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
			camera.position.set( -250, 150, 200 );
            camera.lookAt(new THREE.Vector3(0, -50, 0))
			controls = new THREE.TrackballControls( camera, renderer.domElement );
			controls.minDistance = 200;
			controls.maxDistance = 500;

			scene.add( new THREE.AmbientLight( 0x222222 ) );

			var light = new THREE.PointLight( 0xffffff );
			light.position.copy( camera.position );
			scene.add( light );

   
            //PROFILE SHAPE
            var spts = [];
            spts.push(new THREE.Vector2(0, 0));
            spts.push(new THREE.Vector2(10, 0));
            spts.push(new THREE.Vector2(10, 25));
            spts.push(new THREE.Vector2(-5, 25));
            spts.push(new THREE.Vector2(-5, 20));
            spts.push(new THREE.Vector2(0, 20));
           

            //PATH POINTS
            var ppth = []
            ppth.push(new THREE.Vector3(0,0,10));
            ppth.push(new THREE.Vector3(100, 0,10));
            ppth.push(new THREE.Vector3(100, 200,10));
            ppth.push(new THREE.Vector3(0, 200,10));

            //-----------------------------------------------EXTRUSION PATH AS A CURVEPATH 
            var cpth = new THREE.CurvePath()

            //THE FOLLOWING STATEMENT APEARS TO CREATE NO NEW CURVES
            //cpth.createGeometry(ppth)

            //ADD CURVES EXPLICITELY
            var v1 = new THREE.LineCurve(new THREE.Vector3(0,0,0), new THREE.Vector3(100,0,0));
            var v2 = new THREE.LineCurve(new THREE.Vector3(100,0,0), new THREE.Vector3(100,200,0));
            var v3 = new THREE.LineCurve(new THREE.Vector3(100, 200, 0), new THREE.Vector3(0, 200, 0));
            var v4 = new THREE.LineCurve(new THREE.Vector3(0, 200, 0), new THREE.Vector3(0, 0, 0));

            cpth.add(v1);
            cpth.add(v2);
            cpth.add(v3);
            cpth.add(v4);
            cpth.autoClose = true;
            //cpth.update;

            //SET EXTRUSION PATH TO CURVEPATH 
            expth = cpth

            //EXTRUSION SETTINGS
            var extrudeSettings = {
                steps: 200,
                bevelEnabled: false,
                extrudePath: expth
            };

            // GENERATE SCENE GEOMETRY
			var shape = new THREE.Shape( spts );

            var geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
          

			var material2 = new THREE.MeshLambertMaterial( { color: 0xff8000, wireframe: false } );

			var mesh = new THREE.Mesh( geometry, material2 );
            mesh.position.x = -50;
            mesh.position.y = -100;

			scene.add( mesh );
        }
		
</script>

For testing: https://jsfiddle.net/texvqohy/

I’m not sure ExtrudeGeometry can handle this scenario. In most cases the extrude path of ExtrudeGeometry is a spline curve like in this example: https://threejs.org/examples/#webgl_geometry_extrude_shapes

Maybe you should post this issue at github.

If you change the extrude path to a spline, your profile shape seems to work: https://jsfiddle.net/texvqohy/2/

The overlapping comes from the origin of the profile, if you set the y axis negative the edges basically work, but it makes a curve. Last time i used ExtrudeGeometry i just used a straight path without line segments, why using a curve with LineCurve segments? It rounds it and creates segments at given steps, only by decreasing the steps it gets close to the shape, i guess straight paths should fix it.
https://s2.postimg.org/k65ahvx8p/jsf.png

I do think this is worth addressing as your code base as this scenario is a
very common context in architecture / product design where paths are often
more rectangular or have sharp corners.
The spline variant does not give me any option to generate the shape I want
with sharp corners to generate a rectangular frame. Seems like I will have
to calculate the individual polygons making up the shape manually.

Thanks for taking the time to respond to my query.

I met the same issue: giving the profile that consist of arc or straight line, extrude the profile along the path that is a rectangle or ‘half rectangle’(two perpendicular straight lines).

Originally I got the same result as you:
868621570098946016

After reading the code of three.js, I found that it may exist mistake when computing the vertices in the corner.
Then I tried to change the code in computeFrenetFrames to modify the initial normal vector(I changed all the initial normal vector to negative), then I got the result:

微信图片_20170914151208

The corner is not smooth because the steps of extrudeSettings is only 50.

When I used two perpendicular straight lines as extrude path, the smoothing result is as this:

微信图片_20170914153004

Thanks for your response Hong.
I think the only way to achieve a frame with sharp corners in 3.js will be to develop an algorithm which calculates the facet polygons individually along the path.

Have you already found a solution :question:

I’ve thought about it and found that you have to distinguish the angles

(orthogonal, acute, obtuse)

Then you can determine the extension of the edge ( h ) and position the vertex.

Change value of profile[ 3 ] = 15 for tests.

<!DOCTYPE html>
<html lang="de">
<head>
	<title> test p </title>
	<meta charset="utf-8" />
</head>
<body>
	<!-- D E B U G !!!!!!!!!--- -->		
	<div id="output"> output </div> 									
</body>
<script>

'use strict'

output.innerHTML = " test output: " + "</br >";

var  x1, y1, z1, x2, y2, z2, x3, y3, z3;
var q1, q2, q3;
var d, h;
var m1, m2;
var alpha;

var shape = [ 1,1, -1,2, -2,0, 1,-1 ]; // 4 corners (shape closed 1,1)
var profile = [ -40,10, 10,15, 20,-30]; // 2 profile bars // change value of profile[ 3 ] = 15 for tests

d = Math.abs( shape[ 2 ] - shape[ 0 ] ); // only a test

x1 = profile[ 0 ] -  profile[ 2 ];
y1 = profile[ 1 ] -  profile[ 3 ];

x2 = profile[ 4 ] -  profile[ 2 ];
y2 = profile[ 5 ] -  profile[ 3 ];

x3 = profile[ 4 ] -  profile[ 0 ];
y3 = profile[ 5 ] -  profile[ 1 ];

m1 = y1 / x1;
m2 = y2 / x2;

if ( m1 !== -1 / m2 ) {

	alpha = Math.abs( Math.atan( ( m2 - m1 ) / ( 1 + m1 * m2 ) ) / 2 );
	
} else { 
	
	alpha = Math.PI / 4;

}	

q1 = x1 * x1 + y1 * y1;
q2 = x2 * x2 + y2 * y2;
q3 = x3 * x3 + y3 * y3;

output.innerHTML = output.innerHTML  + " orthogonal: " + ( m1 === -1 / m2 ) +   " >> alpha:  " + Math.floor( alpha * 1000) / 1000;

output.innerHTML = output.innerHTML  +" q123: " + q1 + ", " + q2 + ", " + q3;

if ( q1 + q2 >= q3) {

	h = d / Math.tan( alpha );
	output.innerHTML = output.innerHTML  +  " acute angled >> extension h = " + Math.floor( h * 1000) / 1000;
	
} else {
	
	h = d * Math.tan( alpha );
	output.innerHTML = output.innerHTML  +  " obtuse angled  >> extension  h = " + Math.floor( h * 1000) / 1000;

}

</script>
</html>

About 2 years ago, when I knew almost nothing, I solved such task with THREE.ExtrudeGeometry() and CSG :slight_smile:

http://west77.ru/content/r70/bouquet.html (in the beginning it’s almost black, as it needs time to load textures)

Nowadays, I wouldn’t do it like that :smiley:

I came up with a similar solution :slight_smile: Now, I think, that I re-invented THREE.TubeGeometry() or THREE.ExtrudeGeometry() :joy:

A function has two parameters: THREE.Shape() for a profile, array of THREE.Vector2() for a contour.
It calculates an angle between two edges of contour and then consiquently applies three matrices of shear/shift, rotation and translation to the points of the profile at each point of contour.

The solution is rough and raw, and it needs lots of improvements and optimizations. But anyway, it does what it was intended to.

https://jsfiddle.net/prisoner849/hkkbo0gx/

  function ProfiledContourGeometry(profileShape, contour) {

    let profileGeometry = new THREE.ShapeBufferGeometry(profileShape);
    profileGeometry.rotateX(Math.PI * .5);
    let profile = profileGeometry.attributes.position;

    let profilePoints = new Float32Array(profile.count * contour.length * 3);

    for (let i = 0; i < contour.length; i++) {
      let v1 = new THREE.Vector2().subVectors(contour[i - 1 < 0 ? contour.length - 1 : i - 1], contour[i]);
      let v2 = new THREE.Vector2().subVectors(contour[i + 1 == contour.length ? 0 : i + 1], contour[i]);
      let angle = v2.angle() - v1.angle();
      let halfAngle = angle * .5;

      let shift = Math.tan(halfAngle - Math.PI * .5);
      console.log(shift);
      let shiftMatrix = new THREE.Matrix4().set(
        1, 0, 0, 0, -shift, 1, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1
      );

      let tempAngle = v2.angle() + Math.PI * .5;
      let rotationMatrix = new THREE.Matrix4().set(
        Math.cos(tempAngle), -Math.sin(tempAngle), 0, 0,
        Math.sin(tempAngle), Math.cos(tempAngle), 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1
      );

      let translationMatrix = new THREE.Matrix4().set(
        1, 0, 0, contour[i].x,
        0, 1, 0, contour[i].y,
        0, 0, 1, 0,
        0, 0, 0, 1,
      );

      let cloneProfile = profile.clone();
      shiftMatrix.applyToBufferAttribute(cloneProfile);
      rotationMatrix.applyToBufferAttribute(cloneProfile);
      translationMatrix.applyToBufferAttribute(cloneProfile);

      profilePoints.set(cloneProfile.array, cloneProfile.count * i * 3);
    }

    let fullProfileGeometry = new THREE.BufferGeometry();
    fullProfileGeometry.addAttribute("position", new THREE.BufferAttribute(profilePoints, 3));
    let index = [];

    for (let i = 0; i < contour.length; i++) {
      for (let j = 0; j < profile.count; j++) {
        let currCorner = i;
        let nextCorner = i + 1 == contour.length ? 0 : i + 1;
        let currPoint = j;
        let nextPoint = j + 1 == profile.count ? 0 : j + 1;

        let a = nextPoint + profile.count * currCorner;
        let b = currPoint + profile.count * currCorner;
        let c = currPoint + profile.count * nextCorner;
        let d = nextPoint + profile.count * nextCorner;


        index.push(a, b, d);
        index.push(b, c, d);
      }
    }

    console.log(index);
    fullProfileGeometry.setIndex(index);
    fullProfileGeometry.computeVertexNormals();

    return fullProfileGeometry;
  }
4 Likes

The result looks great. If the code is still made perfect, it will surely find its way into three. js :interrobang:

Since my addon (geometry with functions) is soon finished, I had considered starting an addon THREEg (geometry collection). So I look at interesting ones that don’t fit into three. js itself. I’m thinking about Christmas stars now.
:christmas_tree: :star2:


Addendum:
The contour is always closed. Maybe it makes sense to do this with a parameter?

Yeah, I thought about it already :slight_smile: I’ll make it later, as right now I’m at work :nerd_face:

Here is a version with open/closed contour :slight_smile:
Looks like it needs .openEnded parameter, like in THREE.CylinderGeometry().

https://jsfiddle.net/prisoner849/bygy1xkt/

  function ProfiledContourGeometry(profileShape, contour, contourClosed) {
    
    contourClosed = contourClosed !== undefined ? contourClosed : true;
		
    let profileGeometry = new THREE.ShapeBufferGeometry(profileShape);
    profileGeometry.rotateX(Math.PI * .5);
    let profile = profileGeometry.attributes.position;

    let profilePoints = new Float32Array(profile.count * contour.length * 3);

    for (let i = 0; i < contour.length; i++) {
      let v1 = new THREE.Vector2().subVectors(contour[i - 1 < 0 ? contour.length - 1 : i - 1], contour[i]);
      let v2 = new THREE.Vector2().subVectors(contour[i + 1 == contour.length ? 0 : i + 1], contour[i]);
      let angle = v2.angle() - v1.angle();
      let halfAngle = angle * .5;
			
      let hA = halfAngle;
      let tA = v2.angle() + Math.PI * .5;
      if (!contourClosed){
      	if (i == 0 || i == contour.length - 1) {hA = Math.PI * .5;}
        if (i == contour.length - 1) {tA = v1.angle() - Math.PI * .5;}
      }
      
      let shift = Math.tan(hA - Math.PI * .5);
      let shiftMatrix = new THREE.Matrix4().set(
        1, 0, 0, 0, -shift, 1, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1
      );
			
      
      let tempAngle = tA;
      let rotationMatrix = new THREE.Matrix4().set(
        Math.cos(tempAngle), -Math.sin(tempAngle), 0, 0,
        Math.sin(tempAngle), Math.cos(tempAngle), 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1
      );

      let translationMatrix = new THREE.Matrix4().set(
        1, 0, 0, contour[i].x,
        0, 1, 0, contour[i].y,
        0, 0, 1, 0,
        0, 0, 0, 1,
      );

      let cloneProfile = profile.clone();
      shiftMatrix.applyToBufferAttribute(cloneProfile);
      rotationMatrix.applyToBufferAttribute(cloneProfile);
      translationMatrix.applyToBufferAttribute(cloneProfile);

      profilePoints.set(cloneProfile.array, cloneProfile.count * i * 3);
    }

    let fullProfileGeometry = new THREE.BufferGeometry();
    fullProfileGeometry.addAttribute("position", new THREE.BufferAttribute(profilePoints, 3));
    let index = [];
		
    let lastCorner = contourClosed == false ? contour.length - 1: contour.length;
    for (let i = 0; i < lastCorner; i++) {
      for (let j = 0; j < profile.count; j++) {
        let currCorner = i;
        let nextCorner = i + 1 == contour.length ? 0 : i + 1;
        let currPoint = j;
        let nextPoint = j + 1 == profile.count ? 0 : j + 1;

        let a = nextPoint + profile.count * currCorner;
        let b = currPoint + profile.count * currCorner;
        let c = currPoint + profile.count * nextCorner;
        let d = nextPoint + profile.count * nextCorner;


        index.push(a, b, d);
        index.push(b, c, d);
      }
    }

    fullProfileGeometry.setIndex(index);
    fullProfileGeometry.computeVertexNormals();

    return fullProfileGeometry;
  }
6 Likes

You’re extremely quick at work and tree.js things .:running_man:

It takes me longer to get a halfway overview of the result.

I had thought to make the parameter contur with Vector3 as 3D.
I don’t yet have the full overview of three.js to see how much effort it takes.

But not yet another solution so quickly - I am an old man and I don’t follow so quickly. :hourglass_flowing_sand:

1 Like

hi bro

your code is very powerful

but i could not understand it .

Can you tell me the name of the algorithm used in it?

so i can search for it

tks a lot

@tsingwong
Hi!
Actually, it was made from a scratch )) So, the name of the algorithm is unknown )))

3 Likes

This is amazing. You should publish this somewhere or submit a merge request, your code is very fast and efficient.

Hi, prisoner849,

I just add more points to the path in your example but the tube are twisted ( https://jsfiddle.net/ozinfo/n4aef83z/17/) . Do you know what is wrong there?

Thanks for your help in advance.

ouyang

this is very clever and I fully get how it works. however, on very sharp turns, you end up with long spikes if you can’t miter or round the cap. a simple miter would be sufficient. can you think of a simple / clever way to accomplish this, too?