直线段的代码裁剪算法

裁剪算法

直线段的代码裁剪算法

比如一张画布上分布着许多不均匀的直线段,而我们想在画布上找一个矩形的窗口,只想得到在矩形窗口内的直线段,这时候我们应该怎么去实现?
我们知道直线段可以用一个参数方程来表示,假设直线段的两个端点是(x1,y1),(x2,y2)。直线段的参数方程可以写成(x-x1)/(x2-x1) =(y-y1)/(y2-y1)=t,这个时候t的范围是[0,1]
第一步判断某条直线段是否会与矩形窗口相交,对于一条直线段,其斜率k是已知的,设一条直线L:y=k*x+b,让L经过窗口的四个顶点,得到四个截距。记最大斜率位bmax,最小斜率为bmin。对于画布内某一条直线段来说,首先判断其斜率是否在[bmin,bmax]之间,若不在,直接去掉该直线。
如果在,考虑一种思路。以窗口为中心,把画布分为9个区域。窗口内的区域标记为0,窗口外的区域都标记为1。给直线段的端点加上记号,端点在那个区域那个区域就标上对应区域的标记。
假设直线段的端点标记为P,Q。如果P|Q=0,说明直线段在窗口内,直接保留。否则如果P&Q=1,则说明与窗口有两个交点,其它情况与直线有一个交点。矩形窗口的四条边也可以用参数方程表示,让直线段依次与四条边相交,找出解,就是交点。设直线段的参数为t,窗口直线的参数为u,只有当t,u同时满足t,u∈[0,1]时,才是交点
下面是程序实现的代码

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>代码裁剪算法</title>
	</head>
	<script src="../作业1/dat.gui.js"></script>
	<script language="JavaScript">
		//窗口的属性
		var xl, xr, yb, yt;
		var canvas, context;
		//储存直线的两个端点与其相应的标志位
		var line = new Array();

		function init() {
			canvas = document.getElementById("output");
			context = canvas.getContext("2d");
			canvas.width = window.innerWidth;
			canvas.height = window.innerHeight;
			var controls = new function() {
				this.draw=function(){
					context.clearRect(0,0,window.innerWidth,window.innerHeight);
					drawWindow();
					drawLine();
				}
				this.cut = function() {
					//裁剪线段
					cutLine();
					context.clearRect(0,0,window.innerWidth,window.innerHeight);
					//重新描绘
					drawWindow();
					afterShow();
				}
				
			};
			var gui=new dat.GUI();
			gui.add(controls,'draw');
			gui.add(controls,'cut');
			drawWindow();
			drawLine();
		}
		//绘制窗口
		function drawWindow() {
			//设置窗口参数
			xl = window.innerWidth / 4;
			xr = 3 * window.innerWidth / 4;
			yb = window.innerHeight / 4;
			yt = window.innerHeight / 2;

			context.beginPath();
			context.moveTo(xl, yb);
			context.lineTo(xl, yt);
			context.lineTo(xr, yt);
			context.lineTo(xr, yb);
			context.closePath();
			context.stroke();
		}
		//画出四条线段,对应于四种情况
		function drawLine() {
			//完全在窗口外
			line[0] = new Array();
			line[0]["xb"] = window.innerWidth / 4;
			line[0]["yb"] = window.innerHeight / 8;
			line[0]["xg"] = window.innerWidth / 8;
			line[0]["yg"] = window.innerHeight / 3;
			line[0]["tagb"] = 0;
			line[0]["tagg"] = 0;
			line[0]["isprint"] = false;
			//一个交点
			line[1] = new Array();
			line[1]["xb"] = window.innerWidth / 2;
			line[1]["yb"] = window.innerHeight / 8;
			line[1]["xg"] = 5 * window.innerWidth / 8;
			line[1]["yg"] = 3 * window.innerHeight / 8;
			line[1]["tagb"] = 0;
			line[1]["tagg"] = 0;
			line[1]["isprint"] = false;
			//完全在窗口内
			line[2] = new Array();
			line[2]["xb"] = 3 * window.innerWidth / 8;
			line[2]["yb"] = 5 * window.innerHeight / 16;
			line[2]["xg"] = 5 * window.innerWidth / 8;
			line[2]["yg"] = 7 * window.innerHeight / 16;
			line[2]["tagb"] = 0;
			line[2]["tagg"] = 0;
			line[2]["isprint"] = false;
			//与窗口有两个交点
			line[3] = new Array();
			line[3]["xb"] = window.innerWidth / 8;
			line[3]["yb"] = 3 * window.innerHeight / 8;
			line[3]["xg"] = window.innerWidth / 2;
			line[3]["yg"] = 5 * window.innerHeight / 8;
			line[3]["tagb"] = 0;
			line[3]["tagg"] = 0;
			line[3]["isprint"] = false;
			for (var i = 0; i < 4; i++) {
				context.moveTo(line[i]["xb"], line[i]["yb"]);
				context.lineTo(line[i]["xg"], line[i]["yg"]);
			}
			context.stroke();
			SureTag();
		}
		//给线段两个端点加上标记
		function SureTag() {
			for (var i = 0; i < 4; i++) {
				line[i]["tagb"] = caculate(line[i]["xb"], line[i]["yb"]);
				line[i]["tagg"] = caculate(line[i]["xg"], line[i]["yg"]);
			}
		}
		//这里不在窗口内统一返回1
		function caculate(x, y) {
			if (x < xl) {
				return 1;
			} else if (x <= xr) {
				if (y >= yb && y <= yt)
					return 0;
				else
					return 1;
			} else {
				return 1;
			}
		}
		//裁剪直线的代码
		function cutLine() {
			var tline=new Array();
			for (var i = 0; i < 4; i++) {
				//两个端点在窗口内
				if ((line[i]["tagb"] || line[i]["tagg"]) == 0) {
					line[i]["isprint"] = true;
				}
				else {
					//两个端点都在窗口外
					//可能没有交点,可能有两个交点,可能一个交点
					//没有交点和一个交点都按照不在窗口内处理
					if ((line[i]["tagb"] && line[i]["tagg"]) == 1){
						//不在窗口内
						if(judge(line[i])==false){
							line[i]["isprint"]=false;
						}
						//在窗口内,裁剪出中间那一段线段
						else{
							tline=getCoor(line[i]);
							line[i]["xb"]=tline["xb"];
							line[i]["yb"]=tline["yb"];
							line[i]["xg"]=tline["xg"];
							line[i]["yg"]=tline["yg"];
							line[i]["isprint"]=true;
						}
					}
					//终止点在窗口内,裁剪
					else if (line[i]["tagg"] == 0) {
						tline=getCoor(line[i]);
						line[i]["xb"]=tline["xb"];
						line[i]["yb"]=tline["yb"];
						line[i]["isprint"]=true;
					} 
					//起始点在窗口内,裁剪
					else {
						tline=getCoor(line[i]);
						line[i]["xg"]=tline["xb"];
						line[i]["yg"]=tline["yb"];
						line[i]["isprint"]=true;
					}
				}
			}

		}
		//判断是否与多边形有交点
		function judge(line){
			var b=new Array();
			var maxb=0;
			var minb=0;
			var lineb=0;
			var k=(line["yg"]-line["yb"])/(line["xg"]-line["xb"]);
			//得出点斜式方程
			//y=k*(x-line["xg"])+line["yg"]
			//y=kx+b,b=y-kx
			b[0]=new Array();
			b[0]=yb-k*xl;
			b[1]=new Array();
			b[1]=yb-k*xr;
			b[2]=new Array();
			b[2]=yt-k*xr;
			b[3]=new Array();
			b[3]=yt-k*xl;
			//与四个顶点相交求出截距的范围
			maxb=Math.max(b[0],b[1],b[2],b[3]);
			minb=Math.min(b[0],b[1],b[2],b[3]);
			lineb=line["yg"]-k*line["xg"];
			if(lineb>minb && lineb<maxb)
				return true;
			else
				return false;
		}
		//获取交点的坐标,可以是一个交点或者是两个交点
		function getCoor(line){
			var tline=new Array();
			tline["tag"]=0;
			//记录是第几个交点
			//获取直线的参数方程,判断t的范围得出与那条边相交
			//y=(1-t)*yb+t*yg
			//x=(1-t)*xb+t*xg
			//分别与四条边求交点
			var t=0;
			
			//y=yb
			t=(yb-line["yb"])/(line["yg"]-line["yb"]);
			if(t>=0 && t<=1){
				tline["xb"]=(1-t)*line["xb"]+t*line["xg"];
				tline["yb"]=yb;
				tline["tag"]=1;
			}
			
			//y=yt
			t=(yt-line["yb"])/(line["yg"]-line["yb"]);
			if(t>=0 && t<=1){
				if(tline["tag"]==0){
					tline["xb"]=(1-t)*line["xb"]+t*line["xg"];
					tline["yb"]=yt;
					tline["tag"]=1;
				}
				else{
					tline["xg"]=(1-t)*line["xb"]+t*line["xg"];
					tline["yg"]=yt;
					tline["tag"]=2;
					//最多有两个交点
					return tline;
				}
			}
			//x=xl
			t=(xl-line["xb"])/(line["xg"]-line["xb"]);
			if(t>=0&&t<=1){
				if(tline["tag"]==0){
					tline["xb"]=xl;
					tline["yb"]=(1-t)*line["yb"]+t*line["yg"];
					tline["tag"]=1;
				}
				else{
					tline["xg"]=xl;
					tline["yg"]=(1-t)*line["yb"]+t*line["yg"];
					tline["tag"]=2;
					//最多有两个交点
					return tline;
				}
			}
			//x=xr
			t=(xr-line["xb"])/(line["xg"]-line["xb"]);
			if(t>=0&&t<=1){
				if(tline["tag"]==0){
					tline["xb"]=xr;
					tline["yb"]=(1-t)*line["yb"]+t*line["yg"];
					tline["tag"]=1;
				}
				else{
					tline["xg"]=xr;
					tline["yg"]=(1-t)*line["yb"]+t*line["yg"];
					tline["tag"]=2;
					//最多有两个交点
					return tline;
				}
			}
			return tline;
		}
		function afterShow(){
			for(var i=0;i<4;i++){
					if(line[i]["isprint"]==true){
					context.moveTo(line[i]["xb"],line[i]["yb"]);
					context.lineTo(line[i]["xg"],line[i]["yg"]);
				}
			}
			context.stroke();	
		}
		window.onload = init;
	</script>
	<body>
		<canvas id="output"></canvas>
	</body>
</html>

初始状态
直线段的代码裁剪算法

点击右上角cut之后
直线段的代码裁剪算法
可以看出代码成功裁剪了直线段,并且保留了窗口内的直线段。

上一篇:C++实现哈夫曼树


下一篇:2021-06-01