Page MenuHome

Accurate NURBS circle and sphere
ClosedPublic

Authored by Laurynas Duburas (laurynas) on Jun 21 2021, 10:17 PM.

Details

Summary

Patch rewrites NURBS knot generation by adding support for CU_NURB_BEZIER | CU_NURB_CYCLIC. In other cases resulting knot doesn't change.
This cyclic Bezier knot is used to create math accurate "Nurbs Circle", "Nurbs Cylinder". Same to "Nurbs Sphere" and "Nurbs Torus" by using rewritten function to fix spin function.

Attached blend file demonstrates changes the patch brings. There are four groups of objects in particular color each. Blue ones are Nurbs created with patched Blender version, red ones are Nurbs created in unpatched, greys are same as red in addition converted to mesh(they are hidden in Outliner). Also there are pure Bezier curves for comparing them with Nurbs CU_NURB_BEZIER mimicking Bezier handles(intended to watch from top view). Bezier circle result differs greatly, because old code ignores CU_NURB_BEZIER flag, if CU_NURB_CYCLIC is set.


In result patch fixes accuracy of NURBS circle. Can be checked by comparing with mesh circle.
Also fixes tessellation spacing differences in circular NURBS, more easily observable are NURBS cylinder and sphere.

These together with geometrical deviation from ideal primitives were causing seam like effect on primitives:

Diff Detail

Repository
rB Blender
Branch
nurbs_circle (branched from master)
Build Status
Buildable 19816
Build 19816: arc lint + arc unit

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes
Laurynas Duburas (laurynas) requested review of this revision.Jun 21 2021, 10:17 PM

Fixes seams on NURBS sphere

Laurynas Duburas (laurynas) retitled this revision from Accurate NURBS circle to Accurate NURBS circle and sphere.Jun 26 2021, 1:31 PM
Laurynas Duburas (laurynas) edited the summary of this revision. (Show Details)

Sphere is fixed in ed_editnurb_spin rather than in ED_curve_add_nurbs_primitive by half hardcoded control points.
This way torus is also fixed.

Circle
The knot vector and weights are taken from literature. To represent a full circle with NURBS usually 90 degree arch representation is found at first. This gives a 2nd degree curve with 2D + w control points {{1,0, 1}, {1, 1, sqrt(2)/2}}, {0, 1, 1}}. Then this arch is repeated four times to construct a full circle and these arches are connected with knots {0, 0, 0, ¼, ¼, ½,½, ¾,¾, 1, 1, 1}. Inner knots are repeated for arches to connect at control points.
Circular Arcs and Circles
I couldn’t find literature giving NURBS scheme without double knots, even found someone suggesting that its not possible
NURBS circle without all the double knots?
I think the current Blender “circle” came from the wish to have evenly spaced knots.
This finishes the explanation of the knot vector defined in BKE_create_circle_knots and nu->orderu decreased to 3 ( degree + 1).

Sphere
In editcurve_add.c at line 427 a similar knot vector is created but only for a half circle. Only two 90 degree aches connected so only one double knot. Previously there was the CU_NURB_BEZIER flag and it would, but I wanted to follow logic and keep consistency with a circle(CU_NURB_BEZIER cannot be used for a circle as the flag cannot be used together with CU_NURB_CYCLIC file curve.c line 1270). Afterwards this 180 arch is spinned in 360 with editcurve.c function ed_editnurb_spin. As it needs to create a circular shape and had same weight as for a circle I used the same logic. The weird thing might be 2.0 / M_SQRT2, it is inverted sqrd(2)/2 to make every second weight 1. This is an old function’s structure and mechanics. I just put the right number.

