【虎书】Fundamentals of Computer Graphics(Fourth Edition)第二章习题解答

注:本题解为笔者个人对于习题的解答,不保证对,仅供参考。

Exercises

Exercise 1: Cardinality of IEEE floats

The cardinality of a set is the number of elements it contains. Under IEEE floating point representation (Section 1.5), what is the cardinality of the floats?

Solution:

具体答案需要分情况讨论:

根据IEEE754 [ 1 ] ^{[1]} [1],单精度浮点数(Single)由三个相互独立的部分组成:① 1-bit sign(值用 s s s表示);② 8-bit biased exponent (指数阶码,其中 b i a s = + 127 bias=+127 bias=+127,用 e = E + b i a s e=E+bias e=E+bias表示);③ 23-bit fraction(值用 f f f表示)。

单精度浮点数的具体表示如下(注:小数域的 f f f只有23位,但最高位1被省略了,所以实际表示为 1 ⋅ f 1\cdot f 1f,当然非规范数表示为 0 ⋅ f 0\cdot f 0f):

  1. e = 255 e=255 e=255 f ≠ 0 f\ne0 f=0,表示NaN,一共 2 1 × 2 0 × ( 2 23 − 1 ) 2^1\times2^0\times(2^{23}-1) 21×20×(2231)种表示;

  2. e = 255 e=255 e=255 f = 0 f=0 f=0,表示 ( − 1 ) s ∞ (-1)^s\infty (1)s,即 ± ∞ , \pm\infty, ±一共 2 1 × 2 0 × 2 0 2^1\times2^0\times2^0 21×20×20种表示;

  3. e = 0 e=0 e=0 f = 0 f=0 f=0,表示 ( − 1 ) s 0 (-1)^s0 (1)s0,即 ± 0 \pm0 ±0,一共 2 1 × 2 0 × 2 0 2^1\times2^0\times2^0 21×20×20种表示;

  4. 0 < e < 255 0\lt e\lt255 0<e<255,表示 ( − 1 ) s 2 e − 127 ( 1 ⋅ f ) (-1)^s2^{e-127}(1\cdot f) (1)s2e127(1f),一共 2 1 × ( 2 8 − 2 ) × 2 23 2^1\times(2^8-2)\times2^{23} 21×(282)×223种表示;

  5. e = 0 e=0 e=0 f ≠ 0 f\ne0 f=0,表示 ( − 1 ) s 2 − 126 ( 0 ⋅ f ) (-1)^s2^{-126}(0\cdot f) (1)s2126(0f),一共 2 1 × 2 0 × 2 23 2^1\times2^0\times2^{23} 21×20×223种表示,这些数为非规范数(denormalized number)。

情况1:如果不同的二进制表示在集合中算不同的元素,则floats集合的基数为 2 32 = 4294967296 2^{32}=4294967296 232=4294967296

情况2:Single表示中有一些特殊的数字(如NaN, ± ∞ \pm\infty ± ± 0 \pm0 ±0),它们中的NaN存在多种表示,但对于集合来说只算同一个元素,则根据上述前3条,floats集合的基数为 2 32 − 2 1 × 2 0 × ( 2 23 − 1 ) + 1 = 4278190083 2^{32}-2^1\times2^0\times(2^{23}-1)+1=4278190083 23221×20×(2231)+1=4278190083

情况3:在情况2的基础上,如果非规范数不算在floats集合中,则基数为 4278190083 − 2 1 × 2 0 × 2 23 = 4261412867 4278190083-2^1\times2^0\times2^{23}=4261412867 427819008321×20×223=4261412867

情况4:如果根据对于计算机图形学是否有效来确定集合的话,那么唯有NaN是无效的,把它去掉后的基数为 4278190083 − 1 = 4278190082 4278190083-1=4278190082 42781900831=4278190082.

其余情况可以自行讨论。

Exercise 2: Existence of inverse function

Is it possible to implement a function that maps 32-bit integers to 64-bit integers that has a well-defined inverse? Do all functions from 32-bit integers to 64-bit integers have well-defined inverses?

