根据three.js中TubeGeometry几何体对源码做了些改动用于绘制变径管道,暂且命名为VarTubeGeometry

/**
 * @author icecream
 * @version 2.0.0
 * @description 绘制变径管道 curve, radiusArr, 20, valuesArr, mapValuesArr, LUT
 * @param curve <THREE.CatmullRomCurve3>  轨迹
 * @param radiusArr <Array> [['井深'<Number>,'半径'<Number>]...] 半径数组,
 * @param radialSegments <Number> 管道截面分段数
 * @param valuesArr <Array> ['值域最小值'<Number>,'值域最大值'<Number>] 半径值域,
 * @param mapValuesArr <Array>  ['映射最小值'<Number>,'映射最大值'<Number>] 半径值域映射
 * @param LUT <THREE.Lut> 根据半径给顶点上色
 */
THREE.VarTubeGeometry = class VarTubeGeometry extends THREE.BufferGeometry {
  constructor(path, radiusArr, radialSegments = 8, valuesArr, mapValuesArr, lut) {
    super();
    const tubularSegments = radiusArr.length - 1
    const wellLength = path.getLength()
    this.type = 'VarTubeGeometry';
    this.parameters = {
      lut: lut,
      path: path,
      tubularSegments: tubularSegments,
      radialSegments: radialSegments,
      closed: closed
    };

    if (lut) {
      lut.setMax(valuesArr[1])
      lut.setMin(valuesArr[0])
    }
    const frames = computeFrenetFrames(path, radiusArr); // expose internals

    this.tangents = frames.tangents;
    this.normals = frames.normals;
    this.binormals = frames.binormals; // helper variables

    const vertex = new THREE.Vector3();
    const normal = new THREE.Vector3();
    const uv = new THREE.Vector2();
    let P = new THREE.Vector3(); // buffer

    const vertices = [];
    const normals = [];
    const uvs = [];
    const colors = [];
    const indices = []; // create buffer data

    generateBufferData(); // build geometry
    this.setIndex(indices);
    this.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
    this.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));
    this.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2)); // functions
    this.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));

    function generateBufferData() {
      for (let i = 0; i <= tubularSegments; i++) {
        generateSegment(i, radiusArr[i]);
      } // if the geometry is not closed, generate the last row of vertices and normals

      generateUVs(); // finally create faces

      generateIndices();
    }

    function generateSegment(i, arr) {
      // we use getPointAt to sample evenly distributed points from the given path
      P = path.getPointAt(arr[0] / wellLength, P); // retrieve corresponding normal and binormal
      const radius = THREE.Math.mapLinear(arr[1], valuesArr[0], valuesArr[1], mapValuesArr[0], mapValuesArr[1]);
      const N = frames.normals[i];
      const B = frames.binormals[i]; // generate normals and vertices for the current segment
      var color = null;
      if (lut) {
        color = lut.getColor(arr[1])
      }
      for (let j = 0; j <= radialSegments; j++) {
        const v = j / radialSegments * Math.PI * 2;
        const sin = Math.sin(v);
        const cos = -Math.cos(v); // normal

        normal.x = cos * N.x + sin * B.x;
        normal.y = cos * N.y + sin * B.y;
        normal.z = cos * N.z + sin * B.z;
        normal.normalize();
        normals.push(normal.x, normal.y, normal.z); // vertex

        if (color) {
          colors.push(color.r, color.g, color.b)
        }

        vertex.x = P.x + radius * normal.x;
        vertex.y = P.y + radius * normal.y;
        vertex.z = P.z + radius * normal.z;
        vertices.push(vertex.x, vertex.y, vertex.z);
      }
    }

    function generateIndices() {
      for (let j = 1; j <= tubularSegments; j++) {
        for (let i = 1; i <= radialSegments; i++) {
          const a = (radialSegments + 1) * (j - 1) + (i - 1);
          const b = (radialSegments + 1) * j + (i - 1);
          const c = (radialSegments + 1) * j + i;
          const d = (radialSegments + 1) * (j - 1) + i; // faces

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

    function computeFrenetFrames(curve, arrRadius) {
      // see http://www.cs.indiana.edu/pub/techreports/TR425.pdf
      const normal = new THREE.Vector3();
      const tangents = [];
      const seg = radiusArr.length - 1
      const normals = [];
      const binormals = [];
      const vec = new THREE.Vector3();
      const mat = new THREE.Matrix4(); // compute the tangent vectors for each segment on the curve
      const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
      for (let i = 0; i < arrRadius.length; i++) {
        const u = arrRadius[i][0] / wellLength;
        tangents[i] = curve.getTangentAt(u, new THREE.Vector3());
        tangents[i].normalize();
      } // select an initial normal vector perpendicular to the first tangent vector,
      // and in the direction of the minimum tangent xyz component


      normals[0] = new THREE.Vector3();
      binormals[0] = new THREE.Vector3();
      let min = Number.MAX_VALUE;
      const tx = Math.abs(tangents[0].x);
      const ty = Math.abs(tangents[0].y);
      const tz = Math.abs(tangents[0].z);

      if (tx <= min) {
        min = tx;
        normal.set(1, 0, 0);
      }

      if (ty <= min) {
        min = ty;
        normal.set(0, 1, 0);
      }

      if (tz <= min) {
        normal.set(0, 0, 1);
      }

      vec.crossVectors(tangents[0], normal).normalize();
      normals[0].crossVectors(tangents[0], vec);
      binormals[0].crossVectors(tangents[0], normals[0]); // compute the slowly-varying normal and binormal vectors for each segment on the curve

      for (let i = 1; i <= seg; i++) {
        normals[i] = normals[i - 1].clone();
        binormals[i] = binormals[i - 1].clone();
        vec.crossVectors(tangents[i - 1], tangents[i]);

        if (vec.length() > Number.EPSILON) {
          vec.normalize();
          const theta = Math.acos(clamp(tangents[i - 1].dot(tangents[i]), -1, 1)); // clamp for floating pt errors

          normals[i].applyMatrix4(mat.makeRotationAxis(vec, theta));
        }

        binormals[i].crossVectors(tangents[i], normals[i]);
      } // if the curve is closed, postprocess the vectors so the first and last normal vectors are the same

      return {
        tangents: tangents,
        normals: normals,
        binormals: binormals
      };
    }

    function generateUVs() {
      for (let i = 0; i <= tubularSegments; i++) {
        for (let j = 0; j <= radialSegments; j++) {

          uv.x = i / tubularSegments;
          uv.y = j / radialSegments;
          uvs.push(uv.x, uv.y);
        }
      }
    }
  }
}

demo

var curve = new THREE.CatmullRomCurve3([
      [10, 20, 15],
      [16, 10, 20],
      [10, 0, 35],
      [45, 30, 20],
      [86, 60, 63],
      [93, 20, 67],
      [10, 60, 55]
    ].map(el => new THREE.Vector3(...el)));
    var radiusArr = []
    valuesArr = [0, 10];
    mapValuesArr = [1, 1.5];
    for (var a = 0; a < curve.getLength(); a++) {
      radiusArr.push([a, Math.random() * 20])
    }
    const LUT = new THREE.Lut()
    var _geo = new THREE.VarTubeGeometry(curve, radiusArr, 20, valuesArr, mapValuesArr, LUT);
    _materialData = new THREE.MeshPhongMaterial({
      side: THREE.DoubleSide,
      vertexColors: true,
    });
    var _mesh = new THREE.Mesh(_geo, _materialData)
    scene.add(_mesh)

效果:在这里插入图片描述
一下感觉比之前的上档次了。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