【问题标题】:Build Circle from 3 Points in 3D space implementation in C or C++用 C 或 C++ 在 3D 空间实现中从 3 点构建圆
【发布时间】:2021-07-24 00:53:05
【问题描述】:

我们有 3(三个)xyz 点在 3D 空间中定义了一个圆,这个圆需要转换为折线(用于进一步渲染)。我正在寻找可以完成这项工作的现成 C 或 C++ 函数或免费库。

不明白为什么this 被关闭了。我什至不能在那里回答我自己的问题。为你们感到羞耻。但你不会阻止知识的传播!

【问题讨论】:

  • 我不明白你为什么不明白为什么你的问题被关闭了。你从两张海报中得到了 cmets,解释了原因。您可能在这里已经足够长的时间看到数十个“请给我代码”问题,因此您应该了解社区为什么要关闭任何此类问题。请不要假设密切投票者的行为是恶意的,或者是为了“阻止知识传播”
  • 不,我没有看到这样的问题。相反,我看到了大量的问题,这些问题要求代码,并有代码或代码链接的答案。所以我不明白为什么这个问题不同?如果我的问题形成得很糟糕,请帮我改写。

标签: c++ geometry


【解决方案1】:

有一个更简单的解决方案可以在真实 3D 中找到圆参数,只需查看 http://en.wikipedia.org/wiki/Circumscribed_circle 中的“重心坐标”部分即可。 您可以从中提取以下优化代码:

// triangle "edges"
const Vector3d t = p2-p1;
const Vector3d u = p3-p1;
const Vector3d v = p3-p2;

// triangle normal
const Vector3d w = t.crossProduct(u);
const double wsl = w.getSqrLength();
if (wsl<10e-14) return false; // area of the triangle is too small (you may additionally check the points for colinearity if you are paranoid)

// helpers
const double iwsl2 = 1.0 / (2.0*wsl);
const double tt = t*t;
const double uu = u*u;

// result circle
Vector3d circCenter = p1 + (u*tt*(u*v) - t*uu*(t*v)) * iwsl2;
double   circRadius = sqrt(tt * uu * (v*v) * iwsl2*0.5);
Vector3d circAxis   = w / sqrt(wsl);

然后您也可以在真实 3D 中计算圆上的点,例如使用 OpenGL 中的 GL_LINE_STRIP 绘制它们。这应该比使用 2D sin/cos 方法快得多。

// find orthogonal vector to the circle axis
const Vector3d an = circAxis.getNormalized();
const Vector3d ao = Vector3d(4.0+an[0], 4.0+an[0]+an[1], 4.0+an[0]+an[1]+an[2]).crossProduct(an).getNormalized();

// 4x4 rotation matrix around the circle axis
const int steps = 360; // maybe adjust according to circle size on screen
Matrix4d R = makeRotMatrix4d(circCenter, circAxis, 2.0*M_PI/double(steps));

// one point on the circle
Vector3d cp = circCenter + ao*circRadius;

// rotate point on the circle
for (int i=0; i<steps; ++i)
{
   circlePoints.push_back(cp);
   cp = transformPoint(cp, R); // apply the matrix
}

如需创建转换矩阵(即 makeRotMatrix4d()),请参见 http://paulbourke.net/geometry/rotate/。

请注意,我没有测试上面的代码是否真的编译,但它应该给你足够的提示。

【讨论】:

  • 使用更直接的重心坐标实现我得到了正确的答案,但我无法让您的“优化”解决方案发挥作用。你能更详细地展示它们的推导吗?我假设 (u*v) 表示点积,但如何从等式得出?
  • @Mark wsl 和 iwsl2 是什么?
  • @Mark and 是 const double tt = t*t; 中表示 t.dot(t) 的符号 t*t 吗?
  • HA,这是一个很酷的解决方案。谢谢! :) 我已将原始代码(导致一些混淆 w.r.t. 那些t*t 和类似的计算)移植到 C# 并将其发布为 additional answer。提示:t*t 确实是点积。
【解决方案2】:

有一篇很好的文章和一个代码示例,介绍了如何在 2D、XY 平面上通过 3 个点构建一个圆。

http://paulbourke.net/geometry/circlesphere/

http://paulbourke.net/geometry/circlesphere/Circle.cpp

要构建一个 3D 圆,我们必须:

  • 将我们的 3 个点旋转到 XY 平面中
  • 计算圆心
  • 使用文章中的代码在XY平面上建一个圆
  • 将其旋转回原来的平面

对于旋转,最好使用四元数。

