ProfiledContourGeometry

Evolving of that post

ProfiledContourGeometry3

The function has four parameters:

  1. THREE.Shape() for a profile;
  2. array of THREE.Vector2() for a contour;
  3. a boolean value for closing the contour (default is true);
  4. a boolean value for open ends (default is false).

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 still rough and raw, and it needs lots of improvements and optimizations (a result geometry has no uvs, and I’m not sure how valid the trick with flipping of a shape).

https://jsfiddle.net/prisoner849/597ecnh8/

  function ProfiledContourGeometry(profileShape, contour, contourClosed, openEnded) {
    
    contourClosed = contourClosed !== undefined ? contourClosed : true;
    openEnded = openEnded !== undefined ? openEnded : false;
    openEnded = contourClosed === true ? false : openEnded;
		
    let profileGeometry = new THREE.ShapeBufferGeometry(profileShape);
    let flipProfileGeometry = flipShapeGeometry(profileGeometry);
    profileGeometry.rotateX(Math.PI * 0.5);
    let profile = profileGeometry.attributes.position;
		
    let addEnds = openEnded === false ? 2 : 0;
    let profilePoints = new Float32Array(profile.count * (contour.length + addEnds) * 3);
		
    let endProfiles = [];
    
    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 * 0.5;
			
      let hA = halfAngle;
      let tA = v2.angle() + Math.PI * 0.5;
      if (!contourClosed){
      	if (i == 0 || i == contour.length - 1) {hA = Math.PI * 0.5;}
        if (i == contour.length - 1) {tA = v1.angle() - Math.PI * 0.5;}
      }
      
      let shift = Math.tan(hA - Math.PI * 0.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();
      cloneProfile.applyMatrix4(shiftMatrix);
      cloneProfile.applyMatrix4(rotationMatrix);
      cloneProfile.applyMatrix4(translationMatrix);

      profilePoints.set(cloneProfile.array, cloneProfile.count * i * 3);
      if (openEnded === false && (i === 0 || i === contour.length - 1)){
      	endProfiles.push(cloneProfile);
      }
    }
    
    endProfiles.forEach((ep, idx) => {
    	profilePoints.set(ep.array, ep.count * (contour.length + idx) * 3)
    });

    let fullProfileGeometry = new THREE.BufferGeometry();
    fullProfileGeometry.setAttribute("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);
      }
    }
    
    if (openEnded === false){
    	// add indices from profile geometries
      flipProfileGeometry.index.array.forEach(i => {index.push(i +  profile.count * (contour.length))});
      profileGeometry.index.array.forEach(i =>{index.push(i + profile.count * (contour.length + 1))});
      
    }

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

    return fullProfileGeometry;
  }
  
  function flipShapeGeometry(shapeGeometry) {
    let flipGeom = shapeGeometry.clone();
    for (let i = 0; i < flipGeom.attributes.position.count; i++) {
      flipGeom.attributes.position.array[i * 3] *= -1;
    }
    flipGeom.attributes.position.needsUpdate = true;

    var index = flipGeom.index.array;
    for (let i = 0; i < index.length; i += 3) {
      let v2 = index[i + 1];
      let tmp = v2;
      let v3 = index[i + 2];
      index[i + 1] = index[i + 2];
      index[i + 2] = tmp;
    }
    flipGeom.computeVertexNormals();
    return flipGeom;
  }
11 Likes

This is beautiful. I’ll be watching this topic.

This solution also allows for capping or cross-sectioning. Do you think it’ll be possible to apply a different color or texture to the cap?

@ssaras
Yes, if one day (when I have time) I’ll add support for groups and calculation of uvs :slight_smile:

1 Like

@prisoner849, Great job. That is what I am looking for. Is that possible to change profile01 to profile02 from one point to next point in the middle of a same contour? I am also thinking how to make Y joints or X joints for three or four profiles meeting at one point. Any suggestions would be appreciated. Thanks a lot.

ouyang

Thank you so much! This really helped me out.

Here’s a little update to

shiftMatrix.applyToBufferAttribute(cloneProfile);
rotationMatrix.applyToBufferAttribute(cloneProfile);
translationMatrix.applyToBufferAttribute(cloneProfile);

exchange to this:

cloneProfile.applyMatrix4(shiftMatrix);
cloneProfile.applyMatrix4(rotationMatrix);
cloneProfile.applyMatrix4(translationMatrix);
1 Like

@vin-ni
That’s the power of the community :metal:
Thanks for the reminding about that fix :slight_smile: I did it in this SO answer, but forgot to put these changes in this topic :slight_smile:

@prisoner849 How would I decrease the triangles size?
On a shape like this I get 85k triangles, which is ways too much i think.

How could I best decrease the amount?

1 Like

I ended up doing

      if (i && i % 3 === 0) {
        // push only every nth vertice
        tempGeometry.push(pos);
      } else if (tempBuf.vertices.length - 1 === i) {
        // if its the last one, push it anyway
        tempGeometry.push(pos);
      }

and for the shape

const profileGeometry = new ShapeBufferGeometry(profileShape, 3); //12 is standard

2 Likes

@prisoner849 Do you have any advice for texturing these kinds of shapes? I’m struggling to get anything but a solid color when applying a texture.

@Nathan Then you better have a look at this topic: ProfiledContourGeometry MultiMaterial
as @hofk improved that idea :slight_smile:

1 Like