图形裁剪算法

news2025/4/18 10:34:47

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 (本地服务器)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2330624.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

R 语言科研绘图第 36 期 --- 饼状图-基础

在发表科研论文的过程中&#xff0c;科研绘图是必不可少的&#xff0c;一张好看的图形会是文章很大的加分项。 为了便于使用&#xff0c;本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中&#xff0c;获取方式&#xff1a; R 语言科研绘图模板 --- sciRplothttps://mp.…

vue 3 从零开始到掌握

vue3从零开始一篇文章带你学习 升级vue CLI 使用命令 ## 查看vue/cli版本&#xff0c;确保vue/cli版本在4.5.0以上 vue --version ## 安装或者升级你的vue/cli npm install -g vue/cli ## 创建 vue create vue_test ## 启动 cd vue_test npm run servenvm管理node版本&#…

【R语言绘图】圈图绘制代码

绘制代码 rm(list ls())# 加载必要包 library(data.table) library(circlize) library(ComplexHeatmap) library(rtracklayer) library(GenomicRanges) library(BSgenome) library(GenomicFeatures) library(dplyr)### 数据准备阶段 ### # 1. 读取染色体长度信息 df <- re…

Python爬虫第6节-requests库的基本用法

目录 前言 一、准备工作 二、实例引入 三、GET请求 3.1 基本示例 3.2 抓取网页 3.3 抓取二进制数据 3.4 添加headers 四、POST请求 五、响应 前言 前面我们学习了urllib的基础使用方法。不过&#xff0c;urllib在实际应用中存在一些不便之处。以网页验证和Cookies处理…

什么是可靠性工程师?

一、什么是可靠性工程师&#xff1f; 可靠性工程师就是负责确保产品在使用过程中不出故障、不给客户添麻烦。 你可以理解为是那种“挑毛病的人”&#xff0c;但不是事后挑&#xff0c;是提前想清楚产品在哪些情况下可能会出问题&#xff0c;然后解决掉。 比如&#xff1a; …

如何根据设计稿进行移动端适配:全面详解

如何根据设计稿进行移动端适配&#xff1a;全面详解 文章目录 如何根据设计稿进行移动端适配&#xff1a;全面详解1. **理解设计稿**1.1 设计稿的尺寸1.2 设计稿的单位 2. **移动端适配的核心技术**2.1 使用 viewport 元标签2.1.1 代码示例2.1.2 参数说明 2.2 使用相对单位2.2.…

【Kafka基础】Kafka 2.8以下版本的安装与配置指南:传统ZooKeeper依赖版详解

对于仍在使用Kafka 2.8之前版本的团队来说&#xff0c;需要特别注意其强依赖外部ZooKeeper的特性。本文将完整演示传统架构下的安装流程&#xff0c;并对比新旧版本差异。 1 版本特性差异说明 1.1 2.8 vs 2.8-核心区别 特性 2.8版本 2.8-版本 协调服务 可选内置KRaft模式 …

Redis-x64-3.2.100.msi : Windows 安装包(MSI 格式)安装步骤

Redis-x64-3.2.100.msi 是 Redis 的 Windows 安装包&#xff08;MSI 格式&#xff09;&#xff0c;适用于 64 位系统。 在由于一些环境需要低版本的Redis的安装包。 Redis-x64-3.2.100.msi 安装包下载&#xff1a;https://pan.quark.cn/s/cc4d38262a15 Redis 是一个开源的 内…

【云计算】打造高效容器云平台:规划、部署与架构设计

引言 随着移动互联网时代的大步跃进&#xff0c;互联网公司业务的爆炸式增长发展给传统行业带来了巨大的冲击和挑战&#xff0c;被迫考虑转型和调整。对于我们传统的航空行业来说&#xff0c;还存在传统的思维、落后的技术。一项新业务从提出需求到立项审批、公开招标、项目实…

DeepSeek底层揭秘——《推理时Scaling方法》内容理解

4月初&#xff0c;DeepSeek 提交到 arXiv 上的最新论文正在 AI 社区逐渐升温。 论文核心内容理解 DeepSeek与清华大学联合发布的论文《奖励模型的推理时Scaling方法及其在大规模语言模型中的应用》&#xff0c;核心在于提出一种新的推理时Scaling方法&#xff0c;即通过动态调…

