有两个问题需要解决。
问题1:给定一组3D点,描述它们所在的平面。
问题2:给定一组3D点,将它们投影到该平面上。
其他答案已经解决了问题2; 我采用基变换的方法来解决。
对于问题1,您需要找到平面的法向量。为此,请从点云中选择一些随机三元组,并找到每个三元组形成的三角形的法向量。取这些法向量的平均值以估计平面的法向量。使用它来形成新正交基的第一个元素。
下面是一些用于生成测试集的代码,Point3D不是生产质量的代码; 无论如何,您可能可以重新使用其他内容。
#include <iostream>
#include <random>
#include <vector>
#include <algorithm>
#include <numeric>
#include <math.h>
struct Point3D
{
Point3D(double a = 0, double b = 0, double c = 0)
{
x_[0] = a;
x_[1] = b;
x_[2] = c;
}
int maxIndex() const
{
int maxIndex = 0;
double maxVal = 0;
for (int i = 0; i != 3; ++i)
{
if (fabs(x_[i]) > maxVal)
{
maxIndex = i;
maxVal = fabs(x_[i]);
}
}
return maxIndex;
}
double lengthSquared() const
{
return std::accumulate(std::begin(x_), std::end(x_), 0.0, [](double sum, double x) { return sum + x*x; });
}
double length() const { return sqrt(lengthSquared()); }
double dot(const Point3D& other) const
{
double d = 0;
for (int i = 0; i != 3; ++i)
d += x_[i] * other.x_[i];
return d;
}
const Point3D& add(const Point3D& other)
{
for (int i = 0; i != 3; ++i)
x_[i] += other.x_[i];
return *this;
}
const Point3D& subtract(const Point3D& other)
{
for (int i = 0; i != 3; ++i)
x_[i] -= other.x_[i];
return *this;
}
const Point3D& multiply(double scalar)
{
for (int i = 0; i != 3; ++i)
x_[i] *= scalar;
return *this;
}
const Point3D& divide(double scalar)
{
return multiply(1 / scalar);
}
const Point3D& unitise()
{
return divide(length());
}
Point3D projectionOfOther(const Point3D& other) const
{
double factor = dot(other) / lengthSquared();
Point3D projection(*this);
projection.multiply(factor);
return projection;
}
double x_[3];
};
void print(const Point3D& p)
{
std::cout << p.x_[0] << ", " << p.x_[1] << ", " << p.x_[2] << ", " << std::endl;
}
typedef Point3D Point3D;
Point3D difference(const Point3D& a, const Point3D& b)
{
return Point3D(a).subtract(b);
}
Point3D sum(const Point3D& a, const Point3D& b)
{
return Point3D(a).add(b);
}
Point3D crossProduct(const Point3D& v1 ,const Point3D& v2)
{
return Point3D(v1.x_[1] * v2.x_[2] - v1.x_[2] * v2.x_[1],
v1.x_[2] * v2.x_[0] - v1.x_[0] * v2.x_[2],
v1.x_[0] * v2.x_[1] - v1.x_[1] * v2.x_[0]
);
}
Point3D crossProductFromThreePoints(std::vector<Point3D>::iterator first)
{
std::vector<Point3D>::iterator second = first + 1;
std::vector<Point3D>::iterator third = first + 2;
return crossProduct(difference(*third, *first), difference(*third, *second));
}
std::vector<Point3D> makeCloud(int numPoints);
Point3D determinePlaneFromCloud(const std::vector<Point3D>& cloud)
{
std::vector<Point3D> randomised(cloud);
int extra = 3- randomised.size() % 3;
random_shuffle(begin(randomised), end(randomised));
randomised.insert(end(randomised), begin(randomised), begin(randomised) + extra);
int numTriangles = randomised.size() / 3;
Point3D normals;
int primaryDir = -1;
for (int tri = 0; tri != numTriangles; ++tri )
{
Point3D cp = crossProductFromThreePoints(begin(randomised) + tri * 3);
cp.unitise();
if (primaryDir < 0)
primaryDir = cp.maxIndex();
if (cp.x_[primaryDir] < 0)
cp.multiply(-1);
normals.add(cp);
}
double sum = std::accumulate(std::begin(cloud), std::end(cloud), 0.0, [normals](double sum, const Point3D& p) { return sum + p.dot(normals); });
double meanC = sum / cloud.size();
normals.divide(meanC);
return normals;
}
std::vector<Point3D> createOrthonormalBasis(const Point3D& v1)
{
std::vector<Point3D> basis(3);
basis[0] = v1;
int mi = v1.maxIndex();
basis[1].x_[(mi + 1) % 3 ] = 1;
basis[2].x_[(mi + 2) % 3] = 1;
for (int i = 0; i != 3; ++i)
{
for (int j = 0; j != i; ++j)
{
basis[i].subtract(basis[j].projectionOfOther(basis[i]));
}
basis[i].unitise();
}
return basis;
}
Point3D convertBasis(const Point3D& p, const std::vector<Point3D>& newBasis)
{
Point3D newCoords;
for (int i = 0; i != 3; ++i)
{
newCoords.x_[i] = p.dot(newBasis[i]);
}
return newCoords;
}
int main()
{
std::vector<Point3D> cloud = makeCloud(99);
Point3D normal = determinePlaneFromCloud(cloud);
print(normal);
std::vector<Point3D> basis = createOrthonormalBasis(normal);
for each (Point3D p in cloud)
{
Point3D mapped = convertBasis(p, basis);
print(mapped);
}
return 0;
}
std::vector<Point3D> makeCloud(int numPoints)
{
std::default_random_engine generator;
std::uniform_real_distribution<double> distribution(0.0, 1.0);
std::vector<Point3D> cloud(numPoints);
Point3D planeNormal;
for (int i = 0; i != 2; ++i)
planeNormal.x_[i] = distribution(generator);
while (planeNormal.x_[2] < 1e-2)
planeNormal.x_[2] = distribution(generator);
for (int i = 0; i != numPoints; ++i)
{
for (int j = 0; j != 2; ++j)
cloud[i].x_[j] = distribution(generator);
cloud[i].x_[2] = (1 - cloud[i].x_[0] * planeNormal.x_[0] - cloud[i].x_[1] * planeNormal.x_[1]) / planeNormal.x_[2];
}
planeNormal.unitise();
return cloud;
}