当前位置: 首页 > news >正文

图形裁剪算法

1.学习目标

理解区域编码(Region Code,RC)

设计Cohen-Sutherland直线裁剪算法

编程实现Cohen-Sutherland直线裁剪算法

2.具体代码

  • 1.具体算法

/**
 * Cohen-Sutherland直线裁剪算法 - 优化版
 * @author AI Assistant
 * @license MIT
 */

// 区域编码常量 - 使用对象枚举以增强可读性
const RegionCode = Object.freeze({
  INSIDE: 0, // 0000
  LEFT: 1,   // 0001
  RIGHT: 2,  // 0010
  BOTTOM: 4, // 0100
  TOP: 8     // 1000
});

/**
 * 计算点的区域编码
 * @param {number} x - 点的x坐标
 * @param {number} y - 点的y坐标
 * @param {Object} clipWindow - 裁剪窗口
 * @returns {number} - 区域编码
 */
function computeCode(x, y, clipWindow) {
  const { xmin, ymin, xmax, ymax } = clipWindow;
  
  // 使用位运算计算区域编码
  let code = RegionCode.INSIDE;
  
  // 左/右测试
  if (x < xmin) {
    code |= RegionCode.LEFT;
  } else if (x > xmax) {
    code |= RegionCode.RIGHT;
  }
  
  // 下/上测试
  if (y < ymin) {
    code |= RegionCode.BOTTOM;
  } else if (y > ymax) {
    code |= RegionCode.TOP;
  }
  
  return code;
}

/**
 * 计算线段与裁剪窗口边界的交点
 * @param {number} code - 端点的区域编码
 * @param {Point} p1 - 线段起点
 * @param {Point} p2 - 线段终点
 * @param {Object} clipWindow - 裁剪窗口
 * @returns {Point} - 交点坐标
 */
function computeIntersection(code, p1, p2, clipWindow) {
  const { xmin, ymin, xmax, ymax } = clipWindow;
  const { x: x1, y: y1 } = p1;
  const { x: x2, y: y2 } = p2;
  
  let x, y;
  
  // 根据区域编码确定交点
  if ((code & RegionCode.TOP) !== 0) {
    // 与上边界相交
    x = x1 + (x2 - x1) * (ymax - y1) / (y2 - y1);
    y = ymax;
  } else if ((code & RegionCode.BOTTOM) !== 0) {
    // 与下边界相交
    x = x1 + (x2 - x1) * (ymin - y1) / (y2 - y1);
    y = ymin;
  } else if ((code & RegionCode.RIGHT) !== 0) {
    // 与右边界相交
    y = y1 + (y2 - y1) * (xmax - x1) / (x2 - x1);
    x = xmax;
  } else if ((code & RegionCode.LEFT) !== 0) {
    // 与左边界相交
    y = y1 + (y2 - y1) * (xmin - x1) / (x2 - x1);
    x = xmin;
  }
  
  return { x, y };
}

/**
 * Cohen-Sutherland直线裁剪算法
 * @param {Point} p1 - 线段起点 {x, y}
 * @param {Point} p2 - 线段终点 {x, y}
 * @param {Object} clipWindow - 裁剪窗口 {xmin, ymin, xmax, ymax}
 * @returns {Object|null} - 裁剪后的线段坐标,如果线段完全在窗口外则返回null
 */
