ProfiledContourGeometry

buffergeometry

#1

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();
      shiftMatrix.applyToBufferAttribute(cloneProfile);
      rotationMatrix.applyToBufferAttribute(cloneProfile);
      translationMatrix.applyToBufferAttribute(cloneProfile);

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

Mesh extruding?
OffsetContour function
Any better implementation of ExtrudeGeometry?
Construction of rectangle using length and width parameter
#2

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


#3

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?


#4

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