Skip to content

Commit 709feae

Browse files
authored
Merge pull request #323 from srseil/bspline-triangulation
[twobrains] Fix unreliable OctTree hit testing for free-form BSplines
2 parents 2871fa2 + 1337f2f commit 709feae

1 file changed

Lines changed: 33 additions & 9 deletions

File tree

CADability/BSpline2D.cs

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,7 +1121,7 @@ internal void SetZValues(BSpline original, Plane projectionPlane)
11211121
if (zMax < p.z) zMax = p.z;
11221122
}
11231123
}
1124-
protected override void GetTriangulationBasis(out GeoPoint2D[] points, out GeoVector2D[] directions, out double[] parameters)
1124+
private double[] GetTriangulationKnots()
11251125
{
11261126
double[] tknots = knots;
11271127
if (knots.Length == 2)
@@ -1131,6 +1131,37 @@ protected override void GetTriangulationBasis(out GeoPoint2D[] points, out GeoVe
11311131
tknots[1] = (knots[0] + knots[1]) / 2.0;
11321132
tknots[2] = knots[1];
11331133
}
1134+
// The triangulation based HitTest relies on each span's control triangle enclosing the
1135+
// curve, which only holds while a span turns less than ~180°. Free-form splines with few
1136+
// knots have spans that turn too much, so the coarse triangle test rejects them and only
1137+
// the knot points (notably the curve start/end) register as hits. Subdivide such spans by
1138+
// turning angle so the resulting triangles stay tight enough to bound the curve.
1139+
const double maxSpanAngle = Math.PI / 4.0; // 45° per sub-span
1140+
List<double> res = new List<double>(tknots.Length);
1141+
GeoVector2D d0 = DirectionAtParam(tknots[0]);
1142+
for (int i = 0; i < tknots.Length - 1; i++)
1143+
{
1144+
res.Add(tknots[i]);
1145+
GeoVector2D d1 = DirectionAtParam(tknots[i + 1]);
1146+
if (!d0.IsNullVector() && !d1.IsNullVector())
1147+
{
1148+
double dot = d0.x * d1.x + d0.y * d1.y;
1149+
double cross = d0.x * d1.y - d0.y * d1.x;
1150+
double angle = Math.Atan2(Math.Abs(cross), dot); // [0, pi], directions are normalized
1151+
int sub = (int)Math.Ceiling(angle / maxSpanAngle);
1152+
for (int j = 1; j < sub; j++)
1153+
{
1154+
res.Add(tknots[i] + (tknots[i + 1] - tknots[i]) * j / sub);
1155+
}
1156+
}
1157+
d0 = d1;
1158+
}
1159+
res.Add(tknots[tknots.Length - 1]);
1160+
return res.ToArray();
1161+
}
1162+
protected override void GetTriangulationBasis(out GeoPoint2D[] points, out GeoVector2D[] directions, out double[] parameters)
1163+
{
1164+
double[] tknots = GetTriangulationKnots();
11341165
points = new GeoPoint2D[tknots.Length];
11351166
directions = new GeoVector2D[tknots.Length];
11361167
parameters = new double[tknots.Length];
@@ -1142,14 +1173,7 @@ protected override void GetTriangulationBasis(out GeoPoint2D[] points, out GeoVe
11421173
}
11431174
internal override void GetTriangulationPoints(out GeoPoint2D[] points, out double[] parameters)
11441175
{
1145-
double[] tknots = knots;
1146-
if (knots.Length == 2)
1147-
{ // there are splines with only two knots and degree 14 which represent a full circle. We invent an additional knot
1148-
tknots = new double[3];
1149-
tknots[0] = knots[0];
1150-
tknots[1] = (knots[0] + knots[1]) / 2.0;
1151-
tknots[2] = knots[1];
1152-
}
1176+
double[] tknots = GetTriangulationKnots();
11531177
points = new GeoPoint2D[tknots.Length];
11541178
parameters = new double[tknots.Length];
11551179
for (int i = 0; i < tknots.Length; i++)

0 commit comments

Comments
 (0)