为了找到正确的四元数,我查看了 Ogre3d 源代码: void Quaternion::FromAngleAxis (const Radian& rfAngle, const Vector3& rkAxis)

还有一个更有用的功能: Quaternion getRotationTo(const Vector3& dest, const Vector3& fallbackAxis = Vector3::ZERO) const 但我没用过。

对于四元数和向量,我使用了我们自己的类。这是完成这项工作的函数的完整源代码:

bool IsPerpendicular(Point3d *pt1, Point3d *pt2, Point3d *pt3);
double CalcCircleCenter(Point3d *pt1, Point3d *pt2, Point3d *pt3, Point3d *center);

void FindCircleCenter(const Point3d *V1, const Point3d *V2, const Point3d *V3, Point3d *center)
{
    Point3d *pt1=new Point3d(*V1);
    Point3d *pt2=new Point3d(*V2);
    Point3d *pt3=new Point3d(*V3);


    if (!IsPerpendicular(pt1, pt2, pt3) )       CalcCircleCenter(pt1, pt2, pt3, center);
    else if (!IsPerpendicular(pt1, pt3, pt2) )  CalcCircleCenter(pt1, pt3, pt2, center);
    else if (!IsPerpendicular(pt2, pt1, pt3) )  CalcCircleCenter(pt2, pt1, pt3, center);
    else if (!IsPerpendicular(pt2, pt3, pt1) )  CalcCircleCenter(pt2, pt3, pt1, center);
    else if (!IsPerpendicular(pt3, pt2, pt1) )  CalcCircleCenter(pt3, pt2, pt1, center);
    else if (!IsPerpendicular(pt3, pt1, pt2) )  CalcCircleCenter(pt3, pt1, pt2, center);
    else {
        delete pt1;
        delete pt2;
        delete pt3;
        return;
    }
    delete pt1;
    delete pt2;
    delete pt3;

}

bool IsPerpendicular(Point3d *pt1, Point3d *pt2, Point3d *pt3)
// Check the given point are perpendicular to x or y axis
{
    double yDelta_a= pt2->y - pt1->y;
    double xDelta_a= pt2->x - pt1->x;
    double yDelta_b= pt3->y - pt2->y;
    double xDelta_b= pt3->x - pt2->x;

    // checking whether the line of the two pts are vertical
    if (fabs(xDelta_a) <= 0.000000001 && fabs(yDelta_b) <= 0.000000001){
        return false;
    }

    if (fabs(yDelta_a) <= 0.0000001){
        return true;
    }
    else if (fabs(yDelta_b) <= 0.0000001){
        return true;
    }
    else if (fabs(xDelta_a)<= 0.000000001){
        return true;
    }
    else if (fabs(xDelta_b)<= 0.000000001){
        return true;
    }
    else
        return false ;
}

double CalcCircleCenter(Point3d *pt1, Point3d *pt2, Point3d *pt3, Point3d *center)
{
    double yDelta_a = pt2->y - pt1->y;
    double xDelta_a = pt2->x - pt1->x;
    double yDelta_b = pt3->y - pt2->y;
    double xDelta_b = pt3->x - pt2->x;

    if (fabs(xDelta_a) <= 0.000000001 && fabs(yDelta_b) <= 0.000000001){
        center->x= 0.5*(pt2->x + pt3->x);
        center->y= 0.5*(pt1->y + pt2->y);
        center->z= pt1->z;

        return 1;
    }

    // IsPerpendicular() assure that xDelta(s) are not zero
    double aSlope=yDelta_a/xDelta_a; //
    double bSlope=yDelta_b/xDelta_b;
    if (fabs(aSlope-bSlope) <= 0.000000001){    // checking whether the given points are colinear.
        return -1;
    }

    // calc center
    center->x= (aSlope*bSlope*(pt1->y - pt3->y) + bSlope*(pt1->x + pt2 ->x)
                         - aSlope*(pt2->x+pt3->x) )/(2* (bSlope-aSlope) );
    center->y = -1*(center->x - (pt1->x+pt2->x)/2)/aSlope +  (pt1->y+pt2->y)/2;

    return 1;
}