Solution:
显然不可能。一个函数存在反函数,当且仅当其定义域和值域满足双射(bijection),即对于定义域中的任意元素,在值域中都有唯一的元素与之对应;同时对于值域中的任意元素,在定义域中都有唯一的元素与之对应。
另外,32-bit整数和64-bit整数是有限集合,后者的基数比前者的大,因而后者总存在一些元素,它在32-bit中没有元素与之对应,因而32-bit整数到64-bit整数的函数不存在反函数。

Exercise 3: Cartesian product

Specify the unit cube (x-, y-, and z-coordinates all between 0 and 1 inclusive) in terms of the Cartesian product of three intervals.

Solution:
( x , y , z ) ∈ [ 0 , 1 ] 3 (x,y,z) \in [0,1]^3 (x,y,z)[0,1]3.

Exercise 4: Log function

If you have access to the natural log function ln ⁡ x \ln x lnx, specify how you could use it to implement a log ⁡ b x \log_bx logbx function where b b b is the base of the log. What should the function do for negative b b b values? Assume an IEEE floating point implementation.

Solution:
第一问用换底公式即可: log ⁡ b x = ln ⁡ x / ln ⁡ b \log_bx=\ln x/\ln b logbx=lnx/lnb。该公式的简单证明(" → \rightarrow "表示推导): b log ⁡ b x = x = e ln ⁡ x → ln ⁡ ( b log ⁡ b x ) = ln ⁡ ( e ln ⁡ x ) → ln ⁡ b log ⁡ b x = ln ⁡ x → log ⁡ b x = ln ⁡ x / ln ⁡ b b^{\log_bx}=x=e^{\ln x}\rightarrow \ln (b^{\log_bx})=\ln (e^{\ln x}) \rightarrow \ln b\log_bx=\ln x \rightarrow \log_bx=\ln x/\ln b blogbx=x=elnxln(blogbx)=ln(elnx)lnblogbx=lnxlogbx=lnx/lnb,其中 x > 0 , b ∈ ( 0 , 1 ) ∪ ( 1 , + ∞ ) x\gt0, b\in(0, 1)\cup(1,+\infty) x>0,b(0,1)(1,+)

第二问,从对数定义下手:我们知道 y = log ⁡ b x ⇔ b y = x > 0 y=\log_bx \Leftrightarrow b^y=x\gt0 y=logbxby=x>0,当 b < 0 b\lt0 b<0时,则有 ( − 1 ) y ⋅ ∣ b ∣ y = x > 0 (-1)^y\cdot\vert b\vert^y=x\gt0 (1)yby=x>0,即 ( − 1 ) y > 0 (-1)^y>0 (1)y>0时才有实数解。

因此,当 b < 0 b\lt0 b<0时,对于实数解,对数是未定义的。

不过,该函数在复数意义下,且当 b < 0 b\lt0 b<0时,非整数解是有意义的,此时函数返回一个复数(不过,复数是一个二元组,不是IEEE754标准讨论的东西)。

Exercise 5: Solutions to specific quadratic equation (with one unknown)

Solve the quadratic equation 2 x 2 + 6 x + 4 = 0 2x^2 + 6x + 4 = 0 2x2+6x+4=0.

Solution:
判别式 Δ : = 36 − 4 × 2 × 4 = 4 > 0 \Delta:=36-4\times2\times4=4\gt0 Δ:=364×2×4=4>0,因而存在两不同的实根: x 1 = − 1 , x 2 = − 2 x_1=-1, x_2=-2 x1=1,x2=2.

Exercise 6: Implementation of solving general quadratic equation (with one unknown)

Implement a function that takes in coefficients A A A, B B B, and C C C for the quadratic equation A x 2 + B x + C = 0 Ax^2 + Bx + C = 0 Ax2+Bx+C=0 and computes the two solutions. Have the function return the number of valid (not NaN) solutions and fill in the return arguments so the smaller of the two solutions is first.

