228 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			228 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
<head>
 | 
						||
    <meta charset="UTF-8">
 | 
						||
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
						||
    <title>凸包算法</title>
 | 
						||
</head>
 | 
						||
<body>
 | 
						||
    <a href="https://git.kuraa.cc/kura/convex_hull">git源码</a>
 | 
						||
    <textarea style="width: 100%;height: 100px;" id="points" placeholder="输入点,格式:x1,y1;x2,y2;x3,y3;..."></textarea>
 | 
						||
    <button onclick="convex_hull()">计算凸包</button>
 | 
						||
    <button onclick="randomPointsToTextarea(30)">随机30点</button>
 | 
						||
    <button onclick="randomPointsToTextarea(3000)">随机3000点</button>
 | 
						||
    <button onclick="randomPointsToTextarea(10000)">随机10000点</button>
 | 
						||
    <button onclick="randomPointsToTextarea(30000)">随机30000点</button>
 | 
						||
    <div id="result"></div>
 | 
						||
    <canvas id="canvas" width="800" height="800"></canvas>
 | 
						||
</body>
 | 
						||
<script src="./convex_hull.js"></script>
 | 
						||
<script>
 | 
						||
    function callWasmConvexHull(points) {
 | 
						||
        //长度
 | 
						||
        var n = points.length;
 | 
						||
        var pointsPtr = Module._malloc(n * 8);  //8字节一个点
 | 
						||
 | 
						||
        for (let i = 0; i < n; i++) {
 | 
						||
            Module.setValue(pointsPtr + i * 8, points[i].x, 'i32');
 | 
						||
            Module.setValue(pointsPtr + i * 8 + 4, points[i].y, 'i32');
 | 
						||
        }
 | 
						||
        //多少点
 | 
						||
        var resultPtr = Module._malloc(n * 8);
 | 
						||
        var resultSizePtr = Module._malloc(4);
 | 
						||
        console.time('wasm耗时')
 | 
						||
        Module._convexHull(pointsPtr, n, resultPtr, resultSizePtr);
 | 
						||
        console.timeEnd('wasm耗时')
 | 
						||
        var resultSize = Module.getValue(resultSizePtr, 'i32');
 | 
						||
        var resultPoints = [];
 | 
						||
        //读
 | 
						||
        for (let i = 0; i < resultSize; i++) {
 | 
						||
            var x = Module.getValue(resultPtr + i * 8, 'i32');
 | 
						||
            var y = Module.getValue(resultPtr + i * 8 + 4, 'i32');
 | 
						||
            resultPoints.push({ x: x, y: y });
 | 
						||
        }
 | 
						||
        // 释放
 | 
						||
        Module._free(pointsPtr);
 | 
						||
        Module._free(resultPtr);
 | 
						||
        Module._free(resultSizePtr);
 | 
						||
        return resultPoints
 | 
						||
    }
 | 
						||
 | 
						||
 | 
						||
 | 
						||
 | 
						||
 | 
						||
 | 
						||
    function randomPointsToTextarea(n) {
 | 
						||
        var points = Array.from({ length: n }, () => ({
 | 
						||
            x: Math.floor(Math.random() * 100),
 | 
						||
            y: Math.floor(Math.random() * 100)
 | 
						||
        }));
 | 
						||
        document.getElementById('points').value = points.map(p => `${p.x},${p.y}`).join(';');
 | 
						||
        draw(points)
 | 
						||
    }
 | 
						||
 | 
						||
 | 
						||
 | 
						||
    //计算凸包
 | 
						||
    function convex_hull(points) {
 | 
						||
        var points = document.getElementById('points').value.split(';').map(item => {
 | 
						||
            var [x, y] = item.split(',');
 | 
						||
            return { x: parseInt(x), y: parseInt(y) };
 | 
						||
        });
 | 
						||
        console.log('--------------------------------')
 | 
						||
        //wasm整体调用计时
 | 
						||
        console.time('wasm整体耗时,包含指针传递')
 | 
						||
        var resultPoints = callWasmConvexHull(points)
 | 
						||
        console.timeEnd('wasm整体耗时,包含指针传递')
 | 
						||
        document.getElementById('result').innerHTML = JSON.stringify(resultPoints)
 | 
						||
        drawConvexHull(resultPoints)
 | 
						||
 | 
						||
        //js实现凸包
 | 
						||
        console.time('js调用')
 | 
						||
        var resultPoints = jsConvexHull(points)
 | 
						||
        console.timeEnd('js调用')
 | 
						||
        drawConvexHull(resultPoints)
 | 
						||
    }
 | 
						||
 | 
						||
 | 
						||
 | 
						||
 | 
						||
    let padding = 100; // 增加内边距,让点不贴边
 | 
						||
    let minX = 0;
 | 
						||
    let minY = 0;
 | 
						||
    let maxX = 0;
 | 
						||
    let maxY = 0;
 | 
						||
    let scale = 1;
 | 
						||
    // 转换坐标
 | 
						||
    function transformPoint(p, canvas) {
 | 
						||
        return {
 | 
						||
            x: padding + (p.x - minX) * scale,
 | 
						||
            y: canvas.height - padding - (p.y - minY) * scale
 | 
						||
        };
 | 
						||
    }
 | 
						||
    /**
 | 
						||
     * canvas画点
 | 
						||
     */
 | 
						||
    function draw(points) {
 | 
						||
        var canvas = document.getElementById('canvas');
 | 
						||
        var ctx = canvas.getContext('2d');
 | 
						||
 | 
						||
        // 清空画布
 | 
						||
        ctx.clearRect(0, 0, canvas.width, canvas.height);
 | 
						||
 | 
						||
        // 找出所有点的最大最小值
 | 
						||
        minX = Math.min(...points.map(p => p.x));
 | 
						||
        maxX = Math.max(...points.map(p => p.x));
 | 
						||
        minY = Math.min(...points.map(p => p.y));
 | 
						||
        maxY = Math.max(...points.map(p => p.y));
 | 
						||
 | 
						||
        // 计算缩放比例
 | 
						||
        var scaleX = (canvas.width - 2 * padding) / (maxX - minX || 1);
 | 
						||
        var scaleY = (canvas.height - 2 * padding) / (maxY - minY || 1);
 | 
						||
        scale = Math.min(scaleX, scaleY);
 | 
						||
 | 
						||
        // 绘制点
 | 
						||
        var transformedPoints = points.map(p => transformPoint(p, canvas));
 | 
						||
        ctx.fillStyle = 'blue';
 | 
						||
 | 
						||
        transformedPoints.forEach(point => {
 | 
						||
            ctx.beginPath();
 | 
						||
            ctx.arc(point.x, point.y, 4, 0, Math.PI * 2); // 绘制半径为4的圆点
 | 
						||
            ctx.fill();
 | 
						||
        });
 | 
						||
    }
 | 
						||
    /**
 | 
						||
     * 画凸包
 | 
						||
     */
 | 
						||
    function drawConvexHull(points) {
 | 
						||
        // 使用相同的绘制函数,但使用不同的颜色
 | 
						||
        var canvas = document.getElementById('canvas');
 | 
						||
        var ctx = canvas.getContext('2d');
 | 
						||
 | 
						||
        // 绘制凸包
 | 
						||
        ctx.beginPath();
 | 
						||
        ctx.strokeStyle = 'red';
 | 
						||
 | 
						||
 | 
						||
        var transformedPoints = points.map(p => transformPoint(p, canvas));
 | 
						||
 | 
						||
        ctx.moveTo(transformedPoints[0].x, transformedPoints[0].y);
 | 
						||
        for (let i = 1; i < transformedPoints.length; i++) {
 | 
						||
            ctx.lineTo(transformedPoints[i].x, transformedPoints[i].y);
 | 
						||
        }
 | 
						||
        // 闭合凸包
 | 
						||
        ctx.lineTo(transformedPoints[0].x, transformedPoints[0].y);
 | 
						||
        ctx.stroke();
 | 
						||
    }
 | 
						||
 | 
						||
 | 
						||
    //loadwasm
 | 
						||
    function loadwasm() {
 | 
						||
        var wasm_url = "./convex_hull.js";
 | 
						||
        var script = document.createElement('script');
 | 
						||
        script.src = wasm_url;
 | 
						||
        script.onload = function () {
 | 
						||
            //loadwasm
 | 
						||
            console.log(Module)
 | 
						||
            Module.onRuntimeInitialized = function () {
 | 
						||
                // convex_hull()
 | 
						||
            }
 | 
						||
        }
 | 
						||
        document.body.appendChild(script);
 | 
						||
    }
 | 
						||
    loadwasm()
 | 
						||
    randomPointsToTextarea(30)
 | 
						||
 | 
						||
 | 
						||
    //js实现凸包 不使用wasm
 | 
						||
    function jsConvexHull(points) {
 | 
						||
        // 如果点少于3个,直接返回原始点集
 | 
						||
        if (points.length < 3) return points;
 | 
						||
 | 
						||
        // 找到最低且最左的点作为起始点
 | 
						||
        let start = points.reduce((lowest, point) => {
 | 
						||
            if (point.y < lowest.y || (point.y === lowest.y && point.x < lowest.x)) {
 | 
						||
                return point;
 | 
						||
            }
 | 
						||
            return lowest;
 | 
						||
        }, points[0]);
 | 
						||
 | 
						||
        // 计算其他点相对于起始点的极角,并排序
 | 
						||
        let sorted = points
 | 
						||
            .filter(point => point !== start)
 | 
						||
            .map(point => ({
 | 
						||
                point: point,
 | 
						||
                angle: Math.atan2(point.y - start.y, point.x - start.x),
 | 
						||
                distance: Math.sqrt(Math.pow(point.x - start.x, 2) + Math.pow(point.y - start.y, 2))
 | 
						||
            }))
 | 
						||
            .sort((a, b) => {
 | 
						||
                if (a.angle === b.angle) {
 | 
						||
                    return a.distance - b.distance;
 | 
						||
                }
 | 
						||
                return a.angle - b.angle;
 | 
						||
            })
 | 
						||
            .map(item => item.point);
 | 
						||
 | 
						||
        // 将起始点加入结果数组
 | 
						||
        sorted.unshift(start);
 | 
						||
 | 
						||
        // Graham扫描
 | 
						||
        let stack = [sorted[0], sorted[1]];
 | 
						||
        for (let i = 2; i < sorted.length; i++) {
 | 
						||
            while (stack.length >= 2 && !isCounterClockwise(
 | 
						||
                stack[stack.length - 2],
 | 
						||
                stack[stack.length - 1],
 | 
						||
                sorted[i]
 | 
						||
            )) {
 | 
						||
                stack.pop();
 | 
						||
            }
 | 
						||
            stack.push(sorted[i]);
 | 
						||
        }
 | 
						||
 | 
						||
        return stack;
 | 
						||
    }
 | 
						||
 | 
						||
    // 判断三个点是否形成逆时针方向
 | 
						||
    function isCounterClockwise(p1, p2, p3) {
 | 
						||
        return (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x) > 0;
 | 
						||
    }
 | 
						||
</script> |