现在的位置: 首页 > 综合 > 正文

opengl绘制固定宽度多边形圆角边框算法(历时周半,撰博文以志纪念)

2018年05月18日 ⁄ 综合 ⁄ 共 5980字 ⁄ 字号 评论关闭

CGPoint.java

package org.bruce.roundcorner.core;

/**
 * @author BruceYang
 */
public class CGPoint {
	public float x;
	public float y;
	
	public CGPoint() {
		
	}
	public CGPoint(float x, float y) {
		this.x = x;
		this.y = y;
	}
	
	@Override
	public String toString() {
		return "x=" + this.x + ", y=" + this.y;
	}
}

Line.java

package org.bruce.roundcorner.core;


/**
 * @author BruceYang
 * 这个是对通用一次直线方程 A*x + B*y + C = 0 的封装~
 * 本来封装的是斜截式,不过发现当斜率k不存在的时候会比较麻烦,因此该用一般式
 * 再个就是接着用一般式的演变方式 x + B/A*y + C/A = 0,但是考虑到可能存在 x == 0 的情况,因此又舍弃~
 * 
 * 一般式还是无济于事,改回斜截式,多提供两个成员变量:
 * 一个 boolean 表示 k 是否存在,一个额外的 float 表示 k 不存在的时候直线方程 x=***, *** 等于多少~
 */
public class Line {
	// 特别声明为 public 类型,免得到时候访问的时候麻烦,到时候直接点就行了
	private boolean kExists;	// 大部分情况下k都应该是存在的,因此提供一个true 的默认值~

	public float k = 77885.201314f;
	public float b = 13145.207788f;
	public float extraX = 52077.881314f;
	
	public Line() {
	}
	
	/**
	 * @param k
	 * @param b
	 * 这是当 k 存在时的构造方法~
	 */
	public Line(float k, float b) {
		this.kExists = true;
		this.k = k;
		this.b = b;
	}
	
	/**
	 * @param extraX
	 * 这是当 k 不存在时的构造方法~
	 */
	public Line(float extraX) {
		this.kExists = false;
		this.extraX = extraX;
	}
	
	@Override
	public String toString() {
		return "Line.toString()方法被调用,y = k*x + b斜截式, k=" + this.k + ", b=" + this.b
				+", kExists=" + this.kExists + ", extraX=" + this.extraX;
	}
	
	public boolean iskExists() {
		return kExists;
	}
	public void setkExists(boolean kExists) {
		this.kExists = kExists;
	}
}

GenericBase.java

保密!!

 

PointsGeneratorGeneric.java

package org.bruce.roundcorner.core;

/**
 * @author BruceYang
 * 假设传入的参数为一个CGPoint的数组,这个数组包含了1个凸五边形的顶点(按顺序,顺时针方向),
 * CGPoint[] points = new CGPoint[5];
 * 首先,利用 points[4],points[0],points[1]这3个顶点做文章
 * 将该3点连接成一个3角形,这里按逆时针方向取,即 points[0], points[4], points[1]
 * 思路(遵循1个原则,将核心方法分步骤放在多个子方法里面,这样逻辑会比较清晰):
 * 首先算出 线段points[0]_points[4]、线段points[0]_points[1] 分别所在的直线方程,
 *  
 */

// 需要的一些附加功能:
// 0.传入2个点,得到经过这两个点的直线方程(斜截式 y = kx + b);
// 1.传入两个直线方程,一个圆角边框的宽度,得到同时满足与这两条直线距离最短的两个点(只有1个点才是符合要求的,依赖2步骤)~
// 2.传入直线方程(斜截式 y = kx + b,封装1个类,包含k,b两个参数,代表直线)以及两个点的参数,返回距离这条直线较近的1个点
// 3.至此已经成功获取该点圆角的圆心点,之后需要得到两个点,这两个点是圆心点距离这两条直线最短距离的点,要求一个新的附加功能方法,求得一条直线的垂直线的斜率
// 4.至此,生成圆角边框的4个最基本的点已经齐备了,
public class PointsGeneratorGeneric {
	/**
	 * @param p
	 * @return
	 * 该方法是本类的核心方法,传入的参数为一个凸多边形的顶点数组
	 * 输出的顶点数组的长度有讲究,详情对着下面的1行代码遐想
	 * int outPCount = inPCount*11 + 2 + inPCount;
	 * 
	 * 末尾,(inPCount + 1) 数量的存储着内框的点
	 * (inPCount*7 + 1) 数量的存储着外框的点
	 * 内外框的点是为圆角边框从内到外生成“梯度”做准备的,由于这种操作是非常频繁的
	 * 因此,只在有需要的时候调用合适的方法
	 * 还有些只用生成纯色圆角边框的情况,内外框的点就不用再做额外的输出了
	 * 为此,我准备提供两个方法,让用户能够进行灵活的选择~
	 * 
	 * 另外,这些代码到时候都是要转换成obj-c代码的,因此我决定不用数组的length属性
	 * (因为obj-c里面的数组对自己的长度是不自知的,到时候调整代码的时候方便一点)~
	 */
	// 需保证传入的顶点是按顺时针方向排布的~
	public static CGPoint[] genPolygonPoints(CGPoint[] p, int inPCount) {
		// 每个凸多边形顶点要绘制出一个扇形至少需要11个点,将所有这些扇形连结起来还需要额外的两个顶点~
		int outPCount = inPCount*11 + 2;
		CGPoint[] target = new CGPoint[outPCount];
		
		// 这是一个标识,记录着target已经被填充到哪个下标的元素了~
		int targetCurrentIndex = 0;
		
		CGPoint p0, p1, p2;
		for(int i = 0; i < inPCount; i ++) {
			// 用临时变量接收一下数组中的元素,下面使用的时候可以少些一组中括号下标
			p0 = p[i];
			p1 = (i-1 == -1) ? p[inPCount-1] : p[i-1];
			p2 = (i+1 < inPCount) ? p[i+1] : p[0];

			CGPoint[] pGroup = handleUnit(p0, p1, p2);
			
			// 算出一组构成圆角曲线的点数组就其加入到目标数组里面~
			for(int j = 0; j < 11; j ++) {
				target[targetCurrentIndex + j]  = pGroup[j];
			}
			targetCurrentIndex += 11;
		}
		target[outPCount-2] = target[0];
		target[outPCount-1] = target[1];
		return target;
	}
	
	
	/**
	 * @return
	 * 每次传入凸多边形的3个顶点,算出核心点附近的圆角曲线(共11个顶点)~
	 */
	public static CGPoint[] handleUnit(CGPoint p0, CGPoint p1, CGPoint p2) {
		// 得到可能是圆心点的4个点
		Line l_p0_p1 = GenericBase.getLine(p0, p1);
		Line l_p0_p2 = GenericBase.getLine(p0, p2);
		CGPoint[] circleCenters = GenericBase.getCircleCenter(l_p0_p1, l_p0_p2);
		
		// 确定得到的这4个圆心点中哪个是真正的圆心点
		Line l_p1_p2 = GenericBase.getLine(p1, p2);
		CGPoint circleCenter = GenericBase.getNearestPoint(circleCenters, l_p1_p2);
		
		// 求出圆心点在两条通过多边形2个相邻顶点的直线的投影点
		CGPoint projectivePoint1 = GenericBase.getProjectivePoint(circleCenter, l_p0_p1);
		CGPoint projectivePoint2 = GenericBase.getProjectivePoint(circleCenter, l_p0_p2);
		
		// 通过3个点生成圆角外边框的曲线~
		CGPoint[] pGroup = GenericBase.genGroupPoints(circleCenter, projectivePoint1, projectivePoint2);
		return pGroup;
	}

}

