zoukankan      html  css  js  c++  java
  • 求过圆心直线与圆的两个交点

    主要是注意所使用的数据类型。

    之前用的是float,出现了一些意外,而且花费了我不少时间来反复验证、推导,

    做了很多的无用功,而且,反复推导得出来的计算步骤并没有什么不牢靠的地方。

    然后计算得到的结果却是让人如此之不省心,梗的我闷得慌。

    今天上午发来了一贴,多位朋友各抒己见,

    总算是让我发现了一些不足的地方,首当其冲的是一个变量弄错了,

    导致大批的计算失准。

    后来修正了这个bug以后,还是会出现计算不精确的地方。

    再后来便将涉及的所有成员变量由float 纠正为 double 类型,

    计算精度果然得到了提高,失准的地方再次被干掉。

    这次给自己的教训就是:

    涉及到精度比较高的数值运算的时候,还是得统统用 double。

    之前还以为 float 已经比较不错,能够满足基本的需求了,

    经过这次我总算是懂了,double的存在离我并不遥远。

    这个问题堵了我比较久了,大概也有快10个月了,当时没解决就规避之没去用了,

    今天能够解决这个遗留已久的问题,真是让人心情愉快!

    下面贴出 Objective-C 和 Java 的相关代码:

    Objective-C 部分(核心代码摘录)

    /** 已知两点,求过该两点的直线表达式~ */
    - (BYLine) getLine:(b2Vec2)p1 anotherPoint:(b2Vec2)p2 {
    	BYLine line;
    	if((p1.x - p2.x) != 0) {
            line.kExists = true;
    		line.k = (p1.y - p2.y) / (p1.x - p2.x);
    		line.b = p1.y - line.k * p1.x;
    	} else {
    		line.kExists = false;
    		line.extraX = p1.x;
    	}
    	return line;
    }
    /** 已知一点和直线斜率,求该直线的表达式~ */
    - (BYLine) getLine:(b2Vec2)point kParam:(double)kParam {
    	BYLine line;
        line.kExists = true;
    	line.k = kParam;
    	line.b = point.y - kParam * point.x;
    	return line;
    }
    - (double) getDistanceBetween2Points:(b2Vec2)p0 anotherPoint:(b2Vec2)p1 {
    	return sqrt(pow(p0.y - p1.y, 2) + pow(p0.x - p1.x, 2));
    }
    /** 获取一条直线上距离某点一定距离的两个点~ */
    - (b2Vec2*) get2Points:(BYLine)ln p:(b2Vec2)point pw:(double)pathWidth {
        b2Vec2* target = new b2Vec2[2];
        double circleRadius = pathWidth / 2;
        
        if(ln.k != 0) {
            // 斜率存在且不为 0~
            double kOfNewLine = -1 / ln.k;
            BYLine newLine = [self getLine:point kParam:kOfNewLine];
            
            // 经过数学运算,得出二元一次方程组的表达式
            double A = pow(newLine.k, 2) + 1;
            double B = 2 * (newLine.k * newLine.b - newLine.k * point.y - point.x);
            double C = pow(point.x, 2) + pow((newLine.b - point.y), 2) - pow(circleRadius, 2);
            double delta = pow(B, 2) - 4 * A * C;
            
            if(delta < 0) {    // 经实践检验有一定几率走入该分支,必须做特殊化处理~
                NSLog(@"竟然会无解,他妈的怎么回事儿啊!");
                target[0] = b2Vec2(point.x, point.y - circleRadius);
                target[1] = b2Vec2(point.x, point.y + circleRadius);
            } else {
                double x1 = (-B + sqrt(delta)) / (2 * A);
                double y1 = newLine.k * x1 + newLine.b;
                target[0] = b2Vec2(x1, y1);
                
                double x2 = (-B - sqrt(delta)) / (2 * A);
                double y2 = newLine.k * x2 + newLine.b;
                target[1] = b2Vec2(x2, y2);
            }
        } else {
            // 斜率存在且为 0~
            target[0] = b2Vec2(point.x, point.y - circleRadius);
            target[1] = b2Vec2(point.x, point.y + circleRadius);
        }
        NSLog(@"离中心点的距离为:%f", [self getDistanceBetween2Points:target[0] anotherPoint:point]);
        return target;
    }
    // 绘制触摸点到移动点的轨迹,1个像素~
    - (void) drawTouchPath {
        if(_mouseDown) {
            // 已知(2等分,用分数表示~)
            b2Vec2 pStart = _touchSegment.p1;
            b2Vec2 pEnd = _touchSegment.p2;
            
            // 推出
            b2Vec2 pMiddle = b2Vec2((pStart.x + pEnd.x) / 2, (pStart.y + pEnd.y) / 2);
            float pathLength = [self getDistanceBetween2Points:pStart anotherPoint:pEnd];
            
            // 设置触摸轨迹的宽度~
            float pathWidth = pathLength / 3.0f;
            if(pathWidth > TOUCH_PATH_MAX_WIDTH) {
                pathWidth = TOUCH_PATH_MAX_WIDTH;
            }
            
            b2Vec2* result;
            BYLine expFunc = [self getLine:pStart anotherPoint:pEnd];
            if(expFunc.kExists) {   // 斜率存在~
                result = [self get2Points:expFunc p:pMiddle pw:pathWidth];
            } else {                // 斜率不存在~
                result = new b2Vec2[2];
                result[0] = b2Vec2(pMiddle.x - pathWidth / 2, pMiddle.y);
                result[1] = b2Vec2(pMiddle.x + pathWidth / 2, pMiddle.y);
            }
            
            b2Vec2 finalResult[5];
            finalResult[0] = pStart;
            finalResult[1] = result[0];
            finalResult[2] = pEnd;
            finalResult[3] = result[1];
            finalResult[4] = pStart;
    
            // 绘制白色内容物~
            glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
            glVertexPointer(2, GL_FLOAT, 0, finalResult);
            glDrawArrays(GL_TRIANGLE_STRIP, 0, 5);
        }
    }

    Java 部分(部件齐全,能直接拿来跑的)

    package org.bruce.vertices.controller.geometry;
    
    /**
     * @author BruceYang
     * 对点的抽象~
     */
    public class CGPoint {
    	public double x;
    	public double y;
    	
    	public CGPoint() {
    		
    	}
    	public CGPoint(double x, double y) {
    		this.x = x;
    		this.y = y;
    	}
    	
    	@Override
    	public String toString() {
    		return "x=" + this.x + ", y=" + this.y;
    	}
    }
    
    *****************************************************

    package org.bruce.vertices.controller.geometry;
    
    
    /**
     * @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 CGLine {
    	// 特别声明为public类型,免得到时候访问的时候麻烦,到时候直接点就行了
    	private boolean kExists;	// 大部分情况下 k 都应该是存在的,因此提供一个 true 的默认值~
    
    	public double k = 77885.201314f;
    	public double b = 13145.207788f;
    	public double extraX = 52077.881314f;
    	
    	
    	/**
    	 * 这是当 k 存在时的构造方法~
    	 * @param k
    	 * @param b
    	 */
    	public CGLine(double k, double b) {
    		this.kExists = true;
    		this.k = k;
    		this.b = b;
    	}
    	
    	/**
    	 * 已知两点,求直线的方程~
    	 * @param p1
    	 * @param p2
    	 */
    	public CGLine(CGPoint p1, CGPoint p2) {
    		if((p1.x - p2.x) != 0) {
    			CGDbg.println("y = k*x + b, k exits!!");
    			this.kExists = true;
    			this.k = (p1.y - p2.y)/(p1.x - p2.x);
    			this.b = (p1.y - p1.x * k);
    		} else {
    			CGDbg.println("y = k*x + b, k doesn't exists!!");
    			// 如果走进这个分支,表示直线垂直于x轴,斜率不存在,保留k的默认值~
    			this.kExists = false;
    			this.extraX = p1.x;
    		}
    		CGDbg.print("过p1("+p1.x+", " +p1.y + "), p2("+p2.x+", "+p2.y+")两点的直线方程表达式为: ");
    		if(kExists) {
    			CGDbg.println("y = " + k + "*x + " + b);
    		} else {
    			CGDbg.println("x = " + extraX + "(垂直于x轴!)");
    		}
    	}
    	
    	/**
    	 * 点斜式~
    	 * @param p	某点
    	 * @param k	过该点的直线的斜率
    	 */
    	public CGLine(double k, CGPoint p) {
    		/**
    		 * (y-y') = k*(x-x')
    		 * 变形成斜截式为:
    		 * y = k*x + y' - k*x'
    		 * k = k, b = y'-k*x'
    		 */
    		this.kExists = true;
    		this.k = k;
    		this.b = p.y - k * p.x;
    	}
    	
    	/**
    	 * 这是当 k 不存在时的构造方法~
    	 * @param extraX
    	 */
    	public CGLine(double 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;
    	}
    }

    *****************************************************

    package org.bruce.vertices.controller.geometry;
    
    /**
     * @author Bruce Yang
     * 用于打印调试~
     */
    public class CGDbg {
    	public static final boolean DEBUG_MODE = true;
    	
    	// 方便进行调试信息的输出,开关~
    	public static void println(Object info) {
    		if(DEBUG_MODE) {			
    			System.out.println(info);
    		}
    	}
    	public static void print(Object info) {
    		if(DEBUG_MODE) {			
    			System.out.print(info);
    		}
    	}
    }

    *****************************************************

    package org.bruce.vertices.controller.geometry;
    
    /**
     * @author BruceYang
     */
    public class CGGeometryLib {
    	
    	/**
    	 * @param p0	第一个点的坐标
    	 * @param p1	第二个点的坐标
    	 * @return		两个点之间的距离
    	 * 计算出两点之间的距离
    	 */
    	public static double getDistanceBetween2Points(CGPoint p0, CGPoint p1) {
    		double distance = Math.sqrt(Math.pow(p0.y - p1.y, 2) + Math.pow(p0.x - p1.x, 2));
    		return distance;
    	}
    	
    	/**
    	 * @param p
    	 * @param l
    	 * @return		该方法用于获取某点在某条直线上的投影点的坐标
    	 */
    	public static CGPoint getProjectivePoint(CGPoint p, CGLine l) {
    		CGPoint target = null;
    		if(l.iskExists()) {
    			if(l.k != 0) {
    				CGLine newLine = new CGLine(-1/l.k, p.y -(-1/l.k)*p.x);
    				target = getCrossPoint(l, newLine);
    			} else {	// 如果直线l的斜率存在且斜率的值为0,明显是一条平行于x轴的直线~
    				// 此时,点 p 到直线 l 的距离为:Math.abs(p.y-l.b)
    				target = new CGPoint(p.x, l.b);
    			}
    		} else {	// 如果直线l的斜率不存在,明显是一条垂直于x轴的直线~
    			// 此时,点 p 到直线 l 的距离为:Math.abs(p.x-l.extraX)
    			target = new CGPoint(l.extraX, p.y);
    		}
    		CGDbg.println("点 ("+p.x+", "+p.y+") 在直线:y="+l.k+"x+"+l.b+" 上的投影点为 ("+target.x+", "+target.y+")");
    		return target;
    	}
    	
    	/**
    	 * 该方法用于求出两条直线的交点坐标
    	 * 这个方法是定制的,只有 l1, l2 均存在斜率 k 时方能使用(限制取消)~
    	 * @param l1
    	 * @param l2
    	 * @return
    	 */
    	public static CGPoint getCrossPoint(CGLine l1, CGLine l2) {
    //		dbgPrintln("into the getCrossPoint, l1: " + l1);
    //		dbgPrintln("into the getCrossPoint, l2: " + l2);
    		double x, y;
    		if(l1.iskExists() && l2.iskExists()) {
    			x = (l2.b - l1.b) / (l1.k - l2.k);
    			y = l1.k * x + l1.b;
    		} else if(!l1.iskExists() && l2.iskExists()) {
    			x = l1.extraX;
    			y = l2.k * x + l2.b;
    		} else if(l1.iskExists() && !l2.iskExists()) {
    			x = l2.extraX;
    			y = l1.k * x + l1.b;
    		} else {
    			// 两条直线的斜率都不存在?!,不可能发生的情况!!
    			x = 0;
    			y = 0;
    		}
    		CGDbg.println("getCrossPoint, CGPoint(x=" + x + ", y=" + y + ")");
    		return new CGPoint(x, y);
    	}
    
    	/**
    	 * @param args
    	 * 怎样判断是否符合要求?将过每组3个点中除开多边形顶点的两个点的直线方程求出来
    	 * 比较求出的 4 个 候选圆心点 哪个与此直线离的比较近,哪个就是符合要求的圆心点
    	 * 以下方法用于获取离特定直线距离最近的一个点(目前只支持斜率k存在的直线,以后慢慢扩充)!
    	 * 要得到距离特定直线距离最远的一个点只要稍作改动即可!
    	 */
    	public static CGPoint getNearestPoint(CGPoint[] points, CGLine line) {
    		double minDistance = 0;
    		int minIndex = 0;
    		if(line.iskExists()) {
    			// 直线斜率存在的分支~
    			for(int i = 0; i < points.length; ++ i) {
    				CGPoint p = points[i];
    				double d = Math.abs(line.k*p.x-p.y+line.b)/Math.sqrt(Math.pow(line.k,2)+1);
    				if(i == 0) {
    					// 赋予初值,不然 minDistance 的值就为 0 了~
    					minDistance = d;
    				}
    				if(d < minDistance) {
    					minDistance = d;
    					minIndex = i;
    				}
    			}
    		} else {
    			// 直线斜率不存在的分支(亦即直线垂直于 x 轴)~
    			for(int i = 0; i < points.length; ++ i) {
    				CGPoint p = points[i];
    				double d = Math.abs(p.x - line.extraX);
    				if(i == 0) {
    					// 赋予初值,不然minDistance的值就为0了~
    					minDistance = d;
    				}
    				if(d < minDistance) {
    					minDistance = d;
    					minIndex = i;
    				}
    			}
    		}
    		CGPoint dest = points[minIndex];
    		CGDbg.println("即将离开chooseOne()方法,圆心点为:("+dest.x+", "+dest.y+")");
    		return dest;
    	}
    	
    	/**
    	 * 获取传入两点的中点~
    	 * @param p1
    	 * @param p2
    	 * @return
    	 */
    	public static CGPoint getMiddlePoint(CGPoint p1, CGPoint p2) {
    		return new CGPoint((p1.x + p2.x) / 2.0f, (p1.y + p2.y) / 2.0f);
    	}
    	
    	/**
    	 * 封装一下 Math 的 pow 、sqrt 方法,调用起来方便一些~
    	 * @param d1
    	 * @param d2
    	 * @return
    	 */
    	public static double pow(double d1, double d2) {
    		return Math.pow(d1, d2);
    	}
    	public static double sqrt(double d) {
    		return Math.sqrt(d);
    	}
    	public static double sin(double theta) {
    		return Math.sin(theta);
    	}
    	public static double cos(double theta) {
    		return Math.cos(theta);
    	}
    	
    	/**
    	 * 传入线段的两个端点,获取中点,以该中点为圆心做半径为 radius 的圆,
    	 * 经过线段中点做线段的垂线,返回垂线与圆的两个交点~
    	 * Objective-C 里面的结果有点儿问题,不知道是什么原因,来java 里面碰碰有运气~
    	 * @param p1		线段端点1
    	 * @param p2		线段端点2
    	 * @param radius	圆半径
    	 * @return			线段中垂线与圆的两个交点~
    	 */
    	public static CGPoint[] getWhatIWanted(CGPoint p1, CGPoint p2, double radius) {
    		CGPoint[] target = new CGPoint[2];
    		CGPoint pMiddle = getMiddlePoint(p1, p2);
    //		double segLength = getDistanceBetween2Points(p1, p2);
    		
    		CGLine l1 = new CGLine(p1, p2);
    		if(l1.iskExists()) {
    			if(l1.k != 0) {
    				double kOfNewLine = -1 / l1.k;
    				CGLine newLine = new CGLine(kOfNewLine, pMiddle);
    				
    		        // 经过数学运算,得出二元一次方程组的表达式
    		        double A = pow(newLine.k, 2) + 1;
    		        double B = 2 * (newLine.k * newLine.b - newLine.k * pMiddle.y - pMiddle.x);
    		        double C = pow(pMiddle.x, 2) + pow((newLine.b - pMiddle.y), 2) - pow(radius, 2);
    		        double delta = pow(B, 2) - 4 * A * C;
    		        
    		        if(delta < 0) {    // 经实践检验有一定几率走入该分支,必须做特殊化处理~
    		        	// 2012。04。28。20。01,精度不够所致,换成double后无该情况出现~
    		            CGDbg.println("竟然会无解,他妈的怎么回事儿啊!");
    		            target[0] = new CGPoint(pMiddle.x, pMiddle.y - radius);
    		            target[1] = new CGPoint(pMiddle.x, pMiddle.y + radius);
    		        } else {
    		            double x1 = (-B + sqrt(delta)) / (2 * A);
    		            double y1 = newLine.k * x1 + newLine.b;
    		            target[0] = new CGPoint(x1, y1);
    		            
    		            double x2 = (-B - sqrt(delta)) / (2 * A);
    		            double y2 = newLine.k * x2 + newLine.b;
    		            target[1] = new CGPoint(x2, y2);
    		        }
    			} else {
    		        target[0] = new CGPoint(pMiddle.x, pMiddle.y - radius);
    		        target[1] = new CGPoint(pMiddle.x, pMiddle.y + radius);
    			}
    		} else {
    			target[0] = new CGPoint(pMiddle.x - radius, pMiddle.y);
    			target[1] = new CGPoint(pMiddle.x + radius, pMiddle.y);
    		}
    		System.out.println("target[0] 距离中点的距离为:" + getDistanceBetween2Points(target[0], pMiddle));
    		System.out.println("target[1] 距离中点的距离为:" + getDistanceBetween2Points(target[1], pMiddle));
    		return target;
    	}
    	
    	/**
    	 * 测试实用性,测试结果如下:
    	 * 之前用 float 类型的时候,每隔 1 度测试一次,共测试一个圆周,无解的情况出现一次
    	 * 每隔 1 度测试一次, 共测试一个圆周,无解的情况无。
    	 * 每隔 0.5 度测试一次,共测试一个圆周,无解的情况只出现一次
    	 * @param args
    	 */
    	public static void main(String[] args) {
    		double currentRadian = 0;
    		double deltaRadian = Math.PI / 360;
    		double bigRadius = 50;
    		double smallRadius = 20;
    		CGPoint origin = new CGPoint(0, 0);	// 原点~
    		CGPoint tail = null;	// tail 是尾巴、末梢的意思~
    		for(int i = 0; i < 720; ++ i) {
    			System.out.println(" -- 第 "+ i + "度!");
    			tail = new CGPoint(bigRadius*cos(currentRadian), bigRadius*sin(currentRadian));
    			currentRadian += deltaRadian;
    			getWhatIWanted(origin, tail, smallRadius);
    		}
    	}
    }

  • 相关阅读:
    MVC的各个部分都有那些技术来实现?如何实现?
    spring入门学习
    动态 SQL 及安全性(Birt vs 润乾)
    报表的 SQL 植入风险及规避方法
    web 如何实现精准打印
    birt 报表与润乾报表对比
    ireport 与润乾报表对比
    银行存折套打续打功能 -- 报表如何记录上次打印的位置
    脚本中如何做填报数据校验
    鼠标悬停出现提示信息怎么做
  • 原文地址:https://www.cnblogs.com/java20130723/p/3212295.html
Copyright © 2011-2022 走看看