JavaScript之Json数据格式

介绍 JavaScript Object Notation&#xff0c; js对象标注法&#xff0c;是轻量级的数据交换格式完全独立于编程语言文本字符集必须用UTF-8格式&#xff0c;必须用“”任何支持的数据类型都可以用JSON表示JS内内置JSON解析JSON本质就是字符串 Json对象和JS对象互相转化 前端…

使用 Rsync + Lsyncd 实现 CentOS 7 实时文件同步

文章目录 &#x1f300;使用 Rsync Lsyncd 实现 CentOS 7 实时文件同步前言介绍架构图&#x1f9f1;系统环境&#x1f527;Rsync配置&#xff08;两台都需安装&#xff09;关闭SELinux&#xff08;两台都需&#xff09; &#x1f4e6;配置目标端&#xff08;client&#xff09…

Android studio学习之路(六)--真机的调试以及多媒体照相的使用

多媒体应用&#xff08;语言识别&#xff0c;照相&#xff0c;拍视频&#xff09;在生活的各个方面都具有非常大的作用&#xff0c;所以接下来将会逐步介绍多媒体的使用&#xff0c;但是在使用多媒体之前&#xff0c;使用模拟器肯定是不行的&#xff0c;所以我们必须要使用真机…

Qt 资源文件(.qrc 文件)

Qt 资源文件&#xff08;.qrc 文件&#xff09;是 Qt 提供的一种机制&#xff0c;用来将文件&#xff08;如图像、音频、文本文件等&#xff09;嵌入到应用程序中&#xff0c;使得这些文件不需要依赖外部文件路径&#xff0c;而是直接打包到程序的可执行文件中。通过使用 Qt 资…

PandaAI:一个基于AI的对话式数据分析工具

PandaAI 是一个基于 Python 开发的自然语言处理和数据分析工具&#xff0c;支持问答式&#xff08;ChatGPT&#xff09;的数据分析和报告生成功能。PandaAI 提供了一个开源的框架&#xff0c;主要核心组件包含用于数据处理的数据准备层&#xff08;Pandas&#xff09;以及实现 …

【C++算法】50.分治_归并_翻转对

文章目录 题目链接&#xff1a;题目描述&#xff1a;解法C 算法代码&#xff1a;图解 题目链接&#xff1a; 493. 翻转对 题目描述&#xff1a; 解法 分治 策略一&#xff1a;计算当前元素cur1后面&#xff0c;有多少元素的两倍比我cur1小&#xff08;降序&#xff09; 利用单…

基于pycatia的CATIA层级式BOM生成器开发全解析

引言:BOM生成技术的革新之路 在高端装备制造领域,CATIA的BOM管理直接影响着研发效率和成本控制。传统VBA方案 虽能实现基础功能,但存在代码维护困难、跨版本兼容性差等痛点。本文基于pycatia框架,提出一种支持动态层级识别、智能查重、Excel联动的BOM生成方案,其核心突破…

Flink 1.20 Kafka Connector:新旧 API 深度解析与迁移指南

Flink Kafka Connector 新旧 API 深度解析与迁移指南 一、Flink Kafka Connector 演进背景 Apache Flink 作为实时计算领域的标杆框架&#xff0c;其 Kafka 连接器的迭代始终围绕性能优化、语义增强和API 统一展开。Flink 1.20 版本将彻底弃用基于 FlinkKafkaConsumer/FlinkK…

2025年渗透测试面试题总结- 某四字大厂面试复盘扩展 一面(题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 某四字大厂面试复盘扩展 一面 一、Java内存马原理与查杀 二、冰蝎与哥斯拉原理对比&#xff08;技术演…

批量压缩 jpg/png 等格式照片|批量调整图片的宽高尺寸

图片格式种类非常的多&#xff0c;并且不同的图片由于像素、尺寸不一样&#xff0c;可能占用的空间也会不一样。文件太大会占用较多的磁盘空间&#xff0c;传输及上传系统都非常不方便&#xff0c;可能会收到限制&#xff0c;因此我们经常会碰到需要对图片进行压缩的需求。如何…