function cohenSutherlandClip(p1, p2, clipWindow) {
  // 创建点的副本,避免修改原始数据
  let point1 = { ...p1 };
  let point2 = { ...p2 };
  
  // 计算端点的区域编码
  let code1 = computeCode(point1.x, point1.y, clipWindow);
  let code2 = computeCode(point2.x, point2.y, clipWindow);
  
  let isAccepted = false;
  
  // 主循环
  while (true) {
    // 情况1: 两端点都在裁剪窗口内
    if ((code1 | code2) === 0) {
      isAccepted = true;
      break;
    }
    
    // 情况2: 两端点都在裁剪窗口外的同一区域
    else if ((code1 & code2) !== 0) {
      break;
    }
    
    // 情况3: 线段部分在裁剪窗口内,需要裁剪
    else {
      // 选择一个在窗口外的端点
      const outCode = code1 !== 0 ? code1 : code2;
      
      // 计算交点
      const intersection = computeIntersection(
        outCode, 
        point1, 
        point2, 
        clipWindow
      );
      
      // 更新端点和区域编码
      if (outCode === code1) {
        point1 = intersection;
        code1 = computeCode(point1.x, point1.y, clipWindow);
      } else {
        point2 = intersection;
        code2 = computeCode(point2.x, point2.y, clipWindow);
      }
    }
  }
  
  // 返回裁剪结果
  return isAccepted ? {
    x1: point1.x,
    y1: point1.y,
    x2: point2.x,
    y2: point2.y
  } : null;
}

/**
 * 绘制裁剪窗口
 * @param {CanvasRenderingContext2D} ctx - Canvas上下文
 * @param {Object} clipWindow - 裁剪窗口
 * @param {Object} style - 绘制样式
 */
function drawClipWindow(ctx, clipWindow, style = {}) {
  const { xmin, ymin, xmax, ymax } = clipWindow;
  const { 
    strokeStyle = 'blue', 
    lineWidth = 2,
    fillStyle = 'rgba(200, 220, 255, 0.1)'
  } = style;
  
  ctx.save();
  
  // 设置样式
  ctx.strokeStyle = strokeStyle;
  ctx.lineWidth = lineWidth;
  ctx.fillStyle = fillStyle;
  
  // 绘制填充矩形
  ctx.fillRect(xmin, ymin, xmax - xmin, ymax - ymin);
  
  // 绘制边框
  ctx.strokeRect(xmin, ymin, xmax - xmin, ymax - ymin);
  
  // 绘制区域标签
  ctx.font = '12px Arial';
  ctx.fillStyle = 'rgba(0, 0, 100, 0.7)';
  ctx.textAlign = 'center';
  
  // 标记窗口四角的区域编码
  const padding = 15;
  ctx.fillText('1001', xmin - padding, ymin - padding);  // 左上
  ctx.fillText('1010', xmax + padding, ymin - padding);  // 右上
  ctx.fillText('0101', xmin - padding, ymax + padding);  // 左下
  ctx.fillText('0110', xmax + padding, ymax + padding);  // 右下
  
  ctx.restore();
}

/**
 * 绘制线段
 * @param {CanvasRenderingContext2D} ctx - Canvas上下文
 * @param {Object} line - 线段数据
 * @param {Object} style - 绘制样式
 */
function drawLine(ctx, line, style = {}) {
  const { x1, y1, x2, y2 } = line;
  const { 
    strokeStyle = 'red', 
    lineWidth = 1.5,
    drawEndpoints = false,
    endpointRadius = 4,
    dashPattern = []
  } = style;
  
  ctx.save();
  
  // 设置样式
  ctx.strokeStyle = strokeStyle;
  ctx.lineWidth = lineWidth;
  
  if (dashPattern.length > 0) {
    ctx.setLineDash(dashPattern);
  }
  
  // 绘制线段
  ctx.beginPath();
  ctx.moveTo(x1, y1);
  ctx.lineTo(x2, y2);
  ctx.stroke();
  
  // 绘制端点
  if (drawEndpoints) {
    ctx.fillStyle = strokeStyle;
    
    // 起点
    ctx.beginPath();
    ctx.arc(x1, y1, endpointRadius, 0, Math.PI * 2);
    ctx.fill();
    
    // 终点
    ctx.beginPath();
    ctx.arc(x2, y2, endpointRadius, 0, Math.PI * 2);
    ctx.fill();
  }
  
  ctx.restore();
}

/**
 * 获取线段区域编码的文本描述
 * @param {number} code - 区域编码
 * @returns {string} - 编码的二进制表示
 */
function getRegionCodeText(code) {
  // 将编码转换为4位二进制字符串
  return (code | 0).toString(2).padStart(4, '0');
}

