Threejs管道效果实现

4,658 阅读2分钟

在产品需求中有管道展示的需求,管道展示有两个点需要注意,一是管道是具有宽度,需要有半径;二是管道拐角处理,需要圆滑。 image.png

image.png

1. TubeGeometry

由于管道需要半径,用Line就不合适。TubeGeometry正合适,拥有半径的参数控制。

image.png

### TubeGeometry(path : Curve, tubularSegments : Integer, radius : Float, radialSegments : Integer, closed : Boolean)

path — Curve - 一个由基类Curve继承而来的3D路径。 Default is a quadratic bezier curve.  
tubularSegments — [Integer](<> "Integer"- 组成这一管道的分段数,默认值为64radius — [Float](<> "Float"- 管道的半径,默认值为1radialSegments — [Integer](<> "Integer"- 管道横截面的分段数目,默认值为8closed — [Boolean](<> "Boolean") 管道的两端是否闭合,默认值为false

2. CatmullRomCurve3 VS CurvePath

管道的路径,是通过TubeGeometry的第一个参数设置的。实际使用的时候,定义的路径都是折线,相当于都是lineTo。由于管道是有半径的,如果直接折线,拐角就被折扁。所以需要对点进行处理。下面提供两种方式。

先假设存在点数组

let points = [
    {"x": 0, "y": 0, "z": 0},
    {"x": 15, "y": 0, "z": 0},
    {"x": 15, "y": 20, "z": 0},
    {"x": 15, "y": 20, "z": 20},
    {"x": 35, "y": 20, "z": 20},
    {"x": 55, "y": 0, "z": 0},
    {"x": 75,"y": 0,"z": 0},
 ];
points = points.map(({x,y,z}) => new THREE.Vector3(x,y,z));

2.1 CatmullRomCurve3

CatmullRomCurve3使用Catmull-Rom算法, 从一系列的点创建一条平滑的三维样条曲线。

const curve = new THREE.CatmullRomCurve3(points);
curve.curveType = 'centripetal';
curve.closed = false;
const geometry1 = new THREE.TubeGeometry(curve, 200, 2, 10, false );
const material1 = new THREE.MeshPhongMaterial({ color: 0xfff000 });
const mesh1 = new THREE.Mesh( geometry1, material1 );
scene.add( mesh1 );

2.2 CurvePath

CurvePath可以理解成是Curve的集合,我们需要将points中的点转换为多个Curve,支持的Curve有CubicBezierCurve3QuadraticBezierCurve3LineCurve3

getRoundCornerPath方法负责将points转换为Curve。

const path = new THREE.CurvePath();
getRoundCornerPath(points, 10, path)

const geometry = new THREE.TubeGeometry(path, 200, 2, 10, false );
const material = new THREE.MeshPhongMaterial({ color: 0x00ff00 });
const mesh = new THREE.Mesh( geometry, material );

scene.add( mesh );

function getRoundCornerPath(points, radius, path) {
    let p0 = points[0];
    moveTo(path,p0)
    let v1,v2 = p0;
    for (let i = 1; i < points.length - 1; i++) {
        let pre = points[i - 1],
                p = points[i],
                next = points[i + 1];
        let sub1 = p.clone().sub(pre).setLength(radius),
                sub2 = next.clone().sub(p).setLength(radius);
        v1 = p.clone().sub(sub1);
        v2 = p.clone().add(sub2);
        // path.lineTo(v1);
        // lineTo(path,v1,v1)
        // path.curveTo(p.x, p.y, p.z, v2.x, v2.y, v2.z);
        curveTo(path,v1,p,v2)
    }
    let pl = points[points.length - 1];
    // path.lineTo(pl);
    lineTo(path,v2,pl)
    return path;
}
function moveTo(path,p0){
    var curve = new THREE.LineCurve3(
        new THREE.Vector3(p0.x, p0.y, p0.z), 
        new THREE.Vector3(p0.x, p0.y, p0.z)
    );
    path.add(curve)
}
function lineTo(path,p0,p1){
    var curve = new THREE.LineCurve3(
        new THREE.Vector3(p0.x, p0.y, p0.z), 
        new THREE.Vector3(p1.x, p1.y, p1.z)
    );
    path.add(curve)
}
function curveTo(path,p0,p1,p2){
    var curve = new THREE.QuadraticBezierCurve3(
            new THREE.Vector3(p0.x, p0.y, p0.z), 
            new THREE.Vector3(p1.x, p1.y, p1.z), 
            new THREE.Vector3(p2.x, p2.y, p2.z)
    );
    path.add(curve)
}

3. 效果

这是上面两种方式的效果,黄色是2.1的效果;绿色是2.2的效果。对比两种方式看不出差别 image.png

4. 注意

有个点需要注意,路径的长度比较长,但是TubeGeometry中的tubularSegments参数,设置的小的话,会让管道不丝滑。看下面的效果,黄色的设置tubularSegments值为20。这里建议通过路径长度计算出一个值。

image.png