convex_hull/convex_hull.html
2024-12-17 17:56:18 +08:00

228 lines
7.3 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>