题意:给出一个5个顶点的多面体以及多面体内一点P。求让 多面体不同的方式(即以不同的面)放在地面上,设这个着地的面为A,多面体重心在A上的投影为B,在保证B在A内部且距离A的各个边界不小于0.2的前提 下(否则这种放置方式就是不合法的),求P距离地面的最大最小距离为多少。
思路:
(1)判断两个点是不是在面的同一侧;否则这个面就不能作为着地的面;
(2)计算重心;
(3)计算点在面的投影;
(4)计算点是否在面内;
(5)计算点到线的距离;
(6)计算点到面的距离。
特殊情况:四点共面当底面.
1 #include<cstdio> 2 #include<cmath> 3 using namespace std; 4 5 const double eps = 1e-8; 6 int dcmp(double x) 7 { 8 if(fabs(x) < eps) return 0; 9 else return x < 0 ? -1 : 1; 10 } 11 12 struct Point3 13 { 14 double x, y, z; 15 Point3(double x=0, double y=0, double z=0):x(x),y(y),z(z) { } 16 }; 17 18 typedef Point3 Vector3; 19 20 Vector3 operator + (const Vector3& A, const Vector3& B) 21 { 22 return Vector3(A.x+B.x, A.y+B.y, A.z+B.z); 23 } 24 25 Vector3 operator - (const Point3& A, const Point3& B) 26 { 27 return Vector3(A.x-B.x, A.y-B.y, A.z-B.z); 28 } 29 30 Vector3 operator * (const Vector3& A, double p) 31 { 32 return Vector3(A.x*p, A.y*p, A.z*p); 33 } 34 35 Vector3 operator / (const Vector3& A, double p) 36 { 37 return Vector3(A.x/p, A.y/p, A.z/p); 38 } 39 40 double Dot(const Vector3& A, const Vector3& B) 41 { 42 return A.x*B.x + A.y*B.y + A.z*B.z; 43 } 44 double Length(const Vector3& A) 45 { 46 return sqrt(Dot(A, A)); 47 } 48 double Angle(const Vector3& A, const Vector3& B) 49 { 50 return acos(Dot(A, B) / Length(A) / Length(B)); 51 } 52 Vector3 Cross(const Vector3& A, const Vector3& B) 53 { 54 return Vector3(A.y*B.z - A.z*B.y, A.z*B.x - A.x*B.z, A.x*B.y - A.y*B.x); 55 } 56 double Area2(const Point3& A, const Point3& B, const Point3& C) 57 { 58 return Length(Cross(B-A, C-A)); 59 } 60 double Volume6(const Point3& A, const Point3& B, const Point3& C, const Point3& D) 61 { 62 return Dot(D-A, Cross(B-A, C-A)); 63 } 64 65 bool read_point3(Point3& p) 66 { 67 if(scanf("%lf%lf%lf", &p.x, &p.y, &p.z) != 3) return false; 68 return true; 69 } 70 71 // 点p到平面p0-n的距离。n必须为单位向量 72 double DistanceToPlane(const Point3& p, const Point3& p0, const Vector3& n) 73 { 74 return fabs(Dot(p-p0, n)); // 如果不取绝对值,得到的是有向距离 75 } 76 77 // 点p在平面p0-n上的投影。n必须为单位向量 78 Point3 GetPlaneProjection(const Point3& p, const Point3& p0, const Vector3& n) 79 { 80 return p-n*Dot(p-p0, n); 81 } 82 83 // 点P到直线AB的距离 84 double DistanceToLine(const Point3& P, const Point3& A, const Point3& B) 85 { 86 Vector3 v1 = B - A, v2 = P - A; 87 return Length(Cross(v1, v2)) / Length(v1); 88 } 89 90 // p1和p2是否在线段a-b的同侧 91 bool SameSide(const Point3& p1, const Point3& p2, const Point3& a, const Point3& b) 92 { 93 return dcmp(Dot(Cross(b-a, p1-a), Cross(b-a, p2-a))) >= 0; 94 } 95 96 // 点在三角形P0, P1, P2中 97 bool PointInTri(const Point3& P, const Point3& P0, const Point3& P1, const Point3& P2) 98 { 99 return SameSide(P, P0, P1, P2) && SameSide(P, P1, P0, P2) && SameSide(P, P2, P0, P1); 100 } 101 102 // 四面体的重心 103 Point3 Centroid(const Point3& A, const Point3& B, const Point3& C, const Point3& D) 104 { 105 return (A + B + C + D)/4.0; 106 } 107 108 #include<algorithm> 109 //using namespace std; 110 111 // 判断P是否在三角形A, B, C中,并且到三条边的距离都至少为mindist。保证P, A, B, C共面 112 bool InsideWithMinDistance(const Point3& P, const Point3& A, const Point3& B, const Point3& C, double mindist) 113 { 114 if(!PointInTri(P, A, B, C)) return false; 115 if(DistanceToLine(P, A, B) < mindist) return false; 116 if(DistanceToLine(P, B, C) < mindist) return false; 117 if(DistanceToLine(P, C, A) < mindist) return false; 118 return true; 119 } 120 121 // 判断P是否在凸四边形ABCD(顺时针或逆时针)中,并且到四条边的距离都至少为mindist。保证P, A, B, C, D共面 122 bool InsideWithMinDistance(const Point3& P, const Point3& A, const Point3& B, const Point3& C, const Point3& D, double mindist) 123 { 124 if(!PointInTri(P, A, B, C)) return false; 125 if(!PointInTri(P, C, D, A)) return false; 126 if(DistanceToLine(P, A, B) < mindist) return false; 127 if(DistanceToLine(P, B, C) < mindist) return false; 128 if(DistanceToLine(P, C, D) < mindist) return false; 129 if(DistanceToLine(P, D, A) < mindist) return false; 130 return true; 131 } 132 133 int main() 134 { 135 for(int kase = 1; ; kase++) 136 { 137 Point3 P[5], F; 138 for(int i = 0; i < 5; i++) 139 if(!read_point3(P[i])) return 0; 140 read_point3(F); 141 142 // 求重心坐标 143 Point3 c1 = Centroid(P[0], P[1], P[2], P[3]); 144 Point3 c2 = Centroid(P[0], P[1], P[2], P[4]); 145 double vol1 = fabs(Volume6(P[0], P[1], P[2], P[3])) / 6.0; 146 double vol2 = fabs(Volume6(P[0], P[1], P[2], P[4])) / 6.0; 147 Point3 centroid = (c1 * vol1 + c2 * vol2) / (vol1 + vol2); 148 149 // 枚举放置方案 150 double mindist = 1e9, maxdist = -1e9; 151 for(int i = 0; i < 5; i++) 152 for(int j = i+1; j < 5; j++) 153 for(int k = j+1; k < 5; k++) 154 { 155 // 找出另外两个点的下标a和b 156 int vis[5] = {0}; 157 vis[i] = vis[j] = vis[k] = 1; 158 int a, b; 159 for(a = 0; a < 5; a++) if(!vis[a]) 160 { 161 b = 10-i-j-k-a; 162 break; 163 } 164 165 // 判断a和b是否在平面i-j-k的异侧(体积法判断) 166 int d1 = dcmp(Volume6(P[i], P[j], P[k], P[a])); 167 int d2 = dcmp(Volume6(P[i], P[j], P[k], P[b])); 168 if(d1 * d2 < 0) continue; // 是,则放置方案不合法 169 170 Vector3 n = Cross(P[j]-P[i], P[k]-P[i]); // 法向量 171 n = n / Length(n); // 单位化 172 173 Point3 proj = GetPlaneProjection(centroid, P[i], n); // 重心在平面i-j-k上的投影 174 bool ok = InsideWithMinDistance(proj, P[i], P[j], P[k], 0.2); 175 if(!ok) 176 { 177 if(d1 == 0) // i-j-k-a四点共面。i和j一定为ABC三个顶点之一,k和a是D或者E 178 { 179 if(!InsideWithMinDistance(proj, P[i], P[k], P[j], P[a], 0.2)) continue; 180 } 181 else if(d2 == 0) // i-j-k-b四点共面。i和j一定为ABC三个顶点之一,k和b是D或者E 182 { 183 if(!InsideWithMinDistance(proj, P[i], P[k], P[j], P[b], 0.2)) continue; 184 } 185 else 186 continue; 187 } 188 189 // 更新答案 190 double dist = DistanceToPlane(F, P[i], n); 191 mindist = min(mindist, dist); 192 maxdist = max(maxdist, dist); 193 } 194 printf("Case %d: %.5lf %.5lf ", kase, mindist, maxdist); 195 } 196 return 0; 197 }