//! Builds a circle in 3D space by 3 points on it and an optional center
void buildCircleBy3Pt(const float *pt1,
                      const float *pt2,
                      const float *pt3,
                      const float *c,       // center, can be NULL
                      std::vector<float> *circle)
{
    /*  Get the normal vector to the triangle formed by 3 points
        Calc a rotation quaternion from that normal to the 0,0,1 axis
        Rotate 3 points using quaternion. Points will be in XY plane 
        Build a circle by 3 points on XY plane 
        Rotate a circle back into original plane using quaternion
     */
    Point3d p1(pt1[0], pt1[1], pt1[2]);
    Point3d p2(pt2[0], pt2[1], pt2[2]);
    Point3d p3(pt3[0], pt3[1], pt3[2]);
    Point3d center;
    if (c)
    {
        center.set(c[0], c[1], c[2]);
    }

    const Vector3d p2top1 = p1 - p2;
    const Vector3d p2top3 = p3 - p2;

    const Vector3d circle_normal = p2top1.crossProduct(p2top3).normalize();
    const Vector3d xy_normal(0, 0, 1);


    Quaternion rot_quat;
    // building rotation quaternion
    {
        // Rotation axis around which we will rotate our circle into XY plane
        Vector3d rot_axis = xy_normal.crossProduct(circle_normal).normalize();
        const double rot_angle = xy_normal.angleTo(circle_normal); // radians

        const double w = cos(rot_angle * 0.5);
        rot_axis *= sin(rot_angle * 0.5);

        rot_quat.set(w, rot_axis.x, rot_axis.y, rot_axis.z);
    }

    Quaternion rot_back_quat;
    // building backward rotation quaternion, same as prev. but -angle
    {
        const double rot_angle = -(xy_normal.angleTo(circle_normal)); // radians
        const double w_back = cos(rot_angle * 0.5);
        Vector3d rot_back_axis = xy_normal.crossProduct(circle_normal).normalize();
        rot_back_axis *= sin(rot_angle * 0.5);
        rot_back_quat.set(w_back, rot_back_axis.x, rot_back_axis.y, rot_back_axis.z);
    }

    rot_quat.rotate(p1);
    rot_quat.rotate(p2);
    rot_quat.rotate(p3);
    rot_quat.rotate(center);

    if (!c)
    {
        // calculate 2D center
        FindCircleCenter(&p1, &p2, &p3, &center);
    }

    // calc radius
    const double radius = center.distanceTo(p1);

    const float DEG2RAD = 3.14159f / 180.0f;
    // build circle
    for (int i = 0; i < 360; ++i)
    {
        float degInRad = i * DEG2RAD;
        Point3d pt(cos(degInRad) * radius + center.x, sin(degInRad) * radius + center.y, 0);

        // rotate the point back into original plane 
        rot_back_quat.rotate(pt);

        circle->push_back(pt.x);
        circle->push_back(pt.y);
        circle->push_back(pt.z);
    }
}

【讨论】:

    【解决方案3】:

    以下是Mark的answer的C#/Unity端口。它使用来自Unity's scripting API 的类型和实用函数。

    // triangle "edges"
    var t = p2 - p1;
    var u = p3 - p1;
    var v = p3 - p2;
    
    // triangle normal
    var w = Vector3.Cross(t, u);
    var wsl = Vector3.Dot(w, w);
    // TODO: if (wsl<10e-14) return false; // area of the triangle is too small (you may additionally check the points for colinearity if you are paranoid)
    
    // helpers
    var iwsl2 = 1f / (2f * wsl);
    var tt = Vector3.Dot(t, t);
    var uu = Vector3.Dot(u, u);
    
    // result circle
    Vector3 circCenter = p1 + (u * tt * (Vector3.Dot(u, v)) - t * uu * (Vector3.Dot(t, v))) * iwsl2;
    var     circRadius = Mathf.Sqrt(tt * uu * (Vector3.Dot(v, v)) * iwsl2 * 0.5f);
    Vector3 circAxis   = w / Mathf.Sqrt(wsl);
    

    使用Unity's Gizmos,圆可以画成如下(本例使用30条线段近似):

    // Draw the circle:
    Gizmos.color = Color.white;
    for (int i = 0; i < 30; ++i) 
    {
        Gizmos.DrawLine(
            circCenter + Quaternion.AngleAxis(360f / 30f *  i     , circAxis) * (p1 - circCenter),
            circCenter + Quaternion.AngleAxis(360f / 30f * (i + 1), circAxis) * (p1 - circCenter)
        );
    }
    

    顶点位置var p1 = new Vector3(0f, 1.44f, 0f); var p2 = new Vector3(0f, 0.73f, 0.65f); var p3 = new Vector3(0f, -1.04f, 0f);的结果如下所示:

    【讨论】:

    • 这就是我要找的。我第一次尝试this answer,但没有成功。
    猜你喜欢
    • 1970-01-01
    • 2016-07-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多