// 导出所有函数和常量
export {
  RegionCode,
  computeCode,
  cohenSutherlandClip,
  drawClipWindow,
  drawLine,
  getRegionCodeText
}; 
  • 2.服务器配置

const http = require('http');
const fs = require('fs');
const path = require('path');

const PORT = 3000;

// MIME类型映射
const mimeTypes = {
  '.html': 'text/html',
  '.js': 'text/javascript',
  '.css': 'text/css',
  '.json': 'application/json',
  '.png': 'image/png',
  '.jpg': 'image/jpeg',
  '.gif': 'image/gif',
  '.svg': 'image/svg+xml',
  '.ico': 'image/x-icon'
};

// 创建HTTP服务器
const server = http.createServer((req, res) => {
  console.log(`请求: ${req.url}`);
  
  // 处理主页请求
  let filePath = '.' + req.url;
  if (filePath === './') {
    filePath = './cohenSutherlandDemo.html';
  }
  
  // 获取文件扩展名
  const extname = path.extname(filePath);
  
  // 设置默认的MIME类型
  let contentType = mimeTypes[extname] || 'application/octet-stream';
  
  // 读取文件
  fs.readFile(filePath, (err, content) => {
    if (err) {
      if (err.code === 'ENOENT') {
        // 文件未找到
        res.writeHead(404);
        res.end('404 Not Found');
      } else {
        // 服务器错误
        res.writeHead(500);
        res.end(`Server Error: ${err.code}`);
      }
    } else {
      // 成功响应
      // 添加正确的CORS头部,以允许ES模块加载
      res.writeHead(200, { 
        'Content-Type': contentType,
        'Access-Control-Allow-Origin': '*'
      });
      res.end(content, 'utf-8');
    }
  });
});

