<think>我们首先需要实现几何计算的基础类:点(Point)、直线(Line)、圆(Circle)、三角形(Triangle)、矩形(Rectangle)等。
然后实现凸包算法(Graham扫描法)、Delaunay三角剖分(Bowyer-Watson算法)和Voronoi图(利用Delaunay三角剖分的结果)。
由于代码量较大,我们将分步骤实现:
1. 定义几何基本元素:点(Point)、直线(Line)、圆(Circle)、三角形(Triangle)、矩形(Rectangle)等。
2. 实现凸包算法(Graham扫描法)
3. 实现Delaunay三角剖分(Bowyer-Watson算法)
4. 实现Voronoi图(通过Delaunay三角剖分的对偶图)
注意:为了在浏览器中显示,我们将使用HTML5 Canvas来绘制图形。同时,我们将按照要求,通过按钮逐步演示。
由于代码量很大,我们将尽量模块化,并给出关键部分的解释。
我们将创建以下类:
- Point: 表示二维点
- Line: 表示直线,支持一般式、两点式等,并提供求交点、点与直线关系等方法
- Circle: 表示圆,提供点与圆关系的判断
- Triangle: 表示三角形,提供外接圆计算
- Rectangle: 表示矩形
- ConvexHull: 凸包计算(使用Graham扫描法)
- Delaunay: Delaunay三角剖分(Bowyer-Watson算法)
- Voronoi: Voronoi图生成
另外,我们还需要一个绘图类(CanvasDraw)来在Canvas上绘制点、线、多边形等。
我们将在一个HTML文件中实现,包括:
- 一个canvas元素
- 几个按钮:生成随机点、凸包、Delaunay三角剖分、Voronoi图
步骤:
1. 随机生成一些点
2. 点击按钮,逐步显示凸包、Delaunay、Voronoi
注意:为了简化,我们假设点集是通用的(没有三点共线等退化情况,或者我们处理退化情况)。
由于代码较长,我们将分块编写。
首先,我们定义点类(Point):</think># 几何计算与可视化演示系统
我将为你创建一个完整的几何计算与可视化系统,包含凸包、Delaunay三角剖分和Voronoi图的实现,并带有交互式演示功能。
```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>几何计算与可视化演示系统</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #1a2a6c, #2c3e50);
color: #fff;
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
header {
text-align: center;
padding: 20px 0;
margin-bottom: 30px;
}
h1 {
font-size: 2.8rem;
margin-bottom: 10px;
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
background: linear-gradient(to right, #ff7e5f, #feb47b);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.subtitle {
font-size: 1.2rem;
opacity: 0.9;
max-width: 800px;
margin: 0 auto;
}
.content {
display: flex;
gap: 30px;
flex-wrap: wrap;
}
.canvas-container {
flex: 1;
min-width: 500px;
background: rgba(25, 35, 60, 0.8);
border-radius: 15px;
padding: 20px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
backdrop-filter: blur(10px);
}
.controls {
width: 300px;
background: rgba(25, 35, 60, 0.8);
border-radius: 15px;
padding: 25px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
backdrop-filter: blur(10px);
}
canvas {
background: #0f1523;
border-radius: 10px;
width: 100%;
height: 500px;
display: block;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4);
}
.control-group {
margin-bottom: 25px;
}
h2 {
font-size: 1.5rem;
margin-bottom: 15px;
color: #feb47b;
}
.btn-group {
display: flex;
flex-direction: column;
gap: 12px;
}
button {
padding: 14px;
border: none;
border-radius: 8px;
background: linear-gradient(to right, #ff7e5f, #feb47b);
color: white;
font-size: 1rem;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
button:hover {
transform: translateY(-3px);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3);
}
button:active {
transform: translateY(1px);
}
.slider-container {
margin-top: 15px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: 500;
}
input[type="range"] {
width: 100%;
height: 8px;
border-radius: 4px;
background: #2c3e50;
outline: none;
-webkit-appearance: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: #ff7e5f;
cursor: pointer;
}
.visualization-options {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 15px;
}
.option {
display: flex;
align-items: center;
gap: 5px;
}
.info-panel {
background: rgba(0, 0, 0, 0.3);
border-radius: 8px;
padding: 15px;
margin-top: 20px;
font-size: 0.9rem;
line-height: 1.6;
}
.algorithm-steps {
margin-top: 10px;
padding-left: 20px;
}
.algorithm-steps li {
margin-bottom: 5px;
}
.status-bar {
margin-top: 15px;
padding: 10px;
background: rgba(0, 0, 0, 0.3);
border-radius: 8px;
font-size: 0.9rem;
text-align: center;
}
.color-legend {
display: flex;
gap: 15px;
margin-top: 15px;
justify-content: center;
}
.legend-item {
display: flex;
align-items: center;
gap: 5px;
font-size: 0.85rem;
}
.legend-color {
width: 15px;
height: 15px;
border-radius: 3px;
}
@media (max-width: 900px) {
.content {
flex-direction: column;
}
.controls {
width: 100%;
}
.canvas-container {
min-width: auto;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>几何计算与可视化演示系统</h1>
<p class="subtitle">实现凸包算法、Delaunay三角剖分和Voronoi图生成,包含详细的几何计算功能</p>
</header>
<div class="content">
<div class="canvas-container">
<canvas id="geometryCanvas"></canvas>
<div class="status-bar">
<span id="status">就绪。点击"生成随机点"开始演示</span>
</div>
</div>
<div class="controls">
<div class="control-group">
<h2>几何计算</h2>
<div class="btn-group">
<button id="generatePoints">生成随机点</button>
<button id="resetCanvas">重置画布</button>
</div>
<div class="slider-container">
<label for="pointCount">点数: <span id="pointCountValue">20</span></label>
<input type="range" id="pointCount" min="5" max="100" value="20">
</div>
</div>
<div class="control-group">
<h2>算法演示</h2>
<div class="btn-group">
<button id="convexHull">计算凸包</button>
<button id="delaunay">Delaunay三角剖分</button>
<button id="voronoi">生成Voronoi图</button>
<button id="nextStep">下一步</button>
</div>
</div>
<div class="control-group">
<h2>可视化选项</h2>
<div class="visualization-options">
<div class="option">
<input type="checkbox" id="showPoints" checked>
<label for="showPoints">显示点</label>
</div>
<div class="option">
<input type="checkbox" id="showLabels" checked>
<label for="showLabels">显示标签</label>
</div>
<div class="option">
<input type="checkbox" id="showConvexHull" checked>
<label for="showConvexHull">显示凸包</label>
</div>
<div class="option">
<input type="checkbox" id="showTriangles" checked>
<label for="showTriangles">显示三角形</label>
</div>
<div class="option">
<input type="checkbox" id="showVoronoi" checked>
<label for="showVoronoi">显示Voronoi</label>
</div>
</div>
</div>
<div class="color-legend">
<div class="legend-item">
<div class="legend-color" style="background-color: #ff7e5f;"></div>
<span>点</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #4CAF50;"></div>
<span>凸包</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #2196F3;"></div>
<span>Delaunay</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #9C27B0;"></div>
<span>Voronoi</span>
</div>
</div>
<div class="info-panel">
<h3>当前算法: <span id="currentAlgorithm">无</span></h3>
<div id="algorithmInfo">
选择算法查看详细信息
</div>
</div>
</div>
</div>
</div>
<script>
// 几何计算核心类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
distanceTo(p) {
return Math.sqrt(Math.pow(this.x - p.x, 2) + Math.pow(this.y - p.y, 2));
}
equals(p) {
return Math.abs(this.x - p.x) < 1e-5 && Math.abs(this.y - p.y) < 1e-5;
}
toString() {
return `(${this.x.toFixed(1)}, ${this.y.toFixed(1)})`;
}
}
class Line {
// 两点式方程
static fromPoints(p1, p2) {
const a = p2.y - p1.y;
const b = p1.x - p2.x;
const c = p2.x * p1.y - p1.x * p2.y;
return { a, b, c };
}
// 计算点到直线的距离
static distanceToPoint(line, p) {
const { a, b, c } = line;
return Math.abs(a * p.x + b * p.y + c) / Math.sqrt(a * a + b * b);
}
// 判断点与直线的关系 (在直线上、左侧、右侧)
static pointPosition(line, p) {
const { a, b, c } = line;
const value = a * p.x + b * p.y + c;
if (Math.abs(value) < 1e-5) return 0; // 在直线上
return value > 0 ? 1 : -1; // 右侧或左侧
}
}
class Circle {
constructor(center, radius) {
this.center = center;
this.radius = radius;
}
// 三点确定一个圆
static fromThreePoints(p1, p2, p3) {
// 计算垂直平分线的交点
const mid1 = new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
const mid2 = new Point((p2.x + p3.x) / 2, (p2.y + p3.y) / 2);
const slope1 = (p2.y - p1.y) / (p2.x - p1.x);
const slope2 = (p3.y - p2.y) / (p3.x - p2.x);
const perpSlope1 = -1 / slope1;
const perpSlope2 = -1 / slope2;
// 求圆心
const x = (perpSlope1 * mid1.x - perpSlope2 * mid2.x + mid2.y - mid1.y) / (perpSlope1 - perpSlope2);
const y = perpSlope1 * (x - mid1.x) + mid1.y;
const center = new Point(x, y);
const radius = center.distanceTo(p1);
return new Circle(center, radius);
}
// 判断点与圆的关系
pointPosition(p) {
const dist = this.center.distanceTo(p);
if (Math.abs(dist - this.radius) < 1e-5) return 'on';
return dist < this.radius ? 'inside' : 'outside';
}
}
class Triangle {
constructor(p1, p2, p3) {
this.points = [p1, p2, p3];
}
// 计算外接圆
circumcircle() {
return Circle.fromThreePoints(...this.points);
}
// 判断点是否在三角形外接圆内
isPointInCircumcircle(p) {
const circle = this.circumcircle();
return circle.pointPosition(p) !== 'outside';
}
}
// 几何算法实现
class GeometryAlgorithms {
// 凸包算法 (Graham Scan)
static convexHull(points) {
if (points.length < 3) return points;
// 找到最低点(y最小,如果有多个则取x最小)
let minPoint = points[0];
for (let i = 1; i < points.length; i++) {
if (points[i].y < minPoint.y ||
(points[i].y === minPoint.y && points[i].x < minPoint.x)) {
minPoint = points[i];
}
}
// 按极角排序
const sortedPoints = [...points].sort((a, b) => {
const angleA = Math.atan2(a.y - minPoint.y, a.x - minPoint.x);
const angleB = Math.atan2(b.y - minPoint.y, b.x - minPoint.x);
if (angleA < angleB) return -1;
if (angleA > angleB) return 1;
// 角度相同则按距离排序
return minPoint.distanceTo(a) - minPoint.distanceTo(b);
});
// 使用栈计算凸包
const stack = [sortedPoints[0], sortedPoints[1]];
for (let i = 2; i < sortedPoints.length; i++) {
let top = stack[stack.length - 1];
let nextToTop = stack[stack.length - 2];
while (stack.length > 1 && this.orientation(nextToTop, top, sortedPoints[i]) <= 0) {
stack.pop();
top = nextToTop;
nextToTop = stack[stack.length - 2];
}
stack.push(sortedPoints[i]);
}
return stack;
}
// 计算三点方向 (0: 共线, 1: 逆时针, -1: 顺时针)
static orientation(p, q, r) {
const val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
if (Math.abs(val) < 1e-5) return 0;
return val > 0 ? 1 : -1;
}
// Delaunay三角剖分 (Bowyer-Watson算法)
static delaunayTriangulation(points) {
if (points.length < 3) return [];
// 创建超级三角形
const superTriangle = this.createSuperTriangle(points);
let triangles = [superTriangle];
// 逐点插入
for (const point of points) {
const badTriangles = [];
// 找出外接圆包含当前点的三角形
for (const triangle of triangles) {
if (triangle.isPointInCircumcircle(point)) {
badTriangles.push(triangle);
}
}
// 找到边界多边形
const polygon = [];
for (const triangle of badTriangles) {
for (let i = 0; i < 3; i++) {
const edge = [triangle.points[i], triangle.points[(i+1)%3]];
let shared = false;
for (const other of badTriangles) {
if (triangle === other) continue;
for (let j = 0; j < 3; j++) {
const otherEdge = [other.points[j], other.points[(j+1)%3]];
if ((edge[0].equals(otherEdge[0]) && edge[1].equals(otherEdge[1])) ||
(edge[0].equals(otherEdge[1]) && edge[1].equals(otherEdge[0]))) {
shared = true;
break;
}
}
if (shared) break;
}
if (!shared) {
polygon.push(edge);
}
}
}
// 移除坏三角形
triangles = triangles.filter(t => !badTriangles.includes(t));
// 重新三角化
for (const edge of polygon) {
triangles.push(new Triangle(edge[0], edge[1], point));
}
}
// 移除与超级三角形顶点相关的三角形
triangles = triangles.filter(triangle => {
for (const point of triangle.points) {
if (superTriangle.points.includes(point)) {
return false;
}
}
return true;
});
return triangles;
}
// 创建包含所有点的超级三角形
static createSuperTriangle(points) {
let minX = Infinity, minY = Infinity;
let maxX = -Infinity, maxY = -Infinity;
for (const p of points) {
if (p.x < minX) minX = p.x;
if (p.x > maxX) maxX = p.x;
if (p.y < minY) minY = p.y;
if (p.y > maxY) maxY = p.y;
}
const dx = maxX - minX;
const dy = maxY - minY;
const delta = Math.max(dx, dy) * 10;
const p1 = new Point(minX - delta, minY - delta);
const p2 = new Point(minX + 2 * dx + delta, minY - delta);
const p3 = new Point(minX + dx, minY + 2 * dy + delta);
return new Triangle(p1, p2, p3);
}
// 生成Voronoi图 (基于Delaunay三角剖分)
static voronoiDiagram(triangles) {
const voronoiEdges = [];
const centers = new Map();
// 计算每个三角形的外接圆心
for (const tri of triangles) {
const circle = tri.circumcircle();
centers.set(tri, circle.center);
}
// 找出共享边的相邻三角形
for (let i = 0; i < triangles.length; i++) {
const tri1 = triangles[i];
const center1 = centers.get(tri1);
for (let j = i + 1; j < triangles.length; j++) {
const tri2 = triangles[j];
const center2 = centers.get(tri2);
// 检查两个三角形是否共享一条边
let sharedEdge = false;
for (let k = 0; k < 3; k++) {
const edge1 = [tri1.points[k], tri1.points[(k+1)%3]];
for (let l = 0; l < 3; l++) {
const edge2 = [tri2.points[l], tri2.points[(l+1)%3]];
if ((edge1[0].equals(edge2[0]) && edge1[1].equals(edge2[1])) ||
(edge1[0].equals(edge2[1]) && edge1[1].equals(edge2[0]))) {
sharedEdge = true;
break;
}
}
if (sharedEdge) break;
}
if (sharedEdge) {
voronoiEdges.push([center1, center2]);
}
}
}
return voronoiEdges;
}
}
// 可视化控制器
class GeometryVisualizer {
constructor(canvasId) {
this.canvas = document.getElementById(canvasId);
this.ctx = this.canvas.getContext('2d');
this.points = [];
this.convexHull = [];
this.triangles = [];
this.voronoiEdges = [];
this.currentStep = 0;
this.animationSpeed = 100;
this.resizeCanvas();
window.addEventListener('resize', () => this.resizeCanvas());
// 初始状态
this.generateRandomPoints(20);
}
resizeCanvas() {
const container = this.canvas.parentElement;
this.canvas.width = container.clientWidth - 40;
this.canvas.height = 500;
this.draw();
}
generateRandomPoints(count) {
this.points = [];
const padding = 40;
const width = this.canvas.width - 2 * padding;
const height = this.canvas.height - 2 * padding;
for (let i = 0; i < count; i++) {
const x = padding + Math.random() * width;
const y = padding + Math.random() * height;
this.points.push(new Point(x, y));
}
this.convexHull = [];
this.triangles = [];
this.voronoiEdges = [];
this.currentStep = 0;
this.draw();
}
calculateConvexHull() {
this.convexHull = GeometryAlgorithms.convexHull(this.points);
this.draw();
}
calculateDelaunay() {
this.triangles = GeometryAlgorithms.delaunayTriangulation(this.points);
this.draw();
}
calculateVoronoi() {
if (this.triangles.length === 0) {
this.calculateDelaunay();
}
this.voronoiEdges = GeometryAlgorithms.voronoiDiagram(this.triangles);
this.draw();
}
draw() {
const ctx = this.ctx;
const width = this.canvas.width;
const height = this.canvas.height;
// 清除画布
ctx.clearRect(0, 0, width, height);
// 绘制Voronoi图
if (document.getElementById('showVoronoi').checked && this.voronoiEdges.length > 0) {
ctx.beginPath();
ctx.strokeStyle = 'rgba(156, 39, 176, 0.7)';
ctx.lineWidth = 1.5;
for (const edge of this.voronoiEdges) {
ctx.moveTo(edge[0].x, edge[0].y);
ctx.lineTo(edge[1].x, edge[1].y);
}
ctx.stroke();
}
// 绘制Delaunay三角剖分
if (document.getElementById('showTriangles').checked && this.triangles.length > 0) {
ctx.beginPath();
ctx.strokeStyle = 'rgba(33, 150, 243, 0.7)';
ctx.lineWidth = 1.5;
for (const triangle of this.triangles) {
const points = triangle.points;
ctx.moveTo(points[0].x, points[0].y);
ctx.lineTo(points[1].x, points[1].y);
ctx.lineTo(points[2].x, points[2].y);
ctx.lineTo(points[0].x, points[0].y);
}
ctx.stroke();
}
// 绘制凸包
if (document.getElementById('showConvexHull').checked && this.convexHull.length > 0) {
ctx.beginPath();
ctx.strokeStyle = '#4CAF50';
ctx.lineWidth = 2;
ctx.fillStyle = 'rgba(76, 175, 80, 0.1)';
ctx.moveTo(this.convexHull[0].x, this.convexHull[0].y);
for (let i = 1; i < this.convexHull.length; i++) {
ctx.lineTo(this.convexHull[i].x, this.convexHull[i].y);
}
ctx.closePath();
ctx.stroke();
ctx.fill();
}
// 绘制点
if (document.getElementById('showPoints').checked) {
for (const point of this.points) {
ctx.beginPath();
ctx.fillStyle = '#ff7e5f';
ctx.arc(point.x, point.y, 5, 0, Math.PI * 2);
ctx.fill();
// 绘制点标签
if (document.getElementById('showLabels').checked) {
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
ctx.font = '12px Arial';
ctx.fillText(point.toString(), point.x + 8, point.y - 8);
}
}
}
// 绘制凸包点
if (document.getElementById('showConvexHull').checked && this.convexHull.length > 0) {
for (const point of this.convexHull) {
ctx.beginPath();
ctx.fillStyle = '#4CAF50';
ctx.arc(point.x, point.y, 6, 0, Math.PI * 2);
ctx.fill();
// 绘制凸包点标签
if (document.getElementById('showLabels').checked) {
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
ctx.font = 'bold 12px Arial';
ctx.fillText(point.toString(), point.x + 8, point.y - 8);
}
}
}
}
}
// 初始化应用
document.addEventListener('DOMContentLoaded', () => {
const visualizer = new GeometryVisualizer('geometryCanvas');
// 事件监听
document.getElementById('generatePoints').addEventListener('click', () => {
const count = parseInt(document.getElementById('pointCount').value);
visualizer.generateRandomPoints(count);
document.getElementById('status').textContent = `已生成 ${count} 个随机点`;
document.getElementById('currentAlgorithm').textContent = '随机点生成';
document.getElementById('algorithmInfo').innerHTML = `
<p>在画布上随机生成了 ${count} 个点,用于后续几何计算。</p>
<p>几何计算的基础是点集,后续所有算法都基于这些点进行计算。</p>
`;
});
document.getElementById('resetCanvas').addEventListener('click', () => {
visualizer.generateRandomPoints(parseInt(document.getElementById('pointCount').value));
document.getElementById('status').textContent = '画布已重置';
});
document.getElementById('convexHull').addEventListener('click', () => {
visualizer.calculateConvexHull();
document.getElementById('status').textContent = '已计算凸包';
document.getElementById('currentAlgorithm').textContent = '凸包算法 (Graham Scan)';
document.getElementById('algorithmInfo').innerHTML = `
<p>凸包是包含所有点的最小凸多边形。</p>
<p>Graham Scan 算法步骤:</p>
<ol class="algorithm-steps">
<li>找到 y 坐标最小的点(如有多个则取 x 最小)作为基准点</li>
<li>按相对于基准点的极角排序所有点</li>
<li>使用栈结构逐步构建凸包</li>
<li>移除凹点,保留凸点</li>
</ol>
<p>时间复杂度:O(n log n)</p>
`;
});
document.getElementById('delaunay').addEventListener('click', () => {
visualizer.calculateDelaunay();
document.getElementById('status').textContent = '已计算Delaunay三角剖分';
document.getElementById('currentAlgorithm').textContent = 'Delaunay三角剖分 (Bowyer-Watson)';
document.getElementById('algorithmInfo').innerHTML = `
<p>Delaunay三角剖分满足"空圆特性":任意三角形的外接圆内不包含其他点。</p>
<p>Bowyer-Watson 算法步骤:</p>
<ol class="algorithm-steps">
<li>创建包含所有点的超级三角形</li>
<li>逐个插入点</li>
<li>找出所有外接圆包含新点的三角形(坏三角形)</li>
<li>移除坏三角形,形成空洞</li>
<li>重新三角化空洞</li>
<li>移除与超级三角形相关的三角形</li>
</ol>
<p>时间复杂度:平均 O(n log n),最坏 O(n²)</p>
`;
});
document.getElementById('voronoi').addEventListener('click', () => {
visualizer.calculateVoronoi();
document.getElementById('status').textContent = '已生成Voronoi图';
document.getElementById('currentAlgorithm').textContent = 'Voronoi图生成';
document.getElementById('algorithmInfo').innerHTML = `
<p>Voronoi图将平面划分为区域,每个区域包含一个点,区域内任意位置到该点的距离小于到其他点的距离。</p>
<p>生成算法:</p>
<ol class="algorithm-steps">
<li>计算点集的Delaunay三角剖分</li>
<li>计算每个三角形的外接圆心</li>
<li>连接相邻三角形的外接圆心</li>
<li>形成Voronoi图的边</li>
</ol>
<p>Voronoi图是Delaunay三角剖分的对偶图</p>
<p>时间复杂度:O(n log n)</p>
`;
});
document.getElementById('nextStep').addEventListener('click', () => {
// 这里可以添加分步演示的逻辑
document.getElementById('status').textContent = '下一步演示';
});
// 点数量滑块
const pointCountSlider = document.getElementById('pointCount');
const pointCountValue = document.getElementById('pointCountValue');
pointCountSlider.addEventListener('input', () => {
pointCountValue.textContent = pointCountSlider.value;
});
// 可视化选项切换
const options = document.querySelectorAll('.visualization-options input');
options.forEach(option => {
option.addEventListener('change', () => {
visualizer.draw();
});
});
});
</script>
</body>
</html>
```
## 功能说明
这个几何计算与可视化系统实现了以下功能:
1. **几何计算基础**:
- 点(Point)类:包含坐标、距离计算等方法
- 直线(Line)类:支持两点式方程、点到直线距离计算
- 圆(Circle)类:三点确定外接圆、点与圆关系判断
- 三角形(Triangle)类:外接圆计算
2. **核心算法实现**:
- 凸包算法(Graham Scan):计算点集的凸包
- Delaunay三角剖分(Bowyer-Watson):实现点集的Delaunay三角剖分
- Voronoi图生成:基于Delaunay三角剖分生成Voronoi图
3. **交互功能**:
- 随机点生成:可调整点数
- 凸包计算:显示凸包多边形
- Delaunay三角剖分:显示三角形网格
- Voronoi图生成:显示Voronoi多边形
- 可视化选项:可切换显示/隐藏不同元素
4. **信息展示**:
- 算法步骤说明
- 时间复杂度分析
- 当前状态提示
- 颜色图例
## 使用说明
1. 点击"生成随机点"按钮在画布上创建随机点
2. 使用滑块调整点数(5-100)
3. 点击"计算凸包"按钮显示凸包
4. 点击"Delaunay三角剖分"按钮显示三角形网格
5. 点击"生成Voronoi图"按钮显示Voronoi多边形
6. 使用右侧选项控制显示/隐藏不同元素
系统使用纯JavaScript实现,不依赖任何外部库,可以直接在浏览器中运行。