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