// 启动服务器
server.listen(PORT, () => {
  console.log(`服务器运行在 http://localhost:${PORT}/`);
  console.log('请使用浏览器访问上述地址查看Cohen-Sutherland算法演示');
}); 
  • 3.HTML前端页面

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Cohen-Sutherland直线裁剪算法演示</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
    <style>
        :root {
            --primary-color: #3f51b5;
            --secondary-color: #f50057;
            --success-color: #4caf50;
            --bg-color: #f8f9fa;
            --canvas-bg: #ffffff;
        }
        
        body {
            font-family: 'Microsoft YaHei', 'PingFang SC', sans-serif;
            background-color: var(--bg-color);
            margin: 0;
            padding: 20px;
            color: #333;
        }
        
        .container {
            max-width: 1000px;
            margin: 0 auto;
            background-color: #fff;
            border-radius: 10px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
            padding: 20px;
        }
        
        h1 {
            color: var(--primary-color);
            text-align: center;
            margin-bottom: 20px;
            font-weight: bold;
            font-size: 2rem;
        }
        
        .canvas-container {
            position: relative;
            margin: 20px 0;
            border-radius: 8px;
            overflow: hidden;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
        }
        
        #canvas {
            display: block;
            background-color: var(--canvas-bg);
            width: 100%;
            height: 500px;
            cursor: default;
        }
        
        .controls {
            display: flex;
            flex-wrap: wrap;
            gap: 10px;
            margin-bottom: 20px;
            justify-content: center;
        }
        
        .btn-primary {
            background-color: var(--primary-color);
            border-color: var(--primary-color);
        }
        
        .btn-danger {
            background-color: var(--secondary-color);
            border-color: var(--secondary-color);
        }
        
        .btn-success {
            background-color: var(--success-color);
            border-color: var(--success-color);
        }
        
        .legend {
            background-color: rgba(255, 255, 255, 0.9);
            border-radius: 8px;
            padding: 15px;
            margin-top: 20px;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
            display: flex;
            flex-wrap: wrap;
            gap: 15px;
        }
        
        .legend-item {
            display: flex;
            align-items: center;
            margin-right: 15px;
        }
        
        .legend-color {
            width: 20px;
            height: 3px;
            margin-right: 8px;
            border-radius: 2px;
        }
        
        .legend-point {
            width: 8px;
            height: 8px;
            border-radius: 50%;
            margin-right: 8px;
        }
        
        .blue {
            background-color: var(--primary-color);
        }
        
        .red {
            background-color: var(--secondary-color);
        }
        
        .green {
            background-color: var(--success-color);
        }
        
        .status-bar {
            margin-top: 10px;
            padding: 10px;
            border-radius: 5px;
            background-color: #f5f5f5;
            font-family: monospace;
            min-height: 40px;
        }
        
        .point-info {
            display: flex;
            justify-content: space-between;
            flex-wrap: wrap;
            margin-top: 10px;
        }
        
        .info-card {
            background-color: #f5f5f5;
            border-radius: 5px;
            padding: 10px;
            width: 48%;
            margin-bottom: 10px;
        }
        
        .code-display {
            font-family: monospace;
            font-weight: bold;
            color: var(--primary-color);
        }
        
        footer {
            text-align: center;
            margin-top: 20px;
            font-size: 0.8rem;
            color: #666;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Cohen-Sutherland直线裁剪算法演示</h1>
        
        <div class="controls">
            <button id="drawLineBtn" class="btn btn-primary">
                <i class="bi bi-pencil"></i> 绘制新线段
            </button>
            <button id="clipBtn" class="btn btn-success">
                <i class="bi bi-scissors"></i> 裁剪线段
            </button>
            <button id="resetBtn" class="btn btn-danger">
                <i class="bi bi-trash"></i> 重置
            </button>
            <button id="toggleCodeBtn" class="btn btn-secondary">
                <i class="bi bi-code-slash"></i> 显示/隐藏区域编码
            </button>
        </div>
        
        <div class="canvas-container">
            <canvas id="canvas"></canvas>
        </div>
        
        <div class="status-bar" id="statusBar">准备就绪。点击"绘制新线段"按钮开始。</div>
        
        <div class="point-info">
            <div class="info-card" id="point1Info">
                <h5>起点:</h5>
                <p>坐标:<span id="p1Coords">-</span></p>
                <p>区域编码:<span class="code-display" id="p1Code">-</span></p>
            </div>
            <div class="info-card" id="point2Info">
                <h5>终点:</h5>
                <p>坐标:<span id="p2Coords">-</span></p>
                <p>区域编码:<span class="code-display" id="p2Code">-</span></p>
            </div>
        </div>
        
        <div class="legend">
            <div class="legend-item">
                <div class="legend-color blue"></div>
                <span>裁剪窗口</span>
            </div>
            <div class="legend-item">
                <div class="legend-color red"></div>
                <span>原始线段</span>
            </div>
            <div class="legend-item">
                <div class="legend-color green"></div>
                <span>裁剪后的线段</span>
            </div>
            <div class="legend-item">
                <div class="legend-point red"></div>
                <span>线段端点</span>
            </div>
        </div>
        
        <footer>
            Cohen-Sutherland直线裁剪算法 &copy; 2023
        </footer>
    </div>
    
    <script type="module">
        // 导入优化后的Cohen-Sutherland模块
        import { 
            RegionCode, 
            computeCode, 
            cohenSutherlandClip,
            drawClipWindow,
            drawLine,
            getRegionCodeText
        } from './cohenSutherland.js';
        
        // DOM元素
        const canvas = document.getElementById('canvas');
        const statusBar = document.getElementById('statusBar');
        const p1CoordsElem = document.getElementById('p1Coords');
        const p2CoordsElem = document.getElementById('p2Coords');
        const p1CodeElem = document.getElementById('p1Code');
        const p2CodeElem = document.getElementById('p2Code');
        
        // 调整Canvas以适应容器大小
        function setupCanvas() {
            // 获取容器的宽度,高度固定为500px
            const containerWidth = canvas.parentElement.clientWidth;
            canvas.width = containerWidth;
            canvas.height = 500;
        }
        
        // 调用初始化
        setupCanvas();
        
        // 监听窗口大小变化,调整Canvas
        window.addEventListener('resize', setupCanvas);
        
        // 获取Canvas上下文
        const ctx = canvas.getContext('2d');
        
        // 裁剪窗口定义
        const clipWindow = {
            xmin: Math.round(canvas.width * 0.25),
            ymin: Math.round(canvas.height * 0.25),
            xmax: Math.round(canvas.width * 0.75),
            ymax: Math.round(canvas.height * 0.75)
        };
        
        // 状态变量
        let lines = [];
        let isDrawing = false;
        let startPoint = null;
        let selectedLine = null;
        let showRegionCodes = false;
        
        // 更新状态栏
        function updateStatus(message) {
            statusBar.textContent = message;
        }
        
        // 更新点信息
        function updatePointInfo(p1, p2) {
            if (p1) {
                p1CoordsElem.textContent = `(${Math.round(p1.x)}, ${Math.round(p1.y)})`;
                const code = computeCode(p1.x, p1.y, clipWindow);
                p1CodeElem.textContent = getRegionCodeText(code);
            } else {
                p1CoordsElem.textContent = '-';
                p1CodeElem.textContent = '-';
            }
            
            if (p2) {
                p2CoordsElem.textContent = `(${Math.round(p2.x)}, ${Math.round(p2.y)})`;
                const code = computeCode(p2.x, p2.y, clipWindow);
                p2CodeElem.textContent = getRegionCodeText(code);
            } else {
                p2CoordsElem.textContent = '-';
                p2CodeElem.textContent = '-';
            }
        }
        
        // 绘制所有元素
        function redraw() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            
            // 绘制裁剪窗口
            drawClipWindow(ctx, clipWindow, {
                strokeStyle: '#3f51b5',
                lineWidth: 2,
                fillStyle: 'rgba(63, 81, 181, 0.05)'
            });
            
            // 绘制区域编码标记
            if (showRegionCodes) {
                // 绘制中心区域标记
                ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
                ctx.font = '12px Arial';
                ctx.textAlign = 'center';
                ctx.textBaseline = 'middle';
                
                // 中心区域
                ctx.fillText('0000', (clipWindow.xmin + clipWindow.xmax) / 2, (clipWindow.ymin + clipWindow.ymax) / 2);
                
                // 上方区域
                ctx.fillText('1000', (clipWindow.xmin + clipWindow.xmax) / 2, clipWindow.ymin / 2);
                
                // 下方区域
                ctx.fillText('0100', (clipWindow.xmin + clipWindow.xmax) / 2, (clipWindow.ymax + canvas.height) / 2);
                
                // 左方区域
                ctx.fillText('0001', clipWindow.xmin / 2, (clipWindow.ymin + clipWindow.ymax) / 2);
                
                // 右方区域
                ctx.fillText('0010', (clipWindow.xmax + canvas.width) / 2, (clipWindow.ymin + clipWindow.ymax) / 2);
            }
            
            // 绘制所有线段
            for (const line of lines) {
                // 原始线段
                drawLine(ctx, line, {
                    strokeStyle: '#f50057',
                    lineWidth: 1.5,
                    drawEndpoints: true,
                    endpointRadius: 4
                });
                
                // 如果有裁剪结果,绘制裁剪后的线段
                if (line.clipped) {
                    drawLine(ctx, line.clipped, {
                        strokeStyle: '#4caf50',
                        lineWidth: 2.5,
                        dashPattern: [],
                        drawEndpoints: true,
                        endpointRadius: 4
                    });
                }
            }
            
            // 如果正在绘制,显示预览线段
            if (isDrawing && startPoint) {
                const mousePos = canvas.mousePosition || { x: 0, y: 0 };
                drawLine(ctx, {
                    x1: startPoint.x,
                    y1: startPoint.y,
                    x2: mousePos.x,
                    y2: mousePos.y
                }, {
                    strokeStyle: 'rgba(245, 0, 87, 0.5)',
                    lineWidth: 1.5,
                    dashPattern: [5, 3],
                    drawEndpoints: true
                });
            }
        }
        
        // 为所有线段应用裁剪算法
        function clipAllLines() {
            if (lines.length === 0) {
                updateStatus('没有线段可裁剪!');
                return;
            }
            
            for (const line of lines) {
                // 使用优化后的接口调用裁剪算法
                line.clipped = cohenSutherlandClip(
                    { x: line.x1, y: line.y1 },
                    { x: line.x2, y: line.y2 },
                    clipWindow
                );
            }
            
            redraw();
            updateStatus(`已完成${lines.length}条线段的裁剪。`);
        }
        
        // 绑定按钮事件
        document.getElementById('drawLineBtn').addEventListener('click', function() {
            if (isDrawing) {
                isDrawing = false;
                startPoint = null;
                canvas.style.cursor = 'default';
                updateStatus('取消绘制线段。');
            } else {
                isDrawing = true;
                startPoint = null;
                canvas.style.cursor = 'crosshair';
                updateStatus('请点击绘制线段的起点...');
            }
        });
        
        document.getElementById('clipBtn').addEventListener('click', function() {
            clipAllLines();
        });
        
        document.getElementById('resetBtn').addEventListener('click', function() {
            lines = [];
            isDrawing = false;
            startPoint = null;
            selectedLine = null;
            canvas.style.cursor = 'default';
            updatePointInfo(null, null);
            updateStatus('已重置。点击"绘制新线段"按钮开始。');
            redraw();
        });
        
        document.getElementById('toggleCodeBtn').addEventListener('click', function() {
            showRegionCodes = !showRegionCodes;
            redraw();
            updateStatus(showRegionCodes ? '显示区域编码。' : '隐藏区域编码。');
        });
        
        // 跟踪鼠标位置
        canvas.addEventListener('mousemove', function(e) {
            const rect = canvas.getBoundingClientRect();
            canvas.mousePosition = {
                x: e.clientX - rect.left,
                y: e.clientY - rect.top
            };
            
            // 如果正在绘制,更新预览
            if (isDrawing && startPoint) {
                redraw();
            }
        });
        
        // 处理鼠标点击
        canvas.addEventListener('mousedown', function(e) {
            if (!isDrawing) return;
            
            const rect = canvas.getBoundingClientRect();
            const x = e.clientX - rect.left;
            const y = e.clientY - rect.top;
            
            if (!startPoint) {
                // 设置起点
                startPoint = { x, y };
                updateStatus('请点击绘制线段的终点...');
                updatePointInfo({ x, y }, null);
            } else {
                // 设置终点,创建线段
                const endPoint = { x, y };
                
                // 创建新线段
                const newLine = {
                    x1: startPoint.x,
                    y1: startPoint.y,
                    x2: x,
                    y2: y,
                    clipped: null
                };
                
                lines.push(newLine);
                
                // 更新点信息
                updatePointInfo(startPoint, endPoint);
                
                // 重置绘制状态
                startPoint = null;
                isDrawing = false;
                canvas.style.cursor = 'default';
                
                updateStatus(`已添加线段 #${lines.length}。点击"裁剪线段"查看结果。`);
                redraw();
            }
        });
        
        // 初始绘制
        redraw();
        
        // 添加键盘快捷键
        document.addEventListener('keydown', function(e) {
            if (e.key === 'Escape') {
                // ESC键取消绘制
                if (isDrawing) {
                    isDrawing = false;
                    startPoint = null;
                    canvas.style.cursor = 'default';
                    updateStatus('取消绘制线段。');
                    redraw();
                }
            }
        });
    </script>
    
    <!-- 添加Bootstrap图标 -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.0/font/bootstrap-icons.css">
