convex_hull/convex_hull.html

227 lines
7.3 KiB
HTML
Raw Normal View History

2024-12-17 09:52:09 +00:00
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>凸包算法</title>
</head>
<body>
<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>