offsetContour 3d shape

by black strings

HTML

<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js"></script>
<script>
  function ProfiledTubularGeometry(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 prevP = contour[i - 1 < 0 ? contour.length - 1 : i - 1];
      let currP = contour[i];
      let nextP = contour[i + 1 == contour.length ? 0 : i + 1];
      
      let v1 = new THREE.Vector3().subVectors(prevP, currP);
      let v2 = new THREE.Vector3().subVectors(nextP, currP);
      
      let midV = new THREE.Vector3().addVectors(v1, v2).multiplyScalar(0.5);
      let midNormal = new THREE.Vector3();
      let tri = new THREE.Triangle(prevP, currP, nextP);
      tri.getNormal(midNormal);
      midV.applyAxisAngle(midNormal, Math.PI * 0.5);
      
      let angle = v2.angleTo(v1);
      let halfAngle = angle * 0.5;
			
      let hA = halfAngle;
      if (!contourClosed){
      	if (i == 0 || i == contour.length - 1) {hA = 0;}
      }
      
      let shift = Math.tan(hA);
      let shiftMatrix = new THREE.Matrix4().set(
             1, 0, 0, 0, 
             0, 1, shift, 0,
             0, 0, 1, 0,
             0, 0, 0, 1
      );
			
      if (!contourClosed){
        if (i === 0) midV.copy(v2).negate();
        if (i...

CSS

body {
  overflow: hidden;
  margin: 0;
}

JavaScript

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(-50, 80, 100);
var renderer = new THREE.WebGLRenderer({
  antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

var controls = new THREE.OrbitControls(camera, renderer.domElement);

var light = new THREE.DirectionalLight(0xffffff, 0.75);
light.position.setScalar(10);
scene.add(light);
scene.add(new THREE.AmbientLight(0xffffff, 0.25));

var helper = new THREE.GridHelper(20, 20);
//helper.geometry.rotateX(Math.PI * -.5);
scene.add(helper);

var profileShape = new THREE.Shape();
//profileShape.absarc(0, 0, 3, 0, Math.PI * 2);
profileShape.moveTo(0,0);
profileShape.lineTo(0, 1);

var tubularSegments = [
	new THREE.Vector3(0, 0, 0),
  new THREE.Vector3(10, 0, 0),
  new THREE.Vector3(10, 10, 0),
  new THREE.Vector3(20, 10, 0),
];

var geometry = ProfiledTubularGeometry(profileShape, tubularSegments, false, true);
var fullProfile = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({
  color: "green",
  wireframe: true
}));
//fullProfile.position.set(-7, 1, -5);
scene.add(fullProfile);

var line = new THREE.Line(new THREE.BufferGeometry().setFromPoints(tubularSegments), new THREE.LineBasicMaterial({color: "yellow", depthTest: false}));
scene.add(line);

render();

function render() {
  requestAnimationFrame(render);
  renderer.render(scene, camera);
}