# Why is there a discrepancy between JoinedCurve/FilledCurve and the underlying BSplineCurve segments?

This may be related to How to discretize a BezierCurve?, but this question deals with BSplineCurves with specific SplineWeights, so I don't think the answers there will help here.

Background

I am using version 11.3.0.0 (on Mac OS 10.11.5).

I recently wrote an Arc function that takes the same arguments as Circle. This spits out a list of degree 2 BSplineCurves with the appropriate SplineWeights to generate pieces of a circle. I did this so that the last piece can be wrapped in an Arrow to make circular-arc arrows.

I also wanted to use JoinedCurve and FilledCurve with the arcs, which cannot be done with Circle.

The Problem

The code

segments = {
BSplineCurve[{{1,0},{1,1},{0,1}},SplineWeights->{1,1/Sqrt[2],1}],
BSplineCurve[{{0,1},{-1,1},{-1,0}},SplineWeights->{1,1/Sqrt[2],1}]
};
Graphics[{{Red,segments},JoinedCurve[segments]}]


produces the output

The red curve is drawn first and seems to be correct. The black curve is drawn second and overlays the first segment properly; however, the second segment doesn't quite follow the red circle. Ideally, the black curve should completely obscure the red curve.

I was thinking that the problem might be that that JoinedCurve was trying to use a cubic spline to try to match the quadratic rational spline; however, then the first segment would not overlay so precisely (a cubic spline cannot exactly trace an arc of a circle unless it employs the proper weights).

The same problem is displayed by FilledCurve:

segments = {
BSplineCurve[{{1/4,-3/4},{1,-3/4},{1,0}},SplineWeights->{1,1/Sqrt[2],1}],
BSplineCurve[{{1,0},{1,3/4},{1/4,3/4}},SplineWeights->{1,1/Sqrt[2],1}],
Line[{{1/4,3/4},{-1/4,3/4}}],
BSplineCurve[{{-1/4,3/4},{-1,3/4},{-1,0}},SplineWeights->{1,1/Sqrt[2],1}],
BSplineCurve[{{-1,0},{-1,-3/4},{-1/4,-3/4}},SplineWeights->{1,1/Sqrt[2],1}],
Line[{{-1/4,-3/4},{1/4,-3/4}}]
};
Graphics[{{Red,segments},Lighter[Purple,3/4],FilledCurve[segments]}]


Does anyone know what is going on here, and whether this persists in newer versions as well.

This problem arose when I was creating my Gravatar for Halloween 2020:

Hopefully, I can remove the mask by next Halloween.

1. To get "the black curve (to) completely obscure the red curve"

You can replace BSplineCurves with Lines using BSplineFunction:

sw = {1, 1/Sqrt[2], 1};
segments = {BSplineCurve[{{1, 0}, {1, 1}, {0, 1}}, SplineWeights -> sw],
BSplineCurve[{{0, 1}, {-1, 1}, {-1, 0}}, SplineWeights -> sw]};

Graphics[{Red , segments, Black, Dashed,
JoinedCurve[segments /.
BSplineCurve[a__] :> Line[BSplineFunction[a] /@ Subdivide[100]]]},
ImageSize -> Large]


Similarly, for FilledCurve:

segments = {BSplineCurve[{{1/4, -(3/4)}, {1, -(3/4)}, {1, 0}},
SplineWeights -> {1, 1/Sqrt[2], 1}],
BSplineCurve[{{1, 0}, {1, 3/4}, {1/4, 3/4}},
SplineWeights -> {1, 1/Sqrt[2], 1}],
Line[{{1/4, 3/4}, {-(1/4), 3/4}}],
BSplineCurve[{{-(1/4), 3/4}, {-1, 3/4}, {-1, 0}},
SplineWeights -> {1, 1/Sqrt[2], 1}],
BSplineCurve[{{-1, 0}, {-1, -(3/4)}, {-(1/4), -(3/4)}},
SplineWeights -> {1, 1/Sqrt[2], 1}],
Line[{{-(1/4), -(3/4)}, {1/4, -(3/4)}}]};

Graphics[{Red, segments, EdgeForm[{Dashed, Black}], FaceForm[Opacity[.25, Blue]],
FilledCurve[segments /.
BSplineCurve[a__] :> Line[BSplineFunction[a] /@ Subdivide[100]]]},
ImageSize -> Large]


2. "Why is there a discrepancy?"

That is, JoinedCurve and FilledCurve both prepend the coordinate list of segment i+1 with the last coordinate of segment i. This does not create an issue when segment primitives are Lines; but with BezierCurve and BSplineCurve repeated coordinates give a different picture. (It is unfortunate that these functions seem not to check if the first coordinate of segment i+1 is already the same as th last coordinate of segment i before inserting a new point).

What spline weight is assigned to the new point is anyone's guess. With a small weight, say 10^-5, for the newly inserted point we can replicate the output from JoinedCurve[segments]:

segmentsb = {BSplineCurve[{{1, 0}, {1, 1}, {0, 1}}, SplineWeights -> sw],
BSplineCurve[{{0, 1}, {0, 1}, {-1, 1}, {-1, 0}},
SplineWeights -> Prepend[10^-5] @ sw]};

