227 lines
7.3 KiB
HTML
227 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>
|
||
<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> |