</body>
</html> 

3.运行结果

4.详细介绍

# Cohen-Sutherland直线裁剪算法演示

这是一个交互式的Cohen-Sutherland直线裁剪算法演示项目,使用现代JavaScript和Canvas技术实现。

## 项目介绍

Cohen-Sutherland算法是一种经典的二维直线裁剪算法,用于确定一条线段是否与给定的矩形窗口相交,并计算交点。该算法使用区域编码(Region Code)来快速判断线段是否需要裁剪。

### 主要特点

- 交互式线段绘制和裁剪

- 美观的用户界面

- 实时显示区域编码

- 详细的算法步骤可视化

- 响应式设计,适应不同屏幕大小

## 文件结构

- `cohenSutherland.js` - 算法核心实现,使用现代ES模块

- `cohenSutherlandDemo.html` - 交互式演示界面

- `server.js` - 用于本地运行的简易HTTP服务器

- `README.md` - 项目说明文档

## 使用方法

1. 启动本地服务器:

```bash

node server.js

```

2. 打开浏览器访问 `http://localhost:3000`

3. 使用界面功能:

   - 点击"绘制新线段"按钮,然后在画布上点击两次定义一条线段

   - 点击"裁剪线段"按钮对绘制的线段进行裁剪

   - 点击"显示/隐藏区域编码"按钮查看区域编码

   - 点击"重置"按钮清除所有线段