Graphics[{Green, segmentsb, Black, Dashed,
JoinedCurve[segments]}, ImageSize -> Large]


• (+1) I saw the description in the documentation, which says "... is equivalent to ..." Obviously, they are not equivalent. I have written a function which takes the points that will be duplicated out of the data. This makes things work as I had expected. – robjohn Nov 1 '20 at 11:35

To avoid the issue mentioned by kglr where points are repeated, you can just add another layer of list:

segments = {
BSplineCurve[{{1,0},{1,1},{0,1}},SplineWeights->{1,1/Sqrt[2],1}],
BSplineCurve[{{0,1},{-1,1},{-1,0}},SplineWeights->{1,1/Sqrt[2],1}]
};
Graphics[{{Red,segments}, JoinedCurve[List/@segments]}]


• (+1) This works for JoinedCurve, but gives odd output when applied to the FilledCurve example. – robjohn Nov 1 '20 at 11:31
• @robjohn You're right, my approach doesn't work for FilledCurve. Why don't you create a single BSplineCurve instead of trying to use JoinedCurve/FilledCurve to connect multiple BSplineCurves? – Carl Woll Nov 1 '20 at 15:23
• I thought about it, but it seemed easier to put together the pieces than to work out the placement all of the knots to get the same output. Furthermore, I still need to use FilledCurve to fill the inside, anyway. – robjohn Nov 1 '20 at 17:38
• @robjohn It's too bad the "SplineCircle" resource function is broken, otherwise you could just use that. – Carl Woll Nov 1 '20 at 17:49
• Does any one have a reference to understanding SplineWeights? The documentation illustrates how to use them, but not their definition. Is it obvious that the o.p.'s choice of SplineWeights -> {1, 1/Sqrt[2], 1} is the correct one? Why? – Craig Carter Nov 3 '20 at 17:55

In addition to the two problems I mentioned above, there was a third problem that

segments = {
BSplineCurve[{{1,0},{1,1},{0,1}},SplineWeights->{1,1/Sqrt[2],1}],
BSplineCurve[{{0,1},{-1,1},{-1,0}},SplineWeights->{1,1/Sqrt[2],1}]
};
Graphics[{Arrow[JoinedCurve[segments]}]


produced no output.

The other answers do get to the root cause of the problem; that is, the addition of points to the segments by JoinedCurve and FilledCurve. However, approximating the curve with small line segments produces choppy output. Encapsulating the segments in Lists produces odd results when filled.

Solution to All Three Problems

To solve all three problems and produce a nice, smooth curve, I ended up writing a function which removes the points that are going to be added by JoinedCurve and FilledCurve. This function is

alef = {First[#],Sequence @@ MapAt[Rest,Rest[#],{All,1}]}&


In a comment, kglr notes that the following is equivalent and shorter

alef = MapAt[Rest,#,{2;;,1}]&


The name is an acronym for "assume last equals first".

1. JoinedCurve Problem

segments = {
BSplineCurve[{{1,0},{1,1},{0,1}},SplineWeights->{1,1/Sqrt[2],1}],
BSplineCurve[{{0,1},{-1,1},{-1,0}},SplineWeights->{1,1/Sqrt[2],1}]
};
Graphics[{{Red,segments},JoinedCurve[alef[segments]]}]


produces

as desired.

2. Arrow and JoinedCurve Problem

segments = {
BSplineCurve[{{1,0},{1,1},{0,1}},SplineWeights->{1,1/Sqrt[2],1}],
BSplineCurve[{{0,1},{-1,1},{-1,0}},SplineWeights->{1,1/Sqrt[2],1}]
};
Graphics[{Arrow[JoinedCurve[alef[segments]]]}]


produces

as desired.

3. FilledCurve Problem

segments = {
BSplineCurve[{{1/4,-3/4},{1,-3/4},{1,0}},SplineWeights->{1,1/Sqrt[2],1}],
BSplineCurve[{{1,0},{1,3/4},{1/4,3/4}},SplineWeights->{1,1/Sqrt[2],1}],
Line[{{1/4,3/4},{-1/4,3/4}}],
BSplineCurve[{{-1/4,3/4},{-1,3/4},{-1,0}},SplineWeights->{1,1/Sqrt[2],1}],
BSplineCurve[{{-1,0},{-1,-3/4},{-(1/4),-3/4}},SplineWeights->{1,1/Sqrt[2],1}],
Line[{{-1/4,-3/4},{1/4,-3/4}}]
};
Graphics[{{Red,segments},Lighter[Purple,3/4],FilledCurve[alef[segments]]}]


produces

as desired.

• slightly shorter: aleph = MapAt[Rest, #, {2 ;;, 1}]& – kglr Nov 1 '20 at 14:57
• Indeed, that works, too. Thanks. – robjohn Nov 1 '20 at 15:03
• @kglr: I have incorporated your comment into my answer (with attribution). – robjohn Nov 1 '20 at 18:13