• 【数学篇】08 # 如何利用三角剖分和向量操作描述并处理多边形?


    说明

    【跟月影学可视化】学习笔记。

    图形学中的多边形是什么?

    多边形又可以分为简单多边形和复杂多边形。

    • 简单多边形:如果一个多边形的每条边除了相邻的边以外,不和其他边相交。
    • 凸多边形:如果一个多边形中的每个内角都不超过 180°。

    在这里插入图片描述

    不同的图形系统如何填充多边形?

    1. Canvas2D 如何填充多边形?

    Canvas2D 的 fill 还支持两种填充规则:

    • nonzero:不管有没有相交的边,只要是由边围起来的区域都一律填充。
    • evenodd:根据重叠区域是奇数还是偶数来判断是否填充的
    DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8" />
            <meta http-equiv="X-UA-Compatible" content="IE=edge" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
            <title>Canvas2D 如何填充多边形title>
            <style>
                canvas {
                    border: 1px dashed salmon;
                }
            style>
        head>
        <body>
            <canvas width="512" height="512">canvas>
            <script type="module">
                import { Vector2D } from "./common/lib/vector2d.js";
    
                const canvas = document.querySelector("canvas");
                const ctx = canvas.getContext("2d");
                const { width, height } = canvas;
                const w = 0.5 * width,
                    h = 0.5 * height;
                ctx.translate(w, h);
                ctx.scale(1, -1);
    
                // 绘制坐标轴
                function drawAxis() {
                    ctx.save();
                    ctx.strokeStyle = "#ccc";
                    ctx.beginPath();
                    ctx.moveTo(-w, 0);
                    ctx.lineTo(w, 0);
                    ctx.stroke();
                    ctx.beginPath();
                    ctx.moveTo(0, -h);
                    ctx.lineTo(0, h);
                    ctx.stroke();
                    ctx.restore();
                }
                drawAxis();
    
                // nonzero:不管有没有相交的边,只要是由边围起来的区域都一律填充。
                // evenodd:根据重叠区域是奇数还是偶数来判断是否填充的
                function draw(
                    context,
                    points,
                    { fillStyle = "salmon", close = false, rule = "nonzero" } = {}
                ) {
                    context.beginPath();
                    context.moveTo(...points[0]);
                    for (let i = 1; i < points.length; i++) {
                        context.lineTo(...points[i]);
                    }
                    if (close) context.closePath();
                    context.fillStyle = fillStyle;
                    context.fill(rule);
                }
    
                // 构建多边形的顶点,这里来5个
                const points = [new Vector2D(0, 100)];
                for (let i = 1; i <= 4; i++) {
                    const p = points[0].copy().rotate(i * Math.PI * 0.4);
                    points.push(p);
                }
    
                // 绘制正五边形
                const polygon = [...points]; // polygon 数组是正五边形的顶点数组
                ctx.save();
                ctx.translate(-128, 0);
                draw(ctx, polygon);
                ctx.restore();
    
                console.log("polygon--->", polygon);
    
                // 绘制正五角星
                const stars = [
                    points[0],
                    points[2],
                    points[4],
                    points[1],
                    points[3],
                ]; // stars 数组是把正五边形的顶点顺序交换之后,构成的五角星的顶点数组。
                ctx.save();
                ctx.translate(128, 0);
                draw(ctx, stars);
                // draw(ctx, stars, {rule: 'evenodd'});
                ctx.restore();
            script>
        body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91

    在这里插入图片描述

    如果改成 rule: 'evenodd' 绘制五角星

    draw(ctx, stars, {rule: 'evenodd'});
    
    • 1

    在这里插入图片描述

    2. WebGL 如何填充多边形?

    将多边形分割成若干个三角形的操作,在图形学中叫做三角剖分(Triangulation)。对 3D 模型,WebGL 在绘制的时候,也需要使用三角剖分,而 3D 的三角剖分又被称为网格化(Meshing)。

    推荐学习:Delaunay Triangulation In Two and Three Dimensions

    可以使用下面库来对多边形进行三角剖分:

    以最简单的 Earcut 库(代码:https://github.com/mapbox/earcut/blob/master/src/earcut.js)为例,来了解 WebGL 填充多边形的过程

    以下面这个多边形为例子,我们利用 Earcut 库对其进行三角剖分
    在这里插入图片描述

    DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8" />
            <meta http-equiv="X-UA-Compatible" content="IE=edge" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
            <title>WebGL 如何填充多边形title>
            <style>
                canvas {
                    border: 1px dashed salmon;
                }
            style>
        head>
        <body>
            <canvas width="512" height="512">canvas>
            <script type="module">
                const canvas = document.querySelector("canvas");
                const gl = canvas.getContext("webgl");
                const vertex = `
                    attribute vec2 position;
                    void main() {
                        gl_PointSize = 1.0;
                        gl_Position = vec4(position, 1.0, 1.0);
                    }
                `;
    
                const fragment = `
                    precision mediump float;
                    void main() {
                        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
                    }
                `;
    
                const vertexShader = gl.createShader(gl.VERTEX_SHADER);
                gl.shaderSource(vertexShader, vertex);
                gl.compileShader(vertexShader);
    
                const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
                gl.shaderSource(fragmentShader, fragment);
                gl.compileShader(fragmentShader);
    
                const program = gl.createProgram();
                gl.attachShader(program, vertexShader);
                gl.attachShader(program, fragmentShader);
                gl.linkProgram(program);
                gl.useProgram(program);
    
                // 不规则多边形的顶点
                const vertices = [
                    [-0.7, 0.5],
                    [-0.4, 0.3],
                    [-0.25, 0.71],
                    [-0.1, 0.56],
                    [-0.1, 0.13],
                    [0.4, 0.21],
                    [0, -0.6],
                    [-0.3, -0.3],
                    [-0.6, -0.3],
                    [-0.45, 0.0],
                ];
    
                // 使用 Earcut 库进行三角剖分:Earcut 库只接受扁平化的定点数据
                import { earcut } from "./common/lib/earcut.js";
                // 使用数组的 flat 方法将顶点扁平化
                const points = vertices.flat();
                // 进行三角剖分
                const triangles = earcut(points);
    
                const position = new Float32Array(points);
                const cells = new Uint16Array(triangles);
    
                const pointBuffer = gl.createBuffer();
                gl.bindBuffer(gl.ARRAY_BUFFER, pointBuffer);
                gl.bufferData(gl.ARRAY_BUFFER, position, gl.STATIC_DRAW);
    
                const vPosition = gl.getAttribLocation(program, "position");
                gl.vertexAttribPointer(vPosition, 2, gl.FLOAT, false, 0, 0);
                gl.enableVertexAttribArray(vPosition);
    
                const cellsBuffer = gl.createBuffer();
                gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cellsBuffer);
                gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, cells, gl.STATIC_DRAW);
    
                gl.clear(gl.COLOR_BUFFER_BIT);
                gl.drawElements(gl.TRIANGLES, cells.length, gl.UNSIGNED_SHORT, 0);
                // 用描边 LINE_STRIP 代替填充 TRIANGLES
                // gl.drawElements(gl.LINE_STRIP, cells.length, gl.UNSIGNED_SHORT, 0);
            script>
        body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90

    在这里插入图片描述
    可以通过用描边 LINE_STRIP 代替填充 TRIANGLES 就可以清晰的看到这个多边形被分割成了多个三角形

    // 用描边LINE_STRIP 代替填充 TRIANGLES
    gl.drawElements(gl.LINE_STRIP, cells.length, gl.UNSIGNED_SHORT, 0);
    
    • 1
    • 2

    在这里插入图片描述
    注意:三角剖分后返回的数组里的值是顶点数据的 index。

    在这里插入图片描述

    如何判断点在多边形内部?

    判断一个点是否在多边形内部时,需要先对多边形进行三角剖分,然后判断该点是否在其中一个三角形内部。

    1. Canvas2D 如何判断点在多边形内部?

    我们先使用 canvas2d 绘制出来上面的多边形

    DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8" />
            <meta http-equiv="X-UA-Compatible" content="IE=edge" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
            <title>Canvas2D 如何判断点在多边形内部title>
            <style>
                canvas {
                    border: 1px dashed salmon;
                }
            style>
        head>
        <body>
            <canvas width="512" height="512">canvas>
            <script type="module">
                const vertices = [
                    [-0.7, 0.5],
                    [-0.4, 0.3],
                    [-0.25, 0.71],
                    [-0.1, 0.56],
                    [-0.1, 0.13],
                    [0.4, 0.21],
                    [0, -0.6],
                    [-0.3, -0.3],
                    [-0.6, -0.3],
                    [-0.45, 0.0],
                ];
    
                const canvas = document.querySelector("canvas");
                const ctx = canvas.getContext("2d");
                const { width, height } = canvas;
                ctx.translate(0.5 * width, 0.5 * height);
                ctx.scale(1, -1);
    
                const poitions = vertices.map(([x, y]) => [x * 256, y * 256]);
    
                function draw(
                    ctx,
                    points,
                    strokeStyle = "salmon",
                    fillStyle = null
                ) {
                    ctx.strokeStyle = strokeStyle;
                    ctx.beginPath();
                    ctx.moveTo(...points[0]);
                    for (let i = 1; i < points.length; i++) {
                        ctx.lineTo(...points[i]);
                    }
                    ctx.closePath();
                    if (fillStyle) {
                        ctx.fillStyle = fillStyle;
                        ctx.fill();
                    }
                    ctx.stroke();
                }
    
                draw(ctx, poitions, "transparent", "salmon");
                // draw(ctx, [[100, 100], [100, 200], [150, 200]], 'transparent', 'salmon');
    
                const { left, top } = canvas.getBoundingClientRect();
    
                canvas.addEventListener("mousemove", (evt) => {
                    const { x, y } = evt;
                    // 坐标转换
                    const offsetX = x - left;
                    const offsetY = y - top;
    
                    ctx.clearRect(-256, -256, 512, 512);
    
                    if (ctx.isPointInPath(offsetX, offsetY)) {
                        draw(ctx, poitions, "transparent", "green");
                        // draw(ctx, [[100, 100], [100, 200], [150, 200]], 'transparent', 'green');
                    } else {
                        draw(ctx, poitions, "transparent", "salmon");
                        // draw(ctx, [[100, 100], [100, 200], [150, 200]], 'transparent', 'salmon');
                    }
                });
            script>
        body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81

    在这里插入图片描述
    鼠标放上去也是可以变色的

    在这里插入图片描述

    我们放开那个小多边形的注释代码

    在这里插入图片描述
    我们发现鼠标只有放在小三角形里的时候才会变色

    在这里插入图片描述
    因为 isPointInPath 方法只能对当前绘制的图形生效。仅能判断鼠标是否在最后一次绘制的小三角形内,所以大多边形就没有被识别出来。

    解决方法:在绘制的过程中获取每个图形的 isPointInPath 结果。

    DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8" />
            <meta http-equiv="X-UA-Compatible" content="IE=edge" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
            <title>Canvas2D 如何判断点在多边形内部2title>
            <style>
                canvas {
                    border: 1px dashed salmon;
                }
            style>
        head>
        <body>
            <canvas width="512" height="512">canvas>
            <script type="module">
                const vertices = [
                    [-0.7, 0.5],
                    [-0.4, 0.3],
                    [-0.25, 0.71],
                    [-0.1, 0.56],
                    [-0.1, 0.13],
                    [0.4, 0.21],
                    [0, -0.6],
                    [-0.3, -0.3],
                    [-0.6, -0.3],
                    [-0.45, 0.0],
                ];
    
                const canvas = document.querySelector("canvas");
                const ctx = canvas.getContext("2d");
                const { width, height } = canvas;
                ctx.translate(0.5 * width, 0.5 * height);
                ctx.scale(1, -1);
    
                const poitions = vertices.map(([x, y]) => [x * 256, y * 256]);
    
                function draw(
                    ctx,
                    points,
                    strokeStyle = "salmon",
                    fillStyle = null
                ) {
                    ctx.strokeStyle = strokeStyle;
                    ctx.beginPath();
                    ctx.moveTo(...points[0]);
                    for (let i = 1; i < points.length; i++) {
                        ctx.lineTo(...points[i]);
                    }
                    ctx.closePath();
                    if (fillStyle) {
                        ctx.fillStyle = fillStyle;
                        ctx.fill();
                    }
                    ctx.stroke();
                }
                
                function isPointInPath(ctx, x, y) {
                    // 根据ctx重新clone一个新的canvas对象出来
                    const cloned = ctx.canvas.cloneNode().getContext("2d");
                    cloned.translate(0.5 * width, 0.5 * height);
                    cloned.scale(1, -1);
                    let ret = false;
                    // 绘制多边形,然后判断点是否在图形内部
                    draw(cloned, poitions, "transparent", "salmon");
                    ret |= cloned.isPointInPath(x, y);
                    if (!ret) {
                        // 如果不在,在绘制小三角形,然后判断点是否在图形内部
                        draw(cloned, [[100, 100], [100, 200], [150, 200]], 'transparent', 'salmon');
                        ret |= cloned.isPointInPath(x, y);
                    }
                    return ret;
                }
    
                draw(ctx, poitions, "transparent", "salmon");
                draw(ctx, [[100, 100], [100, 200], [150, 200]], 'transparent', 'salmon');
    
                const { left, top } = canvas.getBoundingClientRect();
    
                canvas.addEventListener("mousemove", (evt) => {
                    const { x, y } = evt;
                    // 坐标转换
                    const offsetX = x - left;
                    const offsetY = y - top;
    
                    ctx.clearRect(-256, -256, 512, 512);
    
                    if (isPointInPath(ctx, offsetX, offsetY)) {
                        draw(ctx, poitions, "transparent", "green");
                        draw(ctx, [[100, 100], [100, 200], [150, 200]], 'transparent', 'green');
                    } else {
                        draw(ctx, poitions, "transparent", "salmon");
                        draw(ctx, [[100, 100], [100, 200], [150, 200]], 'transparent', 'salmon');
                    }
                });
            script>
        body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98

    在这里插入图片描述

    2. 实现通用的 isPointInPath 方法

    三角形有一个非常简单的方法可以判断点是否在其中。

    已知一个三角形的三条边分别是向量 a、b、c,平面上一点 u 连接三角形三个顶点的向量分别为 u1、u2、u3,那么 u 点在三角形内部的充分必要条件是:u1 X a、u2 X b、u3 X c 的符号相同。

    在这里插入图片描述

    当点 u 在三角形 a、b、c 内时,因为 u1到 a、u2到 b、u3到 c 的小角旋转方向是相同的(这里都为顺时针),所以 u1 X a、u2 X b、u3 X c 要么同正,要么同负。当点 v 在三角形外时,v1到 a 方向是顺时针,v2到 b 方向是逆时针,v3到 c 方向又是顺时针,所以它们叉乘的结果符号并不相同。

    左图是点u和a不在一条直线上,右图是点u和a在一条直线上
    在这里插入图片描述

    只有当 u1 和 a 的比值在 0 到 1 之间时,才能说明点在三角形的边上。

    DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8" />
            <meta http-equiv="X-UA-Compatible" content="IE=edge" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
            <title>实现通用的 isPointInPath 方法title>
            <style>
                canvas {
                    border: 1px dashed salmon;
                }
            style>
        head>
        <body>
            <canvas width="512" height="512">canvas>
            <script type="module">
                import { Vector2D } from "./common/lib/vector2d.js";
                import { earcut } from "./common/lib/earcut.js";
    
                // 判断点是否在三角形里面
                function inTriangle(p1, p2, p3, point) {
                    const a = p2.copy().sub(p1);
                    const b = p3.copy().sub(p2);
                    const c = p1.copy().sub(p3);
    
                    const u1 = point.copy().sub(p1);
                    const u2 = point.copy().sub(p2);
                    const u3 = point.copy().sub(p3);
    
                    const s1 = Math.sign(a.cross(u1));
                    let p = a.dot(u1) / a.length ** 2;
                    if (s1 === 0 && p >= 0 && p <= 1) return true;
    
                    const s2 = Math.sign(b.cross(u2));
                    p = b.dot(u1) / b.length ** 2;
                    if (s2 === 0 && p >= 0 && p <= 1) return true;
    
                    const s3 = Math.sign(c.cross(u3));
                    p = c.dot(u1) / c.length ** 2;
                    if (s3 === 0 && p >= 0 && p <= 1) return true;
    
                    return s1 === s2 && s2 === s3;
                }
    
                // 判断点是否在多边形里面
                function isPointInPath({ vertices, cells }, point) {
                    let ret = false;
                    for (let i = 0; i < cells.length; i += 3) {
                        const p1 = new Vector2D(...vertices[cells[i]]);
                        const p2 = new Vector2D(...vertices[cells[i + 1]]);
                        const p3 = new Vector2D(...vertices[cells[i + 2]]);
                        if (inTriangle(p1, p2, p3, point)) {
                            ret = true;
                            break;
                        }
                    }
                    return ret;
                }
    
                const canvas = document.querySelector("canvas");
                const gl = canvas.getContext("webgl");
    
                const vertex = `
                    attribute vec2 position;
                    uniform vec4 u_color;
    
                    varying vec4 vColor;
    
                    void main() {
                        gl_PointSize = 1.0;
                        gl_Position = vec4(position, 1.0, 1.0);
                        vColor = u_color;
                    }
                `;
    
                const fragment = `
                    precision mediump float;
                    varying vec4 vColor;
    
                    void main() {
                        gl_FragColor = vColor;
                    }    
                `;
    
                const vertexShader = gl.createShader(gl.VERTEX_SHADER);
                gl.shaderSource(vertexShader, vertex);
                gl.compileShader(vertexShader);
    
                const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
                gl.shaderSource(fragmentShader, fragment);
                gl.compileShader(fragmentShader);
    
                const program = gl.createProgram();
                gl.attachShader(program, vertexShader);
                gl.attachShader(program, fragmentShader);
                gl.linkProgram(program);
                gl.useProgram(program);
    
                const vertices = [
                    [-0.7, 0.5],
                    [-0.4, 0.3],
                    [-0.25, 0.71],
                    [-0.1, 0.56],
                    [-0.1, 0.13],
                    [0.4, 0.21],
                    [0, -0.6],
                    [-0.3, -0.3],
                    [-0.6, -0.3],
                    [-0.45, 0.0],
                ];
    
                const points = vertices.flat();
                const triangles = earcut(points);
                
                console.log("triangles---->", triangles);
    
                const position = new Float32Array(points);
                const cells = new Uint16Array(triangles);
    
                const pointBuffer = gl.createBuffer();
                gl.bindBuffer(gl.ARRAY_BUFFER, pointBuffer);
                gl.bufferData(gl.ARRAY_BUFFER, position, gl.STATIC_DRAW);
    
                const vPosition = gl.getAttribLocation(program, "position");
                gl.vertexAttribPointer(vPosition, 2, gl.FLOAT, false, 0, 0);
                gl.enableVertexAttribArray(vPosition);
    
                const cellsBuffer = gl.createBuffer();
                gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cellsBuffer);
                gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, cells, gl.STATIC_DRAW);
    
                const colorLoc = gl.getUniformLocation(program, "u_color");
                gl.uniform4fv(colorLoc, [1, 0, 0, 1]);
    
                gl.clear(gl.COLOR_BUFFER_BIT);
                gl.drawElements(gl.TRIANGLES, cells.length, gl.UNSIGNED_SHORT, 0);
    
                const { left, top } = canvas.getBoundingClientRect();
                canvas.addEventListener("mousemove", (evt) => {
                    const { x, y } = evt;
                    // 坐标转换
                    const offsetX = (2 * (x - left)) / canvas.width - 1.0;
                    const offsetY = 1.0 - (2 * (y - top)) / canvas.height;
    
                    gl.clear(gl.COLOR_BUFFER_BIT);
    
                    const colorLoc = gl.getUniformLocation(program, "u_color");
                    if (
                        isPointInPath(
                            { vertices, cells },
                            new Vector2D(offsetX, offsetY)
                        )
                    ) {
                        gl.uniform4fv(colorLoc, [0, 0.5, 0, 1]);
                    } else {
                        gl.uniform4fv(colorLoc, [1, 0, 0, 1]);
                    }
    
                    gl.drawElements(gl.TRIANGLES, cells.length, gl.UNSIGNED_SHORT, 0);
                });
            script>
        body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163

    鼠标悬浮效果如下:

    在这里插入图片描述

  • 相关阅读:
    数学问题:导函数的左右极限与函数的左右导数是一回事吗?
    Java项目:JSP员工出差请假考勤管理系统
    服务器安装运行jupyter notebook
    Linux CentOS 7 离线安装.NET环境
    cmake vs2022编译opencv4.5.2 x86 版本
    牛客竞赛每日俩题 - Day6
    如何搭建一个网站 -- 搭建一个网站需要多少钱
    基于PHP+MySQL学院信息发布系统的设计与实现
    密码学——1.密码学概论
    如何修改mtp模式在电脑上显示的存储容量大小?
  • 原文地址:https://blog.csdn.net/kaimo313/article/details/126833832