Solution:
仅讨论实数解,函数签名为f(A,B,C),返回一个二元组,表示实数解。算法流程如下:

  1. 首先确定是否为一元二次方程,即判断 A A A是否为0,若为0则方程变为一次方程 B x + C = 0 Bx+C=0 Bx+C=0,此时看 B B B是否为0,若为0则视为没有有效解,返回NaN作为标记;否则有唯一解,返回二元组 < x , N a N > <x, \rm{NaN}> <x,NaN>

  2. 至此说明方程为一元二次方程,计算判别式 Δ : = B 2 − 4 A C \Delta:=B^2-4AC Δ:=B24AC,若 Δ ≥ 0 \Delta\ge0 Δ0则有实数解,否则返回NaN作为标记;

  3. 确定存在实数解后,根据求根公式 x = − B ± Δ 2 A x=\dfrac{-B\pm\sqrt{\Delta}}{2A} x=2AB±Δ 获得两个实数解 x 1 x_1 x1 x 2 x_2 x2

  4. 将两个解从小到大排序使得 x 1 ≤ x 2 x_1\le x_2 x1x2,并依次作为二元组的元素,最后将二元组 < x 1 , x 2 > <x_1, x_2> <x1,x2>作为返回值。

具体实现见下一个练习。

Exercise 7: Solutions to quadratic equation (considering numerical accuracy)

Show that the two forms of the quadratic formula on page 17 are equivalent (assuming exact arithmetic) and explain how to choose one for each root in order to avoid subtracting nearly equal floating point numbers, which leads to loss of precision.

Solution:

两种解的形式分别为: x = − B ± B 2 − 4 A C 2 A x=\dfrac{-B\pm\sqrt{B^2-4AC}}{2A} x=2AB±B24AC 以及 x = 2 C − B ∓ B 2 − 4 A C x=\dfrac{2C}{-B\mp\sqrt{B^2-4AC}} x=BB24AC 2C,当 B 2 ≫ 4 A C B^2\gg4AC B24AC时, ∣ B ∣ ≃ B 2 − 4 A C \vert B\vert \simeq \sqrt{B^2-4AC} BB24AC ,因而讨论 B B B的正负:

  1. B ≥ 0 B\ge0 B0时, x = − B + B 2 − 4 A C 2 A x=\dfrac{-B+\sqrt{B^2-4AC}}{2A} x=2AB+B24AC x = 2 C − B + B 2 − 4 A C x=\dfrac{2C}{-B+\sqrt{B^2-4AC}} x=B+B24AC 2C出现“相近减相近”的情况,此时选择剩余的两根作为解;

  2. B < 0 B\lt0 B<0时, x = − B − B 2 − 4 A C 2 A x=\dfrac{-B-\sqrt{B^2-4AC}}{2A} x=2ABB24AC x = 2 C − B − B 2 − 4 A C x=\dfrac{2C}{-B-\sqrt{B^2-4AC}} x=BB24AC 2C出现“相近减相近”的情况,此时选择剩余的两根作为解。

这个判断可以扩展至任意 B 2 ≥ 4 A C B^2\ge4AC B24AC的情况,因而确保任何时候都不会出现近似数相减的情况。

这样,综合练习6和7,我们可以实现一个比较健壮的一元二次方程求解程序,其C++11实现如下,可以设置满足 B 2 ≫ 4 A C B^2\gg4AC B24AC的例子,比较未考虑数值精度的程序和该程序的求解结果。

#include<iostream>
#include<cmath>
using namespace std;

const float EPS = 1e-9;


// 三态示性函数 
inline int dcmp(float x){
    
    
	return fabs(x) < EPS? 0: x > 0? 1: -1;
}

inline pair<float, float> f(float A, float B, float C){
    
    
	if(dcmp(A) == 0){
    
     // A为零 
		if(dcmp(B) == 0){
    
     // B为零 
			return make_pair(nanf("1"), nanf("1")); // 标记为1 
		}
		return make_pair(-C/B, nanf("1"));
	}
	float delta = B * B - 4 * A * C;
	if(dcmp(delta) < 0){
    
     // 无实数解 
		return make_pair(nanf("2"), nanf("2")); // 标记为2 
	}
	pair<float, float> res;
	delta = sqrt(delta);
	// 根据B的正负选根 
	if(dcmp(B) >= 0){
    
     // - -
		res.first = (-B - delta) / (2 * A);
		if(C == 0){
    
     // 避免出现-0 
			res.second = 0; 	
		}
		else{
    
     
			res.second = (2 * C) / (-B - delta); 	
		} 		
	}else{
    
     // + +
		res.first = (-B + delta) / (2 * A);
		if(C == 0){
    
    
			res.second = 0;
		}
		else{
    
    
			res.second = (2 * C) / (-B + delta);	
		}		
	}
	// 从小到大排序 
	if(res.first > res.second){
    
    
		swap(res.first, res.second);
	}
	return res;
}


