• 贝塞尔曲线(面)二三维可视化(Three+d3)


    贝塞尔曲线(面)二三维可视化(Three+d3)

    在学完 games101 几何后开始实践,可视化贝塞尔曲线

    我想实现三维的贝塞尔曲线,用 threejs,但是 threejs 控制太麻烦了,因此,我使用了 d3js 实现二维贝塞尔曲线的控制,threejs 实现三维贝塞尔曲线的可视化

    展示一下二三维贝塞尔曲线的样子

    展示3

    展示1

    功能一:重现二维和三维的贝塞尔曲线;功能二:可对二维贝塞尔曲线进行控制

    理论基础

    首先我们看三个控制点的贝塞尔曲线是如何画出来的

    曲线1

    b0, b1, b2 分别为三个控制点,设置一个自变量 t,t 的取值范围 [0, 1],0 对应一段线条的起始点,1 对应线段的终点。对于第一条线段 b0 b1,t=0 代表 b0 处,t=1 代表 b1 处。设 t 此时为定义域内的某个值,如 0.3,在 b0 b1,b1 b2,上分别取这个比例位置的点 b10,b11。连接 b10,b11,再取 0.3 的点 b20。最终得到的 b20 就是当 t=0.3 时点的位置。

    令 t 从 0 变化到 1,就能够得到一个贝塞尔曲线。

    控制点为四个时,取点方式原理一样

    曲线2

    也是设置一个变量 t,第一次选择 t 位置的点就能将四个控制点的情况转换为三个控制点的情况,如此,我们就能计算任意控制点的贝塞尔曲线了,因为了解了过程原理,最终的计算也就能够理解了,公式如下所示

    公式1

    可以看到,bi 前面的系数为 (1-t+t)^2 的展开式,总结规律得

    \[b^n(t)=b^n_0(t)=\sum^n_{i=0} C^i_n \times t^i \times (1-t)^{n-i} \times b_i \]

    有了这个公式后就可以开始做实践了,在做贝塞尔曲面时需要两个自变量,u,v 替换 t,实质是将 u 取代了 t,然后再让 v 成为新的 t,再次遍历。这里以 16 个点为例,每四个点先做一次贝塞尔曲线转换

    三维1

    这里可以看到,每四个点都做了一次贝塞尔曲线的转换,再这个基础上,把得到的四个贝塞尔曲线上同 u 的点再次进行贝塞尔曲线的转换。

    1. u 从 0 到 1 ,得到了四条曲线
    2. 当 u 为某个值时,得到四条曲线上四个顶点作为控制点
    3. 以控制点为基础,v 从 0 到 1,得到一条曲线
    4. 遍历下去,由一条条曲线也就得到了类似曲面的图形

    曲面1

    代码实践

    在代码中,最重要得是算法的重现,首先看三个控制点如何实现

    // 求得 t 时刻点的位置
    function getFinalPosition(t, controls, length){
        const n = length-1;
        const sum = {x:0, y:0};
        for (let i = 0; i <= n; i++) {
            const Bjn = getCombination(n, i)*Math.pow(t, i)*Math.pow(1-t, n-i);
            sum.x += controls[i].x * Bjn;
            sum.y += controls[i].y * Bjn;
        }
        return sum;
    }
    
    // 求阶乘
    function getFactorial(n){
        let result = 1;
        for (let i = 2; i <= n; i++) {
            result *= i;
        }
        return result;
    }
    
    // 求组合 C
    function getCombination(n, m){
        if(m === 0 || m === n){
            return 1;
        }
        return getFactorial(n)/(getFactorial(m)*getFactorial(n-m))
    }
    
    function fillPoints(){
        const resultPoints = [];
        for (let i = 0; i < NUMBER; i++) {
            const v1 = getFinalPosition(i/NUMBER, data, 4);
            resultPoints.push(v1);
        }
        path.attr('d', line(resultPoints))
    }
    fillPoints()
    

    注意点

    • getFinalPosition 方法就是计算公式的转换
    • 求组合 C 的公式是 n!/(m!*(n-m)!),这里有加速的方法,比如C0n=Cnn
    • fillPoints 方法是将所有点收集起来,最后连成线

    这块是使用了 d3js 进行展示,球和线均手动创建,由于是自定义的图形,因此使用 d3js 自由绘画,十分方便,画点和线的代码如下

    // 点
    const points = canvas.selectAll('.point')
        .data(data)
        .enter()
        .append('circle')
        .attr('cx', d=>d.x)
        .attr('cy', d=>d.y)
        .attr('r', d=>10)
        .attr('fill', '#f9d2bb')
        .call(
            d3.drag()
                .on("drag", (event, d)=>{
                    d.x = event.x;
                    d.y = event.y;
                })
                .on("end", ()=>{
                    console.log(3)
                })
                .on('drag.update', ()=>{
                    points.attr('cx', d=>d.x)
                        .attr('cy', d=>d.y);
                    fillPoints();
                })
        )
    
    const line = d3.line()
        .x(d => {
            return d.x})
        .y(d => d.y);
    
    // 线
    const path = canvas
        .append('path')
        .attr("stroke", "red")
        .attr("stroke-width", 3)
        .attr("fill", "none")
        .attr("stroke-opacity", 0.4);
    

    给四个点添加拖动事件,每次拖动时重现计算线条

    展示2

    在三维情况下复杂一点,但是核心不变

    const NUMBER = 1000;
    for (let u = 0; u < NUMBER; u++) {
        const resultPoints = [];
        const resultColor = [];
        for (let v = 0; v < NUMBER; v++) {
            const acrossPoints = [];
            for (let i = 0; i < 4; i++) {
                const v1 = getFinalPosition(v/NUMBER, points[i], 4);
                acrossPoints.push(v1.clone());
            }
            const v2 = getFinalPosition(u/NUMBER, acrossPoints, 4);
            resultPoints.push(v2.clone());
            const color = new THREE.Color(
                u/NUMBER, 1-u/NUMBER, u/NUMBER)
            resultColor.push(color.r, color.g, color.b);
        }
        const lineGeometry = new THREE.BufferGeometry().setFromPoints(resultPoints);
        lineGeometry.setAttribute('color', new THREE.Float32BufferAttribute(resultColor, 3))
        const lineMaterial = new THREE.LineBasicMaterial({
            vertexColors: true
        })
        const line = new THREE.Line(lineGeometry, lineMaterial);
        scene.add(line)
    }
    

    注意点

    1. getFinalPosition 方法和二维类似,重现方程
    2. 将 u 和 v 进行循环,u 为外层,v 为里层,v 遍历后相当于产生了四条控制线;u 遍历后产生最终的点位
    3. 里面用到了一些 threejs 中的方法,如果感兴趣可以相互交流

    总结

    实现贝塞尔曲线最重要的是理解公式,实现公式的语言和可视化方法有很多种,我采用了 three 展示三维和 d3js 展示二维。代码太凌乱,有兴趣可以交流。

    贝塞尔曲线目前采用了以线取代控制点的方式,但是由于时间关系,没有使用 d3js 实现这个功能,如果点赞够多,给个鼓励,我就有精力实现一下,

    控制点1

    希望读者在看完后能提出意见, 点个赞, 鼓励一下, 我们一起进步. 加油 !!
  • 相关阅读:
    装饰模式(Decorator Pattern)
    适配器模式(Adapter Pattern)
    组合模式
    单例的两种模式
    抽象工厂模式(Abstract Factory Pattern)
    工厂方法模式
    桥接模式
    原型模式(Prototype Pattern)
    建造者模式(Builder Pattern)
    解决使用tomcat服务器发布web项目时出现URL中文乱码的问题
  • 原文地址:https://www.cnblogs.com/xiaxiangx/p/15795487.html
Copyright © 2020-2023  润新知