You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

355 lines
12 KiB

#pragma once
#include <real.hpp>
#include <utility>
#include <vec.hpp>
#include <line.hpp>
class ISolid
{
public:
virtual ~ISolid() = default;
virtual real sdf(const Vec3 &p) = 0;
};
Vec2 get2DRepOf3DPt(const Vec3 &pt3D, const Vec3 &u, const Vec3 &v, const Vec3 &localO)
{
Vec3 OP = pt3D - localO;
return {OP.dot(u), OP.dot(v)};
}
Vec2 get2DRepOf3DDir(const Vec3 &dir, const Vec3 &u, const Vec3 &v) { return Vec2{dir.dot(u), dir.dot(v)}.normalize(); }
class IExtrudedSolid : public ISolid
{
public:
Polyline _profile; // TODO: may be replaced by const ref to profile
real _rScale;
public:
IExtrudedSolid(Polyline profile, real rScale)
: _profile(std::move(profile))
, _rScale(rScale)
{
}
};
/**
* calculate winding number of a point w.r.t. a segment ab
*/
real unsignedWindingNumberSegment(const Vec3 &p, const Vec3 &a, const Vec3 &b, const Vec3 &refNormal)
{
Vec3 pa = a - p;
Vec3 pb = b - p;
return std::acos(std::clamp(pa.dot(pb) / (pa.norm() * pb.norm()), static_cast<real>(-1.), static_cast<real>(1.))) /
(std::numbers::pi * 2);
}
class ExtrudedSolidPolyline : public IExtrudedSolid
{
private:
Polyline _axis;
Pt2Array _localProfile2D;
Pt2Array _localCircleCenter2D;
Pt2Array _localInCircleDir;
public:
ExtrudedSolidPolyline(Polyline profile, Polyline axis, real rScale)
: IExtrudedSolid(std::move(profile), rScale)
, _axis(std::move(axis))
{
assert(_profile.isClosed());
// TODO: project profile at st point to 2D
Vec3 T = _axis.der1(0).normalize();
Vec3 N = _axis.der2(0).normalize();
Vec3 B = T.cross(N);
Vec3 Q = _axis.eval(0);
int segCount = _profile.getPoints().size();
_localProfile2D.resize(segCount);
_localInCircleDir.resize(segCount);
_localCircleCenter2D.resize(segCount);
for (int i = 0; i < segCount; ++i)
{
_localProfile2D[i] = get2DRepOf3DPt(_profile.getPoints()[i] - Q, N, B, Q);
_localCircleCenter2D[i] = get2DRepOf3DPt(_profile.getCircularArcs()[i].center - Q, N, B, Q);
_localInCircleDir[i] = get2DRepOf3DDir(_profile.getCircularArcs()[i].inCircleDir, N, B);
}
}
real sdf(const Vec3 &p) override
{
ClosestDescOnSeg closestDesc = _axis.getClosestParam(p);
// TNB coordinate system
auto t = closestDesc.t;
Vec3 T = _axis.der1(t).normalize();
Vec3 N = _axis.der2(t).normalize();
Vec3 B = T.cross(N);
Vec3 Q = _axis.eval(t);
Vec3 QP = p - Q;
auto p2D = get2DRepOf3DPt(QP, N, B, Q);
// TODO: to test if p2D is in _localProfile2D
for (auto i = 0; i < _localProfile2D.size(); ++i)
{
}
ClosestDescOnSeg closestDescOnProfile = distance2Profile2D(p2D);
// TODO: on的情况
return closestDescOnProfile.dis * isPtInside2DProfile(p2D) ? 1 : -1;
}
private:
/**
* in + in = out
* in + out = in
* out + in = in
* out + out = out
*/
bool isPtInside2DProfile(const Vec2 &p2D)
{
assert(_profile.isClosed());
int segCount = _profile.getBugles().size();
// 先判断是否在outline上
// 顺便判断点-扇的位置关系
bool inFan = false;
int onLinesegButHasBugle = -1;
for (int i = 0; i < segCount; ++i)
{
const Vec2 &a = _localProfile2D[i];
const Vec2 &b = _localProfile2D[(i + 1) % segCount];
if (_profile.getBugles()[i] == 0)
{
//line segment
if (isPointOnSegment(p2D, a, b))
{
return true;
}
continue;
}
if (isPointOnSegment(p2D, a, b))
{
onLinesegButHasBugle = i;
break;
}
const auto &arc = _profile.getCircularArcs()[i];
real po = (p2D - _localCircleCenter2D[i]).norm();
if (po == arc.radius)
{
return true;
}
if (po < arc.radius && (p2D - a).dot(_localInCircleDir[i]) > 0)
{
inFan = true;
break;
}
}
// 判断点-直线多边形的关系
const auto ptInPolygon = [&](const Vec2 &p) {
int intersectionCount = 0;
// int onSegIdx = -1;
constexpr int numRays = 3; // 射线数量
int majorityIn = 0; // 在多边形内的射线计数
int majorityOut = 0; // 在多边形外的射线计数
for (int rayIdx = 0; rayIdx < numRays; ++rayIdx)
{
double angle = (2.0 * std::numbers::pi * rayIdx) / numRays;
Vec2 rayDir(cos(angle), sin(angle));
int crossings = 0;
for (int i = 0; i < segCount; ++i)
{
const Vec2 &a = _localProfile2D[i];
const Vec2 &b = _localProfile2D[(i + 1) % segCount];
assert(isPointOnSegment(p, a, b));
// if (isPointOnSegment(p2D, a, b))
// {
// onSegIdx = i;
// break;
// }
// 使用向量方法计算射线和边的交点
double dx1 = b[0] - a[0];
double dy1 = b[1] - a[1];
double dx2 = rayDir[0];
double dy2 = rayDir[1];
double determinant = dx1 * dy2 - dy1 * dx2;
// 如果determinant为0,则射线和边平行,不计算交点
if (isEqual(determinant, 0))
continue;
double t1 = ((p[0] - a[0]) * dy2 - (p[1] - a[1]) * dx2) / determinant;
double t2 = ((p[0] - a[0]) * dy1 - (p[1] - a[1]) * dx1) / determinant;
// 检查交点是否在边上(0 <= t1 <= 1)且射线上(t2 >= 0)
if (t1 >= 0 && t1 <= 1 && t2 >= 0)
{
crossings++;
}
}
if (crossings % 2 == 0)
{
majorityOut++;
}
else
{
majorityIn++;
}
}
return majorityIn > majorityOut;
};
if (onLinesegButHasBugle != -1)
{
// 需要特殊考虑的情况
// 从p2D向inCircle方向前进一小步
Vec2 samplePt =
p2D + _localCircleCenter2D[onLinesegButHasBugle] * std::numeric_limits<real>::epsilon() * 1e6;
return !ptInPolygon(samplePt); // 取反
}
return ptInPolygon(p2D) ^ inFan;
// TODO: 返回on的情况
}
ClosestDescOnSeg distance2Profile2D(const Vec2 &p2D)
{
// TODO: 2D 下点到圆弧的距离应该可以直接算,不用这么迭代!
assert(_profile.isClosed());
real closestDis = std::numeric_limits<real>::max();
real closestParam;
int segCount = _profile.getBugles().size();
for (int i = 0; i < segCount; ++i)
{
const Vec2 &a = _localProfile2D[i];
const Vec2 &b = _localProfile2D[(i + 1) % segCount];
const auto &arc = _profile.getCircularArcs()[i];
real dis2Seg = Polyline::segPtDist(p2D, a, b).dis;
if (dis2Seg - arc.h > closestDis)
continue;
if ((a - p2D).norm() < closestDis)
{
closestDis = (a - p2D).norm();
closestParam = i;
}
if ((b - p2D).norm() < closestDis)
{
closestDis = (b - p2D).norm();
closestParam = i + 1;
}
int segInsertedCnt = arc.theta / DISC_ARC_ANGLE;
for (int j = 0; j < segInsertedCnt; ++j)
{
real insertParam = i + j * DISC_ARC_ANGLE / arc.theta;
real dis2InsertPt = (p2D - eval(insertParam)).norm();
if (dis2InsertPt < closestDis)
{
closestDis = dis2InsertPt;
closestParam = insertParam;
}
}
}
// TODO: 为了鲁棒和精度,应该在每个可能最近的seg上做newton iteration
int seg = static_cast<int>(closestParam);
// Q = arc.center + arc.radius * (arc.u * std::cos(phi) + arc.v * std::sin(phi))
// d2 = (Q - p)^2
Vec2 q = eval(closestParam);
Vec2 qDer1 = der1(closestParam);
Vec2 qDer2 = der2(closestParam);
real lDer1 = (q - p2D).dot(qDer1);
int iter = 0;
while (abs(lDer1) > std::numeric_limits<real>::epsilon() * 1e6)
{
closestParam -= lDer1 / (qDer1.dot(qDer1) + (q - p2D).dot(qDer2)); // -der1 / der2
q = eval(closestParam);
qDer1 = der1(closestParam);
qDer2 = der2(closestParam);
lDer1 = (q - p2D).dot(qDer1);
printf("After iter %d, dL is %lf\n", iter, lDer1);
if (closestParam < seg - std::numeric_limits<real>::epsilon())
{
closestParam = seg;
closestDis = (_localProfile2D[seg] - p2D).norm();
break;
}
else if (closestParam > seg + 1 + std::numeric_limits<real>::epsilon())
{
closestParam = seg + 1;
closestDis = (_localProfile2D[(seg + 1) % segCount] - p2D).norm();
break;
}
closestDis = (q - p2D).norm();
iter++;
}
return {closestDis, closestParam};
}
bool isOn2DPolyline(const Vec2 &p2D)
{
int segCount = _profile.getBugles().size();
for (int i = 0; i < segCount; ++i)
{
const Vec2 &a = _localProfile2D[i];
const Vec2 &b = _localProfile2D[(i + 1) % segCount];
if (_profile.getBugles()[i] == 0)
{
//line segment
if (isPointOnSegment(p2D, a, b))
{
return true;
}
continue;
}
}
}
bool isPointOnSegment(const Vec2 p, const Vec2 &a, const Vec2 &b)
{
// check collinearity
double crossProduct = (p[1] - a[1]) * (b[0] - a[0]) - (p[0] - a[0]) * (b[1] - a[1]);
if (!isEqual(crossProduct, 0))
return false; // Not collinear
// Check if point is within segment bounds
return (p[0] >= std::min(a[0], b[0]) && p[0] <= std::max(a[0], b[0]) && p[1] >= std::min(a[1], b[1]) &&
p[1] <= std::max(a[1], b[1]));
}
real wnCircularArc(
const Vec3 &p, const Vec3 &a, const Vec3 &b, const Vec3 &plgNormal, const Polyline::CircularArc &arc, int dir)
{
Vec3 pa = a - p;
Vec3 pb = b - p;
real wn =
std::acos(std::clamp(pa.dot(pb) / (pa.norm() * pb.norm()), static_cast<real>(-1.), static_cast<real>(1.))) /
(std::numbers::pi * 2);
auto inOutCircle = arc.inCircleCheck(p);
if (inOutCircle == PtBoundaryRelation::Outside || pa.cross(pb).dot(plgNormal) < 0)
{
// outside
// pa.cross(pb).dot(plgNormal) 不会 == 0
return -wn * dir;
}
if (inOutCircle == PtBoundaryRelation::Inside)
{
return wn * dir;
}
return 0;
}
// Vec2 eval2DProfile(real param)
// {
// int seg = static_cast<int>(param);
// real tOnSeg = param - seg;
// const auto &arc = circularArcs[seg];
// real phi = tOnSeg * arc.theta;
// return arc.center + arc.radius * (arc.u * std::cos(phi) + arc.v * std::sin(phi));
// }
};
class ExtrudedSolidPolynomialLine : public IExtrudedSolid
{
protected:
PolynomialLine _axis;
};