source/blender/blenkernel/intern/curve.c
1302 ↗(On Diff #38794)

I'm not sure it makes sense to have this function since it's basically just a wrapper around memcpy.

Even if it saves a few lines of code, it sort of just adds to the complexity.

source/blender/editors/curve/editcurve_add.c
336

I think you could just embed the circle knots directly in this function, how does that sound?

Function BKE_create_knots_from removed

source/blender/editors/curve/editcurve_add.c
336

BKE_create_circle_knots is call in editcurve.c so this would lead to copy pasting.
There is another alternative to fix makeknots to support CU_NURB_CYCLIC | CU_NURB_BEZIER.
Hardcoded circleKnots array is same thing as CU_NURB_BEZIER just with cyclic tail.
But I'm afraid of side effects as makeknots is used everywhere.

Just find out this patch worsens Nurbs circle in Geometry Nodes

That's probably because spline_from_dna_nurbs doesn't copy the original knot vector. It could do that, or maybe it makes more sense to improve the implementation of the bezier knot mode in the cyclic case. Not sure.

In some sense both options.
Fastest and most reasonable at a time is to support cyclic with bezier in both old and new nurbs code.
But in a future knots should be recalculated only on user action like change of cyclic or endpoint mode. Because tools like "cut"
rely on custom knot vectors. For ex. curve on [0 0 0 1 2 3 3 3] cut at 1.25 gives two curves on [0 0 0 1 1.25 1.25 1.25] and [1.25 1.25 1.25 2 3 3 3].
With this approach copying should happen.

Added support of cyclic bezier NURBS curves in old and new code.

Though all knot generation code looks unreasonably messy and asks for rewrite.

Function calcknots() and method NURBSpline::calculate_knots() rewritten to support cyclic Bezier.
Also less messy now.

Statement in comment: "bezier with 4 orderu needs 5 points" is wrong same as code.
Maybe someone mixed up term "order" with "degree". 4th degree Bezier would need 5 points, same as cubic (3rd degree) curve needs 4 points. If we speak about order, then for Bezier order = points.
Clear bug can be observed and in released version. If we create NURBS curve of 4 points and change knot type to Bezier, then geometry disappears as it insists on 5 points. After adding 5th point we see geometry clamps only to lasting 4 points as only those were necessary:

Refactoring of calcknots affects only CU_NURB_BEZIER case output. For output instead of oldU = [0, 0, 0, 0.5, 0.5, 1, 1, 1.5, 1.5, 2, 2, 2] now newU=[0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 4] is returned. Resulting NURBS curve/surface are equivalent only defined on different intervals. It doesn’t affect curve/surface shape or normals as from math point of view knots magnitude is not important, important are ratios of knot spacing and they are preserved, for ex. (oldU[3] - oldU[2]) / (oldU[5] - oldU[2]) = 0.5 / 1 = 0.5 = (newU[3] - newU[2]) / (newU[5] - newU[2]) = 1 / 2.
From code quality side magic numbers like 0.34, 0.6 and ⅓ are removed.
Another difference and benefit that now it supports not only orders 3 and 4, just Blender interface in the main branch restricts other orders.

Hans Goudey (HooglyBoogly) requested changes to this revision.Sep 14 2021, 10:59 PM

Thanks for continuing work on this, and sorry for the delay with review. Mostly comments about code style right now.

source/blender/blenkernel/intern/curve.c
4787–4788 ↗(On Diff #41824)

Comment style (start with capital, end with period) https://wiki.blender.org/wiki/Style_Guide/C_Cpp#Comments

source/blender/blenkernel/intern/spline_nurbs.cc
199

Use 0.0f since its' a float

200
203

Best to stick to snake_case for variable names, no need to break convention here.
Also, declare as const

220

Let's keep this as a for loop, there's no reason for it to be a while loop.

Same with below, the logic is harder to follow as a while loop.

This revision now requires changes to proceed.Sep 14 2021, 10:59 PM
Laurynas Duburas (laurynas) marked 7 inline comments as done.Sep 15 2021, 8:56 PM
Hans Goudey (HooglyBoogly) requested changes to this revision.Dec 15 2021, 11:41 PM

Thanks a lot for your patience. The reason I've delayed review here is that I really don't know enough of the details about NURBS math to be confident accepting this, since it looks like a rather large change to knots generation at this point.
So, I think the patch description and code should focus on increasing the confidence it can give a reader about the change. That means:

  • Proper before and after comparisons for the many cases. The patch improves the cases in the screenshots above, but what about all of the combinations of control point counts, orders, and cyclic/bezier/endpoint toggles?
    • Ideally the patch would include a file used for before/after comparisons, with a clear separation between what changes are intentional, and how everything else is unchanged.
  • The patch description should give an overview of the most important parts of the change.
  • The code can become clearer with more comments, const variables, and a more "standard" for loops (as in-- looks like the surrounding area in the code)

Also, I have some more questions:

  • Does this patch change the meaning of Nurb.orderu, Nurb.orderv, or NURBSpline.order_?
  • I see now CU_NURB_ENDPOINT | CU_NURB_BEZIER are often used in combination.
  • I'm a little surprised to see a change to the "Spin" operator here. How is that related?
  • What's the reasoning for giving the NURBS circle primitive CU_NURB_BEZIER knots? As a user I wouldn't necessarily expect the default NURBS circle to have a knot vector I thought was used to emulate Bezier curves.

I hope this doesn't seem like too much. With these things addressed, I think the general change sounds like an improvement, based on what you've said in comments above.

source/blender/blenkernel/intern/spline_nurbs.cc
199–202

Declare variables const wherever possible, structure for loops like for (const int XX : IndexRange(YY))

source/blender/editors/curve/editcurve.c
4992

What is the goal of adding CU_NURB_BEZIER here?

This revision now requires changes to proceed.Dec 15 2021, 11:41 PM
Laurynas Duburas (laurynas) marked an inline comment as done.Dec 18 2021, 1:35 PM

Does this patch change the meaning of Nurb.orderu, Nurb.orderv, or NURBSpline.order_?

No. Don't know why you suspect that.

I see now CU_NURB_ENDPOINT | CU_NURB_BEZIER are often used in combination.

I'm canceling this one. In Blender Nurbs of CU_NURB_BEZIER type has one leading control point and it looked like a bug. It has no visual effect. Intention was for Bezier (green in screenshot) with handles:


to have Nurbs (red in screenshot) with controls points:

But while answering this review I understood that extra cp was on purpose as Blender Bezier has leading and trailing handles. So I fixed knot function to act same as it was before in CU_NURB_BEZIER case:

I'm a little surprised to see a change to the "Spin" operator here. How is that related?

It is used to generate "Nurbs Sphere" and "Nurbs Torus". I had versions rewriting Sphere code with a couple constant arrays, but finally was forced to fix spin. More on this in code comment.

What's the reasoning for giving the NURBS circle primitive CU_NURB_BEZIER knots? As a user I wouldn't necessarily expect the default NURBS circle to have a knot vector I thought was used to emulate Bezier curves.

And it does. Circle is expressed as four Bezier arcs connecting into single curve. It is impossible to create accurate circle with uniform knots. In fact for user there should be no such thing as knot vector type,
knot vector should be generated once and left as is for user to modify with tools like "Refine/Insert Control Point" or "Slice/Split" and others.

source/blender/editors/curve/editcurve.c
4992

Fixing ed_editnurb_spin fixes "Nurbs Sphere" and "Nurbs Torus" to math accurate, not circle like.
It is impossible to create math circle with evenly spaced knots. My first tries where with hardcoded knot vector
{0, 0, 0, 0.25, 0.25, 0.5, 0.5, 0.75, 0.75, 1, 1, 1, 1.25, 1.25}, but in Blender knot vector is recalculated constantly (it is a wrong doing, but latter on this).
So had to use generation scheme and got lucky that one of existing is CU_NURB_BEZIER. In literature NURBS circles are constructed by connecting four Bezier arcs.

I'm not sure why, but I'm having trouble applying this patch. It seems to start a bit differently than others here:

Index: source/blender/blenkernel/intern/curve.cc
===================================================================

vs. the regular format:

diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py
--- a/release/scripts/startup/nodeitems_builtins.py
+++ b/release/scripts/startup/nodeitems_builtins.py

Could you try creating the diff with git diff master -U100 > ~/temp.diff?

Trying different diff format

Pasting patch instead of uploading file

To get right diff format

Tests pass for me locally. And the change looks good to me now. (I just added a few very small comments inline). Thanks for your patience and your work here.

source/blender/blenkernel/intern/curve.cc
36–37

Add this next to the other BLI includes.

1174

Add a * at the beginning of this line for consistent comment formatting.

1182–1187

The 0 is unnecessary at the beginning of the IndexRange constructor.

source/blender/editors/curve/editcurve.c
4992

Could you add part of this to a comment above CU_NURB_CYCLIC | CU_NURB_BEZIER?

Maybe something like

/* It is impossible to create math circle with evenly spaced knots.
 * In literature NURBS circles are constructed by connecting four Bezier arcs. */
This revision is now accepted and ready to land.Jan 6 2022, 5:11 PM
Laurynas Duburas (laurynas) marked 5 inline comments as done.Jan 6 2022, 10:59 PM

Hey guys, I want to join your discussion, hopefully you don't mind. I'm going to digest/test this patch for the next couple of days and leave my thoughts here.

source/blender/editors/curve/editcurve.c
4968–4969

@Laurynas Duburas (laurynas) addressing your statements in backwards order:

  • "In literature NURBS circles are constructed by connecting four Bezier arcs."

Can you provide the references to the papers/books supporting this? One of the big selling points for NURBS is that they can represent arbitrary conic sections (including circles)[1] in opposite to Bezier curves which can only approximate such geometry[2].

  • "It is impossible to create math circle with evenly spaced knots."

Why is this a problem? Is this strictly related to Blender implementation of NURBS?

[1] https://www.wikiwand.com/en/Non-uniform_rational_B-spline#/Example:_a_circle
[2] https://pomax.github.io/bezierinfo/#circles

@Piotr Makal (pmakal)

Can you provide the references to the papers/books supporting this? One of the big selling points for NURBS is that they can represent arbitrary conic sections (including circles)[1] in opposite to Bezier curves which can only approximate such geometry[2].

In my statement I skipped word Rational before Bezier. The thing is that if you repeat knot degree(order - 1) times each segment of resulting curve is Rational Bezier curve and this
is what Blender does for Bezier(Rational Bezier to be precise) knot generation. Your link [1] as my first link in comments above shows same the knot structure.
You are right about conics as selling point. But to dive more into details circles can be expressed with several separate Rational Bezier curves and NURBS allows to express that structure as single curve.

Why is this a problem?

I don’t see it as a problem. As far as I understood Hans, he didn’t like that for Circle/Sphere primitives you get Bezier checkbox checked.

Is this strictly related to Blender implementation of NURBS?

At some point yes, because Blender implementation doesn’t allow user to edit knot vector(not necessarily directly, through tools also), it constantly regenerates it for a given scheme (Endpoint, Bezier,..).
So it is not possible to create for ex. NURBS Circle with fixed knot like in your [1] without Bezier checked. Knot will be rewritten and lost for ex. after toggling Cyclic couple of times.

Hopefully tomorrow I will wrap up my testing. Just want to point out some smaller things:

  • I've noticed that for 4th order, 8-point NURBS curve with Bezier + Cyclic setup, the knot vector is: [0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4, 5, 5]. Looking at the code it doesn't seem intentional to only double the "3" here?
  • Referring to what you wrote previously:

Is this strictly related to Blender implementation of NURBS?

At some point yes, because Blender implementation doesn’t allow user to edit knot vector(not necessarily directly, through tools also), it constantly regenerates it for a given scheme (Endpoint, Bezier,..).
So it is not possible to create for ex. NURBS Circle with fixed knot like in your [1] without Bezier checked. Knot will be rewritten and lost for ex. after toggling Cyclic couple of times.

Good point

source/blender/blenkernel/intern/spline_nurbs.cc
209

Should be just IndexRange(tail) - remove 0, probably some left-over from previous changes. Same in curve.cc file.

Looking at the code it doesn't seem intentional to only double the "3" here?

It is intentional because loop starts with first "3" (knot[8] counting from 0). The input is not proper. Last Bezier segment needs one more control point (to end at cp[0])). Now you get situation where 3rd degree Bezier curve has only three control points given.
So function does most of what is possible, creates correct cyclic knot vector.

In my opinion in UI status bar user could be informed that more points are needed to complete Bezier segment. And visually could:

  1. stay as it is. Last segment not a Bezier curve (if there is not enough cp's)
  2. only show cycle from Bezier's having enough control points. And other control points would float around until all necessary for next Bezier are added or lost on tool change.

But this is out of this patch scope

  • redundant 0 in IndexRange call removed
Laurynas Duburas (laurynas) marked an inline comment as done.Jan 12 2022, 10:23 PM
Laurynas Duburas (laurynas) added inline comments.
source/blender/blenkernel/intern/spline_nurbs.cc
209

Thanks. Somehow missed it.

I've tested the patch on my side and I'm ok with the changes. I understand the reasoning behind it, as the "cyclic" implementation for NURBS in Blender comes with some challenges. Going for Bezier knots interpretation for circular shapes seems a little bit hacky, but then again, it produces better results then previous approach as shown by @Laurynas Duburas (laurynas).
Some final thoughts for consideration:

  • I would suggest to investigate the current limitation for Bezier knots mode in NURBS that currently only works for 3rd and 4th order curves. I think that after this change it should be possible to remove this restriction and the value range should be between 2-6 (like for every other curve). While testing the patch I've removed this restriction in BKE_nurb_order_clamp_u function in curve.cc file, but when switching to i.e. 5th order a curve wasn't displaying itself in the viewport although it seem to have proper knot vector (when checked with debugger). Again, this is something to think about.
  • If this patch going to be pushed I would split it into two separate commits: (1) change in knot vector calculations in calcknots and NURBSpline::calculate_knots functions and (2) change in ed_editnurb_spin and ed_editnurb_spin functions.
  • I would update the description for this patch, clarifying that knot vector in 3rd order NURBS curve with Bezier option turned on (without cyclic) is changed in comparison to previous calculations, although it doesn't change the curve shape itself. For example the knot vector for 3rd order, 8-point NURBS curve before the change was: (0, 0, 0, 1, 1, 2, 2, 3, 3, 3, 3); and after this patch is: (0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5) - again with Bezier flag and without Cyclic flag.
source/blender/editors/curve/editcurve.c
4968–4969

I dunno but this comment is just rubbing me in the wrong way :) I would propose to either remove it or change it to something like this:
It is challenging to create a good approximation of a circle with uniform knots vector (which is forced in Blender for cyclic NURBS curves). Here a NURBS circle is constructed by connecting four Bezier arcs.
And also if it's here, why it's not in editcurve_add.c file where similar changes are made?

Laurynas Duburas (laurynas) marked an inline comment as done.Jan 14 2022, 10:56 PM

I would suggest to investigate the current limitation for Bezier knots mode in NURBS that currently only works for 3rd and 4th order curves. I think that after this change it should be possible to remove this restriction and the value range should be between 2-6 (like for every other curve).

I'm preparing followup patch removing some restrictions including order on NURBS Bezier, but need to lay down concepts

While testing the patch I've removed this restriction in BKE_nurb_order_clamp_u function in curve.cc file

Would have missed this one.

source/blender/editors/curve/editcurve.c
4968–4969

First of all I would never comment code voluntarily.
Green check mark I needed badly and it seamed easiest way to get closer.
Now see no reason to copy paste into editcurve_add.c :)

For the commit I took the relevant parts of the commit message and edited them a bit, I hope that's okay.

source/blender/editors/curve/editcurve.c
4968–4969

I used the comment from @Piotr Makal (pmakal) here.

About the "I would never comment code voluntarily" comment, it's so important for Blender because that's how knowledge is passed between developers over the years.
Just think how much easier this change would have been if the code it modified accurately reflected the assumptions it made in comments.