用例:

JFrameGeneric3.java

package org.bruce.roundcorner.test;

import java.awt.Graphics;

import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

import org.bruce.roundcorner.core.CGPoint;
import org.bruce.roundcorner.core.PointsGeneratorGeneric;
import org.bruce.roundcorner.test.component.BaseJFrame;

/**
 * @author BruceYang
 */
public class JFrameGeneric3 extends BaseJFrame {
	private static final long serialVersionUID = 526700305514755530L;
	// 设置属性~
	private static final int OVAL_RADIUS = 3;	// 该值为1的时候什么都看不见(3比较漂亮,清晰而不显粗)~
	private static final boolean DRAW_OVAL = true;
	private static final boolean DRAW_LINE = true;
	private static final boolean DRAW_STRING = false;
	
	// 对于不同定点数的图形,这个数值都要进行重新设置,规律为:顶点数*11 + 2 ~
	private static final int vertexCount = 35;
	
	private static P[] ps = new P[vertexCount];
	private InnerPanel ip;

	// 构造方法
	public JFrameGeneric3() {
		super();
		title = "生成点检视工具";
		bAtCenter = true;
		bAlwaysDisplay = false;
		bFastQuitConfirm = true;
		bResizable = true;
		WIDTH = 500;
		HEIGHT = 500;
		super.initialize();
	}

	public void loadPoints(P[] ps, CGPoint[] points) {
		for(int i = 0; i < points.length; i ++) {
			ps[i] = new P();
			int tmpX = (int)(points[i].x);
			int tmpY = (int)(points[i].y);
			ps[i].setX(tmpX);
			ps[i].setY(tmpY);
			System.out.println(tmpX + "  @@@" + i + "@@@ " + tmpY);
			System.out.println();
		}
	}

	/**
	 * @param input
	 * @return
	 * 该方法用于将所有点的y值进行取反变换
	 */
	protected CGPoint[] yAxisConvert(CGPoint[] input) {
		for(int i = 0; i < input.length; i ++) {
			input[i].y = -input[i].y;
		}
		return input;
	}

	
	@Override
	protected void personalize() {
		CGPoint[] input = new CGPoint[3];
		input[0] = new CGPoint(0, 0);
		input[1] = new CGPoint(250, (float)(500*Math.sqrt(3)/2));
		input[2] = new CGPoint(500, 0);
				
		CGPoint[] target = PointsGeneratorGeneric.genPolygonPoints(input, 3);
		
		loadPoints(ps, target);
		
		ip = new InnerPanel();
		this.add(ip);
	}
	
	class InnerPanel extends JPanel {
		private static final long serialVersionUID = 4614854384387952672L;
		@Override
		public void paint(Graphics g) {
			for(int i = 0 ; i < vertexCount; i ++) {
				if(DRAW_STRING) {
					g.drawString("p" + i, ps[i].x + 10, ps[i].y - 5);
				}
				if(DRAW_OVAL) {
					g.fillOval(ps[i].x, ps[i].y, OVAL_RADIUS, OVAL_RADIUS);
				}
				if(DRAW_LINE) {
					if(i != vertexCount-1) {
						g.drawLine(ps[i].x, ps[i].y, ps[i + 1].x, ps[i + 1].y);
					}
				}
			}
		}
	}
	class P {
		private int x;
		private int y;
		P() {
		}
		P(int x, int y){
			this.x = x;
			this.y = y;
		}
		public int getX() {
			return x;
		}
		public void setX(int x) {
			this.x = x;
		}
		public int getY() {
			return y;
		}
		public void setY(int y) {
			this.y = y;
		}
	}

	public static void main(String[] args) {
		try {
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		} catch (Exception e) {
			e.printStackTrace();
		}
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				new JFrameGeneric3();
			}
		});
	}
}

效果图片:

抱歉!评论已关闭.