## 区域编码说明

Cohen-Sutherland算法将二维平面划分为9个区域,使用4位二进制编码表示点所在的区域:

```

    1001 | 1000 | 1010

    ----------------------

    0001 | 0000 | 0010

    ----------------------

    0101 | 0100 | 0110

```

每一位的含义:

- 第1位(LEFT):点在窗口左侧 (0001)

- 第2位(RIGHT):点在窗口右侧 (0010)

- 第3位(BOTTOM):点在窗口下方 (0100)

- 第4位(TOP):点在窗口上方 (1000)

中间区域(0000)表示点在窗口内部。

## 算法步骤

1. 计算线段两个端点P1(x1,y1)和P2(x2,y2)的区域编码code1和code2

2. 如果(code1 | code2) == 0,说明两点都在窗口内,直接接受该线段

3. 如果(code1 & code2) != 0,说明线段完全在窗口外的同一侧,直接拒绝该线段

4. 否则,线段部分在窗口内,需要裁剪:

   - 选择一个在窗口外的端点

   - 根据区域编码确定端点与窗口边界的交点

   - 用交点替换原来的端点

   - 重新计算新端点的区域编码

   - 重复上述步骤,直到两点都在窗口内或线段被拒绝

## 技术栈

- 原生JavaScript (ES6+)