int main(){
    
    
	float a, b, c;
	cout << "Input coefficients A, B and C for equation: Ax^2 + Bx + C = 0(separated by a space): " << endl;	 
	while (cin >> a >> b >> c && (a || b || c)){
    
     // 不全为0时才进行计算 
		auto ans = f(a, b, c);
		if(isnan(ans.first)){
    
    
			cout << "No real solution." << endl;
		}else if(isnan(ans.second)){
    
     // 只有一个解(一元一次方程) 
			cout << "One solution: x = " << ans.first << endl;
		}else{
    
    
			cout << "x1 = " << ans.first << "; x2 = " << ans.second << endl;
		}
	}
	cout << "Bye~" << endl; 
	return 0;
}

Exercise 8: Counterexample to the associativity on cross product

Show by counterexample that it is not always true that for 3D vectors a \bold{a} a, b \bold{b} b, and c \bold{c} c, a × ( b × c ) = ( a × b ) × c \bold{a} \times (\bold{b} \times \bold{c}) = (\bold{a} \times \bold{b}) \times \bold{c} a×(b×c)=(a×b)×c.

Solution:
反例:假设三个向量均不是零向量,且有如下关系: a \bold{a} a b \bold{b} b平行,但与 c \bold{c} c不平行。因而等式右边有 ( a × b ) × c = 0 × c = 0 (\bold{a} \times \bold{b}) \times \bold{c}=\bold{0}\times \bold{c}=\bold{0} (a×b)×c=0×c=0,而等式左边不可能是零向量,因而等式两端不相等。

Exercise 9: Constructing a basis from two vectors

Given the nonparallel 3D vectors a \bold{a} a and b \bold{b} b, compute a right-handed orthonormal basis such that u \bold{u} u is parallel to a \bold{a} a and v \bold{v} v is in the the plane defined by a \bold{a} a and b \bold{b} b.

Solution:

{ u = a ∥ a ∥ w = u × b ∥ u × b ∥ v = w × u \left\{ \begin{aligned} \bold{u} &= \dfrac{\bold{a}}{\Vert\bold{a}\Vert}\\ \bold{w} &= \dfrac{\bold{u}\times\bold{b}}{\Vert\bold{u}\times\bold{b}\Vert}\\ \bold{v} &= \bold{w}\times\bold{u} \end{aligned} \right. uwv=aa=u×bu×b=w×u
如图所示:
在这里插入图片描述

Exercise 10: Gradient

What is the gradient of f ( x , y , z ) = x 2 + y − 3 z 3 f(x,y,z) = x^2 + y − 3z^3 f(x,y,z)=x2+y3z3?

Solution:
∇ f ( x , y , z ) = ( ∂ f ∂ x , ∂ f ∂ y , ∂ f ∂ z ) = ( 2 x , 1 , − 9 z 2 ) \nabla f(x,y,z)=(\dfrac{\partial f}{\partial x}, \dfrac{\partial f}{\partial y}, \dfrac{\partial f}{\partial z}) = (2x, 1, -9z^2) f(x,y,z)=(xf,yf,zf)=(2x,1,9z2).

Exercise 11: 2D Parametric curves

What is a parametric form for the axis-aligned 2D ellipse?

Solution:
轴对齐2D椭圆方程(不妨设长半轴与x轴平行)形式为 ( x − x c ) 2 a 2 + ( y − y c ) 2 b 2 = 1 \dfrac{(x-x_c)^2}{a^2}+\dfrac{(y-y_c)^2}{b^2}=1 a2(xxc)2+b2(yyc)2=1 ( a > b > 0 a>b>0 a>b>0),结合毕达哥拉斯定理(即勾股定理) sin ⁡ 2 t + cos ⁡ 2 t = 1 \sin^2 t + \cos^2 t =1 sin2t+cos2t=1,其中参数 t ∈ [ 0 , 2 π ) t \in [0, 2\pi) t[0,2π),以及 t = 0 t=0 t=0 x = a x=a x=a, 可得2D椭圆的参数方程:
{ x = x c + a cos ⁡ t , y = y c + b sin ⁡ t , t ∈ [ 0 , 2 π ) \left\{ \begin{aligned} x &= x_c + a\cos t,\\ y &= y_c + b\sin t, \quad t \in [0, 2\pi) \end{aligned} \right. { xy=xc+acost,=yc+bsint,t[0,2π)

或表示成向量(矩阵)形式:

[ x y ] = [ x c + a cos ⁡ t y c + b sin ⁡ t ] , t ∈ [ 0 , 2 π ) . \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} x_c + a\cos t \\ y_c + b\sin t \end{bmatrix}, \quad t \in [0, 2\pi). [xy]=[xc+acostyc+bsint],t[0,2π).

Exercise 12: About the 3D plane

What is the implicit equation of the plane through 3D points (1,0,0), (0,1,0), and (0,0,1)? What is the parametric equation? What is the normal vector to this plane?

Solution:
三个不共线的点确定一个平面,设三个点分别为点 A ( 1 , 0 , 0 ) A(1,0,0) A(1,0,0) B ( 0 , 1 , 0 ) B(0,1,0) B(0,1,0) C ( 0 , 0 , 1 ) C(0,0,1) C(0,0,1),则有向量 A B → = ( − 1 , 1 , 0 ) \overrightarrow{AB}=(-1,1,0) AB =(1,1,0)以及 A C → = ( − 1 , 0 , 1 ) \overrightarrow{AC}=(-1,0,1) AC =(1,0,1)
故平面的法向量 n = A B → × A C → = ( 1 , 1 , 1 ) \bold{n}=\overrightarrow{AB}\times\overrightarrow{AC}=(1,1,1) n=AB ×AC =(1,1,1)
设该平面上的任意一点为 P ( x , y , z ) P(x,y,z) P(x,y,z),则该平面的隐式方程为 A P → ⋅ n = 0 \overrightarrow{AP}\cdot\bold{n}=0 AP n=0,即 x + y + z − 1 = 0 x+y+z-1=0 x+y+z1=0
这样一来, A B → \overrightarrow{AB} AB A C → \overrightarrow{AC} AC A P → \overrightarrow{AP} AP 三个向量共面,从而有 A P → = u A B → + v A C → \overrightarrow{AP}=u\overrightarrow{AB}+v\overrightarrow{AC} AP =uAB +vAC ,即 ( x − 1 , y , z ) = ( − u − v , u , v ) , (x-1,y,z)=(-u-v,u,v), (x1,y,z)=(uv,u,v)故相应的参数方程为:
{ x = − u − v + 1 , y = u , z = v . \left\{ \begin{aligned} x &= -u-v+1,\\ y &= u, \\ z &= v. \end{aligned} \right. xyz=uv+1,=u,=v.

Exercise 13: Program design - line segment intersection

Given four 2D points a 0 \bold{a}_0 a0, a 1 \bold{a}_1 a1, b 0 \bold{b}_0 b0, and b 1 \bold{b}_1 b1, design a robust procedure to determine whether the line segments a 0 a 1 \bold{a}_0\bold{a}_1 a0a1 and b 0 b 1 \bold{b}_0\bold{b}_1 b0b1 intersect.

Solution:
解决此问题之前,实现一个2D向量结构体(Vector2D,同时取别名为Point2D),就像虎书说的,点和向量尽管物理意义上存在限制,但是否有意义取决于具体情形和人的理解,因而从具体实现上不加以区分,只是通过人为写代码时用不同的名称来加以区分,向量的常用操作包括:① 求向量模长;② 向量加;③ 向量减;④ 向量数乘;⑤ 向量数除;⑥ 向量点乘;⑦ 向量叉乘。

网上有很多两线段相交判定的算法,这里我推荐一个博主写的简洁有效的方法 [ 2 ] ^{[2]} [2],他采用两步方法依次过滤掉不相交的情况,具体流程我就不赘述了。

这里我们自己推导相交的条件,首先考虑两线段(不)相交的情况,典型例子如下图所示:
在这里插入图片描述
上图中,1、5为一般情况,其余都为特殊情况。

先观察1、5两种情况,可以发现两线段相交的充分不必要条件为:每条线段的两个端点都在另一条线段的两侧,这意味着叉乘的符号互异。

而特殊情况2,3,4均存在叉积为0的情况,这时候若相交成立,则要么只交于线段的一个端点(情况2),要么有一块区域重叠(情况3)。情况4与2,3的不同之处在于,不存在一条线段的端点位于另一条线段上,因此针对特殊情况,我们还需要判断点是否在线段上的函数。

由此,我们可以编写如下程序(C++11),用到了二维几何的板子 [ 3 ] ^{[3]} [3],其中涵盖了二维几何的大多数操作,不过这里我们只需要用到一部分:

#include<iostream>
#include<cmath>
using namespace std;

using flt = double;


struct Point2{
    
    
	flt x, y;
	Point2(flt _x=0, flt _y=0): x(_x), y(_y){
    
    }
};

using Vector2 = Point2;

const flt EPS = 1e-10;

inline int dcmp(flt x){
    
    
	return fabs(x) < EPS? 0: x > 0? 1: -1;
}

inline flt dot(const Vector2 &a, const Vector2 &b){
    
     // 向量点乘 
	return a.x * b.x + a.y * b.y;
}

inline flt len(const Vector2 &a){
    
     // 向量模长 
	return sqrt(dot(a, a));
}

inline flt angle(const Vector2 &a, const Vector2 &b){
    
     // 向量夹角 
	return acos(dot(a, b) / len(a) / len(b));
}

inline flt cross(const Vector2 &a, const Vector2 &b){
    
     // 向量叉乘 
	return a.x * b.y - a.y * b.x;
}

inline Vector2 operator+(const Vector2 &a, const Vector2 &b){
    
    
	return Vector2(a.x + b.x, a.y + b.y);
}

inline Vector2 operator-(const Vector2 &a, const Vector2 &b){
    
    
	return Vector2(a.x - b.x, a.y - b.y);
}

inline Vector2 operator*(const Vector2 &a, flt k){
    
    
	return Vector2(k * a.x, k * a.y);
}

inline Vector2 operator/(const Vector2 &a, flt k){
    
    
	return Vector2(a.x / k, a.y / k);
}

bool operator < (const Point2 &A, const Point2 &B){
    
     // 点在坐标系中的方位 
	return A.x < B.x || (A.x == B.x && A.y < B.y);
}

bool operator==(const Point2 &a, const Point2 &b){
    
     // 点是否重合 
	return dcmp(a.x - b.x) == 0 && dcmp(a.y - b.y) == 0;
}

inline flt area2(const Point2 &A, const Point2 &B, const Point2 &C){
    
     // 两倍三角形面积 
	return cross(B - A, C - A);
}

inline Vector2 rotate(const Vector2 &a, flt rad){
    
     // 将向量a逆时针旋转rad弧度 
	return Vector2(a.x*cos(rad)-a.y*sin(rad), a.x*sin(rad)-a.y*cos(rad));
}

inline Vector2 normal(const Vector2 &a){
    
     // 求单位法线,先得确保不是零向量 
	flt L = len(a);
	return Vector2(-a.y/L, a.x/L);
}

// C是否在线段AB上(包含A和B):先看是否共线,再看点乘符号是否为负 
bool isOnSegment(const Point2 &A, const Point2 &B, const Point2 &C){
    
    
	return dcmp(cross(B-A, C-A)) == 0 && dcmp(dot(C-A, C-B)) <= 0;
}

// 线段是否相交 
bool isSegmentIntersect(const Point2 &a0, const Point2 &a1, 
const Point2 &b0, const Point2 &b1){
    
    
	flt c1 = cross(b0-a0, a1-a0), c2 = cross(b1-a0, a1-a0);
	flt c3 = cross(a0-b0, b1-b0), c4 = cross(a1-b0, b1-b0);
	if(dcmp(c1) * dcmp(c2) < 0 && dcmp(c3) * dcmp(c4) < 0){
    
     // 情况1:交点非端点 
		return true;
	} 
	// 剩余情况2-5:看看有没有端点在另一条线段上 
	if(isOnSegment(a0, a1, b0) || isOnSegment(a0, a1, b1) 
	|| isOnSegment(b0, b1, a0) || isOnSegment(b0, b1, a1)){
    
    
		return true;
	}
	return false;
}


int main(){
    
    
	Point2 a0, a1, b0, b1;
	int in[8];
	while (true){
    
    
		int cnt = 0;
		for (int i=0; i<8; ++i){
    
    
			cin >> in[i];
			if(in[i]) cnt++;
		}
		if(cnt == 0){
    
     // 全为0时退出 
			break;
		}
		Point2 a0(in[0], in[1]), a1(in[2], in[3]), b0(in[4], in[5]), b1(in[6], in[7]);
		bool res = isSegmentIntersect(a0, a1, b0, b1);
		if(res){
    
    
			cout << "Line segments intersect." << endl;	
		}		
		else{
    
    
			cout << "No intersection." << endl;	
		}
	}
	
	return 0;
}

对于以上程序,我们可以给一些涵盖情况1-5的数据测一测,输入输出如下:

0 0 4 4 1 3 3 0
Line segments intersect.
0 0 2 0 2 0 3 0
Line segments intersect.
0 0 2 0 3 0 4 0
No intersection.
0 0 2 2 1 1 3 3
Line segments intersect.
0 0 0 0 0 0 2 2
Line segments intersect.
1 1 4 4 3 3 6 0
Line segments intersect.
0 0 0 0 0 0 0 0

Exercise 14: Program design - 2D barycentric coordinates

Design a robust procedure to compute the barycentric coordinates of a 2D point with respect to three 2D non-collinear points.

Solution:
设三个不共线的点依次为 A ( x 1 , y 1 ) , B ( x 2 , y 2 ) , C ( x 3 , y 3 ) A(x_1,y_1),B(x_2,y_2),C(x_3,y_3) A(x1,y1),B(x2,y2),C(x3,y3),则可选 A B → , A C → \overrightarrow{AB}, \overrightarrow{AC} AB ,AC 作为2D重心坐标系的两个基向量,再设点 P ( x , y ) P(x,y) P(x,y) A B C ABC ABC确定的平面中的一点,满足:
A P → = β A B → + γ A C → \overrightarrow{AP}=\beta\overrightarrow{AB}+\gamma\overrightarrow{AC} AP =βAB +γAC
将上式从向量形式变为点可得:
P = ( 1 − β − γ ) A + β B + γ C P=(1-\beta-\gamma)A+\beta B + \gamma C P=(1βγ)A+βB+γC
令上式中 α : = 1 − β − γ \alpha:=1-\beta-\gamma α:=1βγ,于是 ( α , β , γ ) (\alpha,\beta,\gamma) (α,β,γ)即点 P P P的重心坐标,其自由度为2,通过上式我们可以轻易求解出 β \beta β γ \gamma γ,其计算表达式为:
β = ( y 3 − y 1 ) x − ( x 3 − x 1 ) y ( x 1 − x 2 ) ( y 1 − y 3 ) − ( y 1 − y 2 ) ( x 1 − x 3 ) \beta = \dfrac{(y_3-y_1)x-(x_3-x_1)y}{(x_1-x_2)(y_1-y_3)-(y_1-y_2)(x_1-x_3)} β=(x1x2)(y1y3)(y1y2)(x1x3)(y3y1)x(x3x1)y
γ = ( x 2 − x 1 ) y − ( y 2 − y 1 ) x ( x 1 − x 2 ) ( y 1 − y 3 ) − ( y 1 − y 2 ) ( x 1 − x 3 ) \gamma = \dfrac{(x_2-x_1)y-(y_2-y_1)x}{(x_1-x_2)(y_1-y_3)-(y_1-y_2)(x_1-x_3)} γ=(x1x2)(y1y3)(y1y2)(x1x3)(x2x1)y(y2y1)x

不过有些情况下分母有可能接近0,这意味着三个点构成的三角形面积近似0,对于这种情况,我们可以通过叉积检查出来,然后返回NaN作为坐标值。

至于要求的鲁棒性是否完全达到,或者是否有更好的方法,就留给读者们和未来的自己分析了。程序的实现如下(C++11):

#include<iostream>
#include<cmath>
#include<tuple>
#include<typeinfo>
using namespace std;

// ... 省略了二维几何的一些定义和操作(具体见上一个代码)

tuple<flt, flt, flt> calc_barycentric_coordinates(const Point2 &A, const Point2 &B, const Point2 &C, const Point2 &P){
    
    
	tuple<flt, flt, flt> res;
	if(dcmp(cross(B-A, C-A)) == 0){
    
     // 三角形面积接近0 
		if(typeid(flt) == typeid(float)){
    
    
			get<0>(res) = nanf("1");	
		}
		else{
    
    
			get<0>(res) = nan("1");	
		}
	}else{
    
    
		flt deno = (A.x-B.x)*(A.y-C.y) - (A.y-B.y)*(A.x-C.x);
		get<1>(res) = ((C.y-A.y)*P.x-(C.x-A.x)*P.y) / deno;
		get<2>(res) = ((B.x-A.x)*P.y-(B.y-A.y)*P.x) / deno;
		get<0>(res) = 1.0 - get<1>(res) - get<2>(res);
	}
	return res;
}

int main(){
    
    
	Point2 A, B, C, P;
	cout << "Input 2D Points A, B, C in order: " << endl;
	cin >> A.x >> A.y >> B.x >> B.y >> C.x >> C.y;
	cout << "Now you can input several 2D points P to get its barycentric coordinates: (0, 0) to quit" << endl;
	while (cin >> P.x >> P.y && (P.x || P.y)){
    
    
		auto res = calc_barycentric_coordinates(A, B, C, P);
		if(isnan(get<0>(res))){
    
     // 三角形ABC面积接近0,提示重新输入ABC 
			cout << "The area of triangle ABC is near zero. Try to reinput points A, B, C: " << endl;
			cin >> A.x >> A.y >> B.x >> B.y >> C.x >> C.y;
			cout << "Now you can input several 2D points P to get its barycentric coordinates: (0, 0) to quit" << endl;
			continue;
		}
		cout << "The barycentric coordinates of P(" << P.x << ", " 
		<< P.y << ") is (" << get<0>(res) << ", " << get<1>(res) << ", " << get<2>(res) << ")" << endl;
	}
	
	return 0;
}

其中的一组输入输出结果如下:

Input 2D Points A, B, C in order:
0 0 1 0 2 0
Now you can input several 2D points P to get its barycentric coordinates: (0, 0) to quit
1 5
The area of triangle ABC is near zero. Try to reinput points A, B, C:
0 0 1 0 1 1
Now you can input several 2D points P to get its barycentric coordinates: (0, 0) to quit
1 1
The barycentric coordinates of P(1, 1) is (0, 0, 1)
1 0
The barycentric coordinates of P(1, 0) is (0, 1, 0)
2 1
The barycentric coordinates of P(2, 1) is (-1, 1, 1)
0.25 0
The barycentric coordinates of P(0.25, 0) is (0.75, 0.25, 0)
0 0

References

[1] IEEE. 754-1985 - IEEE Standard for Binary Floating-Point Arithmetic[J]. IEEE, 1985.

[2] 肘子zhouzi. 判断两线段相交[EB/OL]. https://blog.csdn.net/zhouzi2018/article/details/81871875, 2018-08-20.

[3] 刘汝佳, 陈锋. 算法竞赛入门经典训练指南[M]. 清华大学出版社, 2012.

笔者水平有限,如描述有误,或有更好的方法,欢迎批评指正及交流~

猜你喜欢

转载自blog.csdn.net/weixin_42430021/article/details/126902083