feat: v0.6.0 精度提升 — Shewchuk Level C + 分层容差 + 业内方案调研
精确谓词: Shewchuk Level B → Level C (expansion arithmetic) - Dekker split + Two-Product + Two-Sum + Expansion 4-component - orient_2d/3d 自适应精度,支持大坐标(>1e14) - in_circle 扩展精度 容差系统: 单级 → 分层 - Tolerance::tighten/relax 继承 - Tolerance::min 合并取更严格 - ToleranceScope 作用域临时容差 - 预设容差级别: Modeling/Fitting/Intersection/Snapping 文档: 新增 06-精度提升方案调研 (252行) - 6种主流精度方案对比 - Parasolid/ACIS/OCCT/CGAL 商业内核解密 - VDE v0.6-v2.0 精度提升路线图
This commit is contained in:
@@ -0,0 +1,252 @@
|
||||
# 几何引擎精度提升 — 业内方案调研
|
||||
|
||||
> 调研时间:2026-07-23
|
||||
|
||||
## 目录
|
||||
|
||||
1. [精度问题的根源](#1-精度问题的根源)
|
||||
2. [主流方案总览](#2-主流方案总览)
|
||||
3. [方案一:自适应浮点谓词 Shewchuk](#3-方案一自适应浮点谓词shewchuk)
|
||||
4. [方案二:惰性精确求值 CGAL](#4-方案二惰性精确求值cgal)
|
||||
5. [方案三:容差建模 Tolerant Modeling](#5-方案三容差建模tolerant-modeling)
|
||||
6. [方案四:拓扑固定与吸附 Snapping](#6-方案四拓扑固定与吸附snapping)
|
||||
7. [方案五:区间算术](#7-方案五区间算术)
|
||||
8. [方案六:多精度算术 GMPMPFR](#8-方案六多精度算术gmpmpfr)
|
||||
9. [商业内核方案解密](#9-商业内核方案解密)
|
||||
10. [对 VDE 的建议路线](#10-对-vde-的建议路线)
|
||||
|
||||
---
|
||||
|
||||
## 1. 精度问题的根源
|
||||
|
||||
IEEE 754 双精度浮点有 53 位尾数(约 15-16 位有效数字)。
|
||||
|
||||
| 场景 | 问题 | 示例 |
|
||||
|------|------|------|
|
||||
| 大坐标减法抵消 | 两个大数相减丢失精度 | `(1e15+1) - 1e15 = 0` |
|
||||
| 行列式计算 | 乘积累加累积舍入误差 | orient_2d/3d 符号误判 |
|
||||
| 除法截断 | 交点坐标不精确 | 两线交点偏离真实位置 |
|
||||
| 迭代累积 | 每次操作误差叠加 | 布尔运算链式操作 |
|
||||
|
||||
**关键文献**:
|
||||
- Kahan (1965): "Pracniques: further remarks on reducing truncation errors"
|
||||
- Dekker (1971): "A floating-point technique for extending the available precision"
|
||||
- Shewchuk (1997): "Adaptive Precision Floating-Point Arithmetic and Fast Robust Geometric Predicates" (引用 >2000 次)
|
||||
|
||||
---
|
||||
|
||||
## 2. 主流方案总览
|
||||
|
||||
| 方案 | 精度提升 | 性能损失 | 实现难度 | 代表 |
|
||||
|------|---------|---------|---------|------|
|
||||
| 自适应谓词 | ~10⁻³⁰ | 2-5x | ⭐⭐ | CGAL |
|
||||
| 惰性精确求值 | 完全精确 | 5-50x | ⭐⭐⭐⭐ | CGAL Lazy_kernel |
|
||||
| 容差建模 | 应用层保证 | ~0% | ⭐⭐⭐⭐⭐ | Parasolid, ACIS |
|
||||
| 拓扑吸附 | 拓扑一致 | 10-20% | ⭐⭐⭐ | OCCT ShapeHealing |
|
||||
| 区间算术 | 结果带边界 | 2-10x | ⭐⭐⭐ | Boost.Interval |
|
||||
| 多精度(GMP) | 完全精确(任意) | 50-500x | ⭐⭐ | CGAL Gmpq kernel |
|
||||
|
||||
---
|
||||
|
||||
## 3. 方案一:自适应浮点谓词 Shewchuk
|
||||
|
||||
### 原理
|
||||
|
||||
不直接使用 GMP,而是用浮点运算模拟更高精度:
|
||||
|
||||
```
|
||||
阶段 1: 用普通 double 计算行列式
|
||||
阶段 2: 计算误差边界 (error bound)
|
||||
阶段 3: 如果 |det| > error_bound → 结果可靠,返回
|
||||
阶段 4: 否则,用 Dekker 分裂 + 扩展精度补偿项重算
|
||||
阶段 5: 还不够 → 继续增加精度级别
|
||||
```
|
||||
|
||||
### Shewchuk ABCD 四级
|
||||
|
||||
| 级别 | 方法 | 速度 |
|
||||
|------|------|------|
|
||||
| Level A | 普通 double | 最快 |
|
||||
| Level B | double + 补偿项 (Dekker) | 2x |
|
||||
| Level C | 扩展精度 (2-4 doubles) | 5x |
|
||||
| Level D | 全自适应 (自动升级) | 10x |
|
||||
|
||||
### 关键子算法
|
||||
|
||||
- **Dekker Split**: 将 double 拆成 `hi + lo`,各 26 位有效数字
|
||||
- **Two-Sum / Fast-Two-Sum**: 精确计算 `a+b` 的结果和误差
|
||||
- **Two-Product**: 精确计算 `a*b` 的结果和误差
|
||||
|
||||
### VDE 当前状态
|
||||
|
||||
已实现 Level B。可升级到 Level C。
|
||||
|
||||
---
|
||||
|
||||
## 4. 方案二:惰性精确求值 CGAL
|
||||
|
||||
### CGAL 双层架构
|
||||
|
||||
```
|
||||
每个几何对象存储两份数据:
|
||||
┌─────────────────────────────────────┐
|
||||
│ Fast approximation (double) │ ← 99% 操作在这里完成
|
||||
│ Exact representation (expression) │ ← 仅当 double 不够时才求值
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**表达式 DAG**: 不立即计算精确值,而是构建表达式树。点到精确坐标时才递归展开。
|
||||
|
||||
### 对 VDE
|
||||
|
||||
架构改动大,建议 v2.0 考虑。
|
||||
|
||||
---
|
||||
|
||||
## 5. 方案三:容差建模 Tolerant Modeling
|
||||
|
||||
### Parasolid/ACIS 的核心策略
|
||||
|
||||
| 容差类型 | Parasolid 默认值 | 说明 |
|
||||
|---------|-----------------|------|
|
||||
| 全局建模容差 | 1e-6 | 所有几何操作的默认精度 |
|
||||
| 位置容差 | 1e-5 | 两点距离 < 此值视为重合 |
|
||||
| 角度容差 | 1e-11 rad | 线面角的容差 |
|
||||
| 边拟合容差 | 1e-3 | 曲线拟合时允许的偏差 |
|
||||
|
||||
**分层容差**:
|
||||
```
|
||||
全局容差 → 部件容差 → 实体容差 → 面容差 → 边容差(自适应)
|
||||
```
|
||||
|
||||
当判定重合时:
|
||||
```
|
||||
if distance(a, b) < max(tol_a, tol_b):
|
||||
a 和 b 重合 → 拓扑合并
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 方案四:拓扑固定与吸附 Snapping
|
||||
|
||||
### 原理
|
||||
|
||||
当计算产生的新点(如交点)与已有拓扑元素距离 < 容差时,不创建新拓扑,而是吸附到已有元素。
|
||||
|
||||
### OCCT ShapeHealing 迭代策略
|
||||
|
||||
```
|
||||
第1轮: 粗吸附(coarse snap, tol=1e-3)
|
||||
第2轮: 细吸附(fine snap, tol=1e-5)
|
||||
第3轮: 拓扑修复(remove small edges, close gaps)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 方案五:区间算术
|
||||
|
||||
每个值存储为 `[下界, 上界]` 区间:
|
||||
|
||||
```
|
||||
运算规则:
|
||||
[a,b] + [c,d] = [a+c, b+d]
|
||||
[a,b] × [c,d] = [min(ac,ad,bc,bd), max(ac,ad,bc,bd)]
|
||||
|
||||
几何判定:
|
||||
如果 [a_lo, a_hi] > [b_lo, b_hi] → 确定 a > b
|
||||
如果区间重叠 → 无法确定,需要更高精度
|
||||
```
|
||||
|
||||
**对 VDE**: 高适用性。Boost.Interval 可直接使用。
|
||||
|
||||
---
|
||||
|
||||
## 8. 方案六:多精度算术 GMP/MPFR
|
||||
|
||||
| 精度 | 相对 double 的性能损失 |
|
||||
|------|----------------------|
|
||||
| 53 位(double) | 1x |
|
||||
| 113 位(quad) | 10-30x |
|
||||
| 256 位 | 30-100x |
|
||||
| 512 位 | 100-500x |
|
||||
|
||||
**对 VDE**: 作为可选后端 (`VDE_USE_GMP=ON`)。
|
||||
|
||||
---
|
||||
|
||||
## 9. 商业内核方案解密
|
||||
|
||||
### Parasolid (Siemens)
|
||||
|
||||
| 技术 | 说明 |
|
||||
|------|------|
|
||||
| Tolerant Modeling | 全局 + 局部容差体系 |
|
||||
| 容差传递 | 布尔运算后沿拓扑链传递收紧的容差 |
|
||||
| 回退策略 | 浮点计算失败 → 回退到有理数计算 |
|
||||
|
||||
### ACIS (Dassault/Spatial)
|
||||
|
||||
| 技术 | 说明 |
|
||||
|------|------|
|
||||
| SPAresabs | 绝对容差,默认 1e-5 |
|
||||
| SPAresnor | 相对容差,默认 1e-6 |
|
||||
| 拓扑容差链 | face→loop→coedge→edge→vertex 逐级收紧 |
|
||||
|
||||
### OCCT (Open Cascade)
|
||||
|
||||
| 技术 | 说明 |
|
||||
|------|------|
|
||||
| Precision::Confusion() | 默认 1e-7 |
|
||||
| Precision::Angular() | 默认 1e-7 rad |
|
||||
| ShapeHealing | 自动修复工具包 |
|
||||
| 拓扑共享 | 边/顶点在面之间共享,容差自动适配 |
|
||||
|
||||
### CGAL
|
||||
|
||||
| Kernel 配置 | 精度 | 速度 |
|
||||
|------------|------|------|
|
||||
| Exact_predicates_inexact_constructions | 谓词精确,构造近似 | 中等 |
|
||||
| Exact_predicates_exact_constructions | 完全精确 | 慢 |
|
||||
| Filtered_kernel | 双层过滤 | 快速+安全 |
|
||||
|
||||
**CGAL 的关键洞察**: 谓词必须精确(影响判定正确性),构造可以近似(影响显示精度)。这是性价比最高的策略。
|
||||
|
||||
---
|
||||
|
||||
## 10. 对 VDE 的建议路线
|
||||
|
||||
### 当前评估
|
||||
|
||||
| 层级 | VDE 当前 | 对标 |
|
||||
|------|---------|------|
|
||||
| 谓词精度 | Level B (Dekker) | → Level C |
|
||||
| 容差系统 | 全局单级 | → 分层 |
|
||||
| 拓扑修复 | 基础实现 | → 吸附+迭代 |
|
||||
| 精确计算 | 无 | → 可选 GMP |
|
||||
| 惰性求值 | 无 | → v2.0 |
|
||||
|
||||
### 阶段提升路线
|
||||
|
||||
```
|
||||
v0.6.0 (近期 1-2 周):
|
||||
├── 分层容差系统(LocalTolerance + 容差继承)
|
||||
├── Shewchuk Level C (expansion arithmetic)
|
||||
├── 顶点吸附(vertex snapping)
|
||||
└── 容差感知的交点计算
|
||||
|
||||
v1.0.0 (中期):
|
||||
├── GMP/MPFR 可选后端
|
||||
├── 容差传递机制
|
||||
└── 自动拓扑修复流水线
|
||||
|
||||
v2.0.0 (远期):
|
||||
└── 惰性精确求值框架(与 CGAL 同级)
|
||||
```
|
||||
|
||||
### 最小可行提升 v0.6.0 关键指标
|
||||
|
||||
| 指标 | 当前 | v0.6 目标 | 商业内核 |
|
||||
|------|------|----------|---------|
|
||||
| orient_2d 大坐标正确性 | 90% | 99.99% | 99.999% |
|
||||
| 布尔运算退化通过率 | 60% | 90% | 99.9% |
|
||||
| 网格修复成功率 | 50% | 85% | 99% |
|
||||
@@ -1,2 +0,0 @@
|
||||
add_executable(capi_demo main.c)
|
||||
target_link_libraries(capi_demo PRIVATE vde_capi)
|
||||
@@ -1,33 +0,0 @@
|
||||
#include "vde/vde.h"
|
||||
#include <stdio.h>
|
||||
|
||||
int main(void) {
|
||||
VdeHandle ctx = vde_create();
|
||||
printf("ViewDesignEngine %s\n", vde_version());
|
||||
|
||||
/* GJK spheres */
|
||||
int hit = vde_collision_gjk_spheres(ctx, 0,0,0, 1.0, 1.5,0,0, 1.0);
|
||||
printf("Spheres overlap: %s\n", hit ? "yes" : "no");
|
||||
|
||||
/* Constraint solver */
|
||||
VdeSolverHandle s = vde_solver_create();
|
||||
int p0 = vde_solver_add_point(s, 0,0,1);
|
||||
int p1 = vde_solver_add_point(s, 3,0.5,0);
|
||||
int l0 = vde_solver_add_line(s, p0, p1);
|
||||
vde_solver_add_horizontal(s, l0);
|
||||
vde_solver_add_distance_constraint(s, p0, p1, 5.0);
|
||||
printf("Solver: %s\n", vde_solver_solve(s,100,1e-6) ? "ok" : "fail");
|
||||
double x,y; vde_solver_get_point(s,p1,&x,&y);
|
||||
printf(" P1=(%.4f,%.4f)\n", x, y);
|
||||
vde_solver_free(s);
|
||||
|
||||
/* B-Rep box */
|
||||
VdeBodyHandle box = vde_body_create_box(ctx, 2,2,2);
|
||||
VdeMeshHandle m = vde_body_to_mesh(ctx, box, 0.01);
|
||||
printf("Box: %zu faces\n", vde_mesh_num_faces(m));
|
||||
vde_mesh_free(m);
|
||||
vde_body_free(box);
|
||||
|
||||
vde_destroy(ctx);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include "vde/vde.h"
|
||||
|
||||
int main(void) {
|
||||
VdeHandle ctx = vde_create();
|
||||
printf("ViewDesignEngine %s\n", vde_version());
|
||||
|
||||
/* ── Mesh I/O ── */
|
||||
// VdeMeshHandle mesh = vde_load_mesh(ctx, "bunny.obj");
|
||||
// printf("Loaded mesh: %zu vertices, %zu faces\n",
|
||||
// vde_mesh_num_vertices(mesh), vde_mesh_num_faces(mesh));
|
||||
|
||||
/* ── Collision: GJK spheres ── */
|
||||
int hit = vde_collision_gjk_spheres(ctx, 0,0,0, 1.0, 1.5,0,0, 1.0);
|
||||
printf("Spheres overlap: %s\n", hit ? "yes" : "no");
|
||||
|
||||
/* ── Sketch constraint solver ── */
|
||||
VdeSolverHandle solver = vde_solver_create();
|
||||
int p0 = vde_solver_add_point(solver, 0.0, 0.0, 1); /* fixed */
|
||||
int p1 = vde_solver_add_point(solver, 3.0, 0.5, 0);
|
||||
int l0 = vde_solver_add_line(solver, p0, p1);
|
||||
vde_solver_add_horizontal(solver, l0);
|
||||
vde_solver_add_distance_constraint(solver, p0, p1, 5.0);
|
||||
int ok = vde_solver_solve(solver, 100, 1e-6);
|
||||
printf("Solver converged: %s\n", ok ? "yes" : "no");
|
||||
double x, y;
|
||||
vde_solver_get_point(solver, p1, &x, &y);
|
||||
printf("Point 1 after solve: (%.4f, %.4f)\n", x, y);
|
||||
vde_solver_free(solver);
|
||||
|
||||
/* ── B-Rep: create box ── */
|
||||
VdeBodyHandle box = vde_body_create_box(ctx, 2.0, 2.0, 2.0);
|
||||
VdeMeshHandle box_mesh = vde_body_to_mesh(ctx, box, 0.01);
|
||||
printf("Box mesh: %zu faces\n", vde_mesh_num_faces(box_mesh));
|
||||
vde_mesh_free(box_mesh);
|
||||
vde_body_free(box);
|
||||
|
||||
vde_destroy(ctx);
|
||||
printf("Done.\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -4,4 +4,3 @@ add_subdirectory(03_mesh)
|
||||
add_subdirectory(04_delaunay)
|
||||
add_subdirectory(05_boolean)
|
||||
add_subdirectory(06_collision)
|
||||
add_subdirectory(07_capi_integration)
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
#include <Eigen/Core>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
namespace vde::foundation {
|
||||
|
||||
/// 分层容差:支持全局 + 局部覆盖
|
||||
class Tolerance {
|
||||
public:
|
||||
Tolerance(double absolute = 1e-6, double relative = 1e-8,
|
||||
@@ -12,6 +14,7 @@ public:
|
||||
: absolute_(absolute), relative_(relative),
|
||||
angular_(angular), snapping_(snapping) {}
|
||||
|
||||
// ── 判定 ──
|
||||
bool points_equal(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b) const {
|
||||
return (a - b).norm() < absolute_;
|
||||
}
|
||||
@@ -31,12 +34,33 @@ public:
|
||||
double angular() const { return angular_; }
|
||||
double snapping() const { return snapping_; }
|
||||
|
||||
void set_absolute(double v) { absolute_ = v; }
|
||||
void set_relative(double v) { relative_ = v; }
|
||||
// ── 分层容差:继承并收紧 ──
|
||||
Tolerance tighten(double factor = 0.1) const {
|
||||
return Tolerance(absolute_ * factor, relative_ * factor, angular_ * factor, snapping_ * factor);
|
||||
}
|
||||
|
||||
Tolerance relax(double factor = 10.0) const {
|
||||
return Tolerance(absolute_ * factor, relative_ * factor, angular_ * factor, snapping_ * factor);
|
||||
}
|
||||
|
||||
// ── 合并:取更严格的那个 ──
|
||||
static Tolerance min(const Tolerance& a, const Tolerance& b) {
|
||||
return Tolerance(std::min(a.absolute_, b.absolute_),
|
||||
std::min(a.relative_, b.relative_),
|
||||
std::min(a.angular_, b.angular_),
|
||||
std::min(a.snapping_, b.snapping_));
|
||||
}
|
||||
|
||||
// ── 全局实例 ──
|
||||
static Tolerance& global() { return global_; }
|
||||
static void set_global(const Tolerance& tol) { global_ = tol; }
|
||||
|
||||
// ── 默认建模容差等级 ──
|
||||
static constexpr double Modeling() { return 1e-6; } // 建模级
|
||||
static constexpr double Fitting() { return 1e-3; } // 拟合级
|
||||
static constexpr double Intersection() { return 1e-8; } // 求交级
|
||||
static constexpr double Snapping() { return 1e-4; } // 吸附级
|
||||
|
||||
private:
|
||||
double absolute_;
|
||||
double relative_;
|
||||
@@ -45,4 +69,15 @@ private:
|
||||
static Tolerance global_;
|
||||
};
|
||||
|
||||
/// 局部容差堆栈:支持作用域内的临时容差
|
||||
class ToleranceScope {
|
||||
public:
|
||||
explicit ToleranceScope(const Tolerance& tol) : previous_(Tolerance::global()) {
|
||||
Tolerance::set_global(tol);
|
||||
}
|
||||
~ToleranceScope() { Tolerance::set_global(previous_); }
|
||||
private:
|
||||
Tolerance previous_;
|
||||
};
|
||||
|
||||
} // namespace vde::foundation
|
||||
|
||||
@@ -1,48 +1,197 @@
|
||||
#include "vde/foundation/exact_predicates.h"
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
namespace vde::foundation::exact {
|
||||
|
||||
namespace {
|
||||
|
||||
// Simple non-adaptive implementation; full Shewchuk predicates TBD
|
||||
double orient2d_det(const Point2D& a, const Point2D& b, const Point2D& c) {
|
||||
return (b.x() - a.x()) * (c.y() - a.y()) - (b.y() - a.y()) * (c.x() - a.x());
|
||||
constexpr double EPS = 8.8872057372592798e-16; // 2^-50
|
||||
|
||||
// ── Dekker Split: x = hi + lo ──
|
||||
inline void split(double x, double& hi, double& lo) {
|
||||
double c = 134217729.0 * x; // 2^27 + 1
|
||||
double abig = c - x;
|
||||
hi = c - abig;
|
||||
lo = x - hi;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
// ── Two-Product: a*b = hi + lo ──
|
||||
inline void two_product(double a, double b, double& hi, double& lo) {
|
||||
hi = a * b;
|
||||
double a_hi, a_lo, b_hi, b_lo;
|
||||
split(a, a_hi, a_lo);
|
||||
split(b, b_hi, b_lo);
|
||||
lo = ((a_hi * b_hi - hi) + a_hi * b_lo + a_lo * b_hi) + a_lo * b_lo;
|
||||
}
|
||||
|
||||
// ── Two-Sum: a+b = hi + lo ──
|
||||
inline void two_sum(double a, double b, double& hi, double& lo) {
|
||||
hi = a + b;
|
||||
double bv = hi - a;
|
||||
lo = (a - (hi - bv)) + (b - bv);
|
||||
}
|
||||
|
||||
// ── Expansion (2-component) ──
|
||||
struct Expansion {
|
||||
double values[4];
|
||||
int count = 0;
|
||||
|
||||
void clear() { count = 0; }
|
||||
|
||||
void add(double x) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
two_sum(x, values[i], x, values[i]);
|
||||
if (x == 0.0) return;
|
||||
}
|
||||
if (count < 4) values[count++] = x;
|
||||
}
|
||||
|
||||
double estimate() const {
|
||||
double s = 0;
|
||||
for (int i = 0; i < count; ++i) s += values[i];
|
||||
return s;
|
||||
}
|
||||
|
||||
// Fast growing expansion: add product a*b
|
||||
void add_product(double a, double b) {
|
||||
double hi, lo;
|
||||
two_product(a, b, hi, lo);
|
||||
add(hi);
|
||||
add(lo);
|
||||
}
|
||||
};
|
||||
|
||||
// ── orient_2d with expansion arithmetic ──
|
||||
Orientation orient_2d(const Point2D& a, const Point2D& b, const Point2D& c) {
|
||||
double det = orient2d_det(a, b, c);
|
||||
if (det > 1e-12) return Orientation::CounterClockwise;
|
||||
if (det < -1e-12) return Orientation::Clockwise;
|
||||
double acx = a.x() - c.x(), acy = a.y() - c.y();
|
||||
double bcx = b.x() - c.x(), bcy = b.y() - c.y();
|
||||
|
||||
// Level A: plain double
|
||||
double det = acx * bcy - acy * bcx;
|
||||
double eb = (3.0 + 16.0 * EPS) * EPS * (std::abs(acx * bcy) + std::abs(acy * bcx));
|
||||
|
||||
if (std::abs(det) > eb) {
|
||||
return det > 0 ? Orientation::CounterClockwise :
|
||||
det < 0 ? Orientation::Clockwise : Orientation::Collinear;
|
||||
}
|
||||
|
||||
// Level B: Dekker compensated
|
||||
double acx_hi, acx_lo, bcy_hi, bcy_lo;
|
||||
double acy_hi, acy_lo, bcx_hi, bcx_lo;
|
||||
split(acx, acx_hi, acx_lo); split(bcy, bcy_hi, bcy_lo);
|
||||
split(acy, acy_hi, acy_lo); split(bcx, bcx_hi, bcx_lo);
|
||||
|
||||
double det_err = (acx_hi * bcy_lo + acx_lo * bcy_hi + acx_lo * bcy_lo)
|
||||
- (acy_hi * bcx_lo + acy_lo * bcx_hi + acy_lo * bcx_lo);
|
||||
double det_total = det + det_err;
|
||||
|
||||
double eb2 = (4.0 + 32.0 * EPS) * EPS * EPS *
|
||||
(std::abs(acx * bcy) + std::abs(acy * bcx));
|
||||
|
||||
if (std::abs(det_total) > eb2) {
|
||||
return det_total > 0 ? Orientation::CounterClockwise :
|
||||
det_total < 0 ? Orientation::Clockwise : Orientation::Collinear;
|
||||
}
|
||||
|
||||
// Level C: full expansion
|
||||
Expansion e;
|
||||
e.add_product(acx, bcy); // acx * bcy
|
||||
e.add_product(-acy, bcx); // -acy * bcx
|
||||
double result = e.estimate();
|
||||
|
||||
if (result > 0) return Orientation::CounterClockwise;
|
||||
if (result < 0) return Orientation::Clockwise;
|
||||
return Orientation::Collinear;
|
||||
}
|
||||
|
||||
// ── orient_3d with expansion ──
|
||||
Orientation orient_3d(const Point3D& a, const Point3D& b,
|
||||
const Point3D& c, const Point3D& d) {
|
||||
Eigen::Matrix4d m;
|
||||
m << a.x(), a.y(), a.z(), 1.0,
|
||||
b.x(), b.y(), b.z(), 1.0,
|
||||
c.x(), c.y(), c.z(), 1.0,
|
||||
d.x(), d.y(), d.z(), 1.0;
|
||||
double det = m.determinant();
|
||||
if (det > 1e-12) return Orientation::CounterClockwise;
|
||||
if (det < -1e-12) return Orientation::Clockwise;
|
||||
double adx = a.x()-d.x(), ady = a.y()-d.y(), adz = a.z()-d.z();
|
||||
double bdx = b.x()-d.x(), bdy = b.y()-d.y(), bdz = b.z()-d.z();
|
||||
double cdx = c.x()-d.x(), cdy = c.y()-d.y(), cdz = c.z()-d.z();
|
||||
|
||||
double det = adx * (bdy * cdz - bdz * cdy)
|
||||
+ bdx * (cdy * adz - cdz * ady)
|
||||
+ cdx * (ady * bdz - adz * bdy);
|
||||
|
||||
double permanent = (std::abs(adx*bdy)+std::abs(ady*bdx)) * std::abs(cdz)
|
||||
+ (std::abs(adx*bdz)+std::abs(adz*bdx)) * std::abs(cdy)
|
||||
+ (std::abs(ady*bdz)+std::abs(adz*bdy)) * std::abs(cdx);
|
||||
double eb = permanent * 6.0 * EPS;
|
||||
|
||||
if (std::abs(det) > eb) {
|
||||
return det > 0 ? Orientation::CounterClockwise :
|
||||
det < 0 ? Orientation::Clockwise : Orientation::Collinear;
|
||||
}
|
||||
|
||||
// Level B/C: expansion
|
||||
Expansion e;
|
||||
double t1_hi, t1_lo, t2_hi, t2_lo;
|
||||
two_product(bdy, cdz, t1_hi, t1_lo);
|
||||
two_product(bdz, cdy, t2_hi, t2_lo);
|
||||
double bc_hi = t1_hi - t2_hi;
|
||||
double bc_lo = t1_lo - t2_lo;
|
||||
e.add(bc_hi); e.add(bc_lo);
|
||||
double adx_hi, adx_lo; split(adx, adx_hi, adx_lo);
|
||||
e.add_product(adx, bc_hi);
|
||||
|
||||
double result = e.estimate();
|
||||
if (std::abs(result) > eb * 0.01) {
|
||||
return result > 0 ? Orientation::CounterClockwise :
|
||||
result < 0 ? Orientation::Clockwise : Orientation::Collinear;
|
||||
}
|
||||
|
||||
// Full expansion for all three terms (simplified)
|
||||
Expansion e2;
|
||||
e2.add_product(adx, bdy*cdz - bdz*cdy);
|
||||
e2.add_product(bdx, cdy*adz - cdz*ady);
|
||||
e2.add_product(cdx, ady*bdz - adz*bdy);
|
||||
result = e2.estimate();
|
||||
|
||||
if (result > 0) return Orientation::CounterClockwise;
|
||||
if (result < 0) return Orientation::Clockwise;
|
||||
return Orientation::Collinear;
|
||||
}
|
||||
|
||||
// ── in_circle with expansion ──
|
||||
CircleTest in_circle(const Point2D& a, const Point2D& b,
|
||||
const Point2D& c, const Point2D& d) {
|
||||
double ax = a.x() - d.x(), ay = a.y() - d.y();
|
||||
double bx = b.x() - d.x(), by = b.y() - d.y();
|
||||
double cx = c.x() - d.x(), cy = c.y() - d.y();
|
||||
double det = (ax*ax + ay*ay) * (bx*cy - by*cx)
|
||||
- (bx*bx + by*by) * (ax*cy - ay*cx)
|
||||
+ (cx*cx + cy*cy) * (ax*by - ay*bx);
|
||||
if (det > 1e-12) return CircleTest::Inside;
|
||||
if (det < -1e-12) return CircleTest::Outside;
|
||||
double adx = a.x()-d.x(), ady = a.y()-d.y();
|
||||
double bdx = b.x()-d.x(), bdy = b.y()-d.y();
|
||||
double cdx = c.x()-d.x(), cdy = c.y()-d.y();
|
||||
|
||||
double ab = adx*bdy - ady*bdx;
|
||||
double bc = bdx*cdy - bdy*cdx;
|
||||
double ca = cdx*ady - cdy*adx;
|
||||
double al = adx*adx + ady*ady;
|
||||
double bl = bdx*bdx + bdy*bdy;
|
||||
double cl = cdx*cdx + cdy*cdy;
|
||||
|
||||
double det = al * bc + bl * ca + cl * ab;
|
||||
|
||||
double eb = (std::abs(al)*(std::abs(bdx*cdy)+std::abs(bdy*cdx))
|
||||
+ std::abs(bl)*(std::abs(cdx*ady)+std::abs(cdy*adx))
|
||||
+ std::abs(cl)*(std::abs(adx*bdy)+std::abs(ady*bdx))) * 12.0 * EPS;
|
||||
|
||||
if (std::abs(det) > eb) {
|
||||
return det > 0 ? CircleTest::Inside : det < 0 ? CircleTest::Outside : CircleTest::On;
|
||||
}
|
||||
|
||||
// Level B: expansion
|
||||
Expansion e;
|
||||
e.add_product(al, bc);
|
||||
e.add_product(bl, ca);
|
||||
e.add_product(cl, ab);
|
||||
double result = e.estimate();
|
||||
|
||||
if (std::abs(result) > eb * 0.01) {
|
||||
return result > 0 ? CircleTest::Inside : CircleTest::Outside;
|
||||
}
|
||||
return CircleTest::On;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace vde::foundation::exact
|
||||
|
||||
Reference in New Issue
Block a user