- HTML5 Canvas

- CSS3

- Bootstrap 5 (样式)

- Node.js (本地服务器)

相关文章:

  • 深度学习天崩开局
  • docker 安装 awvs15
  • 计算机面试八股(自整)
  • 历年跨链合约恶意交易详解(四)——Chainswap20210711
  • 什么是EXR透视贴图 ?
  • 【云计算】打造高效容器云平台:规划、部署与架构设计
  • Linux第四章练习
  • 【Docker基础】--查阅笔记1
  • C语言中单链表操作:查找节点与删除节点
  • 滑动窗口例题
  • 通过 axios 请求回来的 HTML 字符串渲染到 Vue 界面上并添加样式
  • 五分钟快速清晰理解作用域和闭包以及封装
  • CPU 压力测试命令大全
  • 问问lua怎么写DeepSeek,,,,,
  • 基于连接池与重试机制的高效TDengine写入方案
  • IDEA 使用Maven打包时内存溢出
  • 服务器虚拟化技术深度解析:医药流通行业IT架构优化指南
  • 基于 Spring Boot 瑞吉外卖系统开发(二)
  • php调用大模型应用接口实现流式输出以及数据过滤
  • Redis常见问题排查与解决方案指南
  • 技术派|威胁F-35、击落“死神”,胡塞武装防空战力如何?
  • 国寿资产获批参与第三批保险资金长期投资改革试点
  • 国家统计局公布2024年城镇单位就业人员年平均工资情况
  • A股三大股指低收:汽车股领涨,大金融走弱,两市成交近1.1万亿元
  • 娃哈哈:自4月起已终止与今麦郎的委托代工关系,未来将坚持自有生产模式
  • 2025财政观察|长三角“三公”经费普降,钱要用在刀刃上