图形裁剪算法

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值