-
-
Notifications
You must be signed in to change notification settings - Fork 118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Consider revamping GlobalCurve
#1079
Comments
Updated the list of action items in the issue description. Forgot one item (bypassing |
In the issue description, I said under Problem 1 that the redundancy I described is not that problematic. Turns out, it's more problematic than previously known! There are some bugs and limitations:
And I'm sure there's more. These are just some that I came across while working on this. If there was any doubt that this issue addresses a real problem, it's gone now! |
It's just a single, simple test so far. I had planned to add more, but it turns out that they aren't easy to write, due to bugs and inconsistencies: #1079 (comment) This is going to be much easier to fix, once #1079 is addressed, so I'm just going ahead with that instead.
Curve approximation is now bypassing This is about as far as I can go here, without addressing #1021 first, so that's what I'm going to do next. |
Now that this issue is unblocked and I've restarted the work here, I'm finding more code that relies on I'm sure there are more places in the code that need to be adjusted. I'll keep this issue updated as I find and address these. |
I have a local branch that makes the suggested change to Equality vs identityDifferent pieces of code can create different objects at different times, and those object can end up equal. But, having been created in different places at different times, they don't have the same identity. This didn't make a difference for a while, because there simply was no way to track identity. Now, we have a centralized store, and equal objects can still end up being stored at different memory locations within that store, hence being not identical. This is actually happening right now with Why identity is importantThis might sound like a problem, but it's actually good, and an intended side effect of the centralized storage concept. Creating non-identical but equal objects works most of the time because floating-point arithmetic, despite all its problems, is still deterministic1. However, it's still something I would like to avoid, because it could hide buggy code. Recomputing equal objects in multiple places could work some of the time, given specific input, but then could stop working other times, given different input. We can avoid this completely, by just never recomputing equal objects, and instead providing a So before I can merge my local branch, I need to change all that code that computes equal but non-identical A slight change of plansWhile working on this, I realized that the original plan can be simplified. By changing Instead, we can simplify:
This might also be confusing (why can't I compare this using Footnotes
|
This constructor is a bit problematic, for multiple reasons: - It fulfills a role that is better fitted for the builder API. - It takes away control over the `GlobalVertex` construction from the caller. That second point is a problem, due to the work that needs to happen to address #1079. This commit doesn't solve that, but the builder API provides a better framework to do so than this constructor did.
This constructor is a bit problematic, for multiple reasons: - It fulfills a role that is better fitted for the builder API. - It takes away control over the `GlobalVertex` construction from the caller. That second point is a problem, due to the work that needs to happen to address #1079. This commit doesn't solve that, but the builder API provides a better framework to do so than this constructor did.
I'm still working on addressing the new problems brought up by the distinction between object identity and object equality that I talked about in my previous comment here. So far, I've identified two main sources of these problems:
Both construct many duplicate objects that end up being equal, which will no longer be good enough in the new world where object identity is a thing. So far, I've worked on the builder API, but I actually believe that both APIs have the same solution: Introduce the notion of partial objects, parts of which can be provided by the caller or computed ad-hoc, depending on the use case. This required a lot of thinking and experimentation, but I think my recent pull requests on that topic (#1133, #1134, #1135) are honing in on a design that should work. |
That last round of pull requests has addressed lots of issues in the builder API. I can't be sure that all of them have been solved, as my local branch still has a million failing tests, but at least the ones I knew about are done now. I've refocused my attention on the transform API. I have rough plan of attack involving the new partial object API, and I'm currently looking into that. I've also identified another major source of problems: The unit tests. By design, they construct objects that are equal (but having been constructed by the unit test, of course not identical) to those constructed by the code under test, and compare them for equality. With the revamped I'm not sure how to solve that yet. Ideas I have include rewriting the unit tests to follow a different style, or introducing some test-only object wrapper that can compare objects using equality where available and ignoring identity. |
Turns out that addressing the (known) problems in the transform API wasn't much of a problem, now that the partial object API is in place. As of #1152, I have a local branch that implements everything that's missing to finish this issue, runs without errors, and is down to 5 failing unit tests. Fixing those failing unit tests is the next thing I'm going to work on. I'm not sure whether my idea for an identity-ignoring test-only object wrapper will work out, but given the low number of failing unit tests, it's probably possible to just refactor all of them, so they no longer trigger any object identity issues. I'm hopeful that I can wrap up this issue before the end of the week, and return to #993! |
Curve
is defined in surface (2D) space whileGlobalCurve
is defined in global (3D) space. Hence,Curve
is used to convert curve (1D) coordinates to surface coordinates, whileGlobalCurve
is used to convert those curve coordinates to global coordinates.This is working well enough for now, but I don't think it will be a suitable solution going forward.
Problem 1: Redundancy between
Curve
/GlobalCurve
GlobalCurve
isn't actually needed for for generating global coordinates.Curve
references theSurface
it is defined on, and thatSurface
can be used to convert the surface coordinates thatCurve
generates into global coordinates, bypassingGlobalCurve
.So, as far as computing approximations goes, we don't need
GlobalCurve
. This isn't really much of a problem, but it's supporting evidence for what is going to be my proposal.Interlude 1: Let's cache curve approximations
If you've been following carefully and have sufficient knowledge of the Fornjot kernel, you might note that what I've just said wouldn't actually work. It could result in slightly different 3D points being generated for
Curve
approximations that are supposed to be the same, thus resulting in an invalid mesh being generated. (If you actually noted that, please send more pull requests. I can use the help. 😄)Right now,
Curve
approximation goes throughGlobalCurve
, and we're being really careful about approximations of should-be-identicalGlobalCurve
s producing the same points (even if thoseGlobalCurve
s point in different directions). If we started going through both theCurve
and theSurface
to create a 3D approximation, then no amount of being really careful would prevent invalid meshes.This "being really careful" business is probably already a warning sign that there's a deeper problem not being addressed. So let's just remove the need for being careful in the first place. When approximating a
Curve
, let's cache the approximation, using theGlobalCurve
as a key.(Actually, we can only cache the curve-space and global-space points generated for the approximation. We'll need to re-compute the surface points per
Curve
, but that's just an implementation detail and not a problem.)This might have the side effect of saving redundant computations, leading to better performance. But right now, these re-computations don't seem to be a performance problem, so whatever.
Problem 2:
CurveKind
won't cut it going forwardBoth
Curve
andGlobalCurve
own aCurveKind
, which defines the actual geometry of the curve. To accommodate bothCurve
andGlobalCurve
,CurveKind
is generic over its dimension.Curve
owns aCurveKind<2>
,GlobalCurve
owns aCurveKind<3>
.There are already some subtleties with how the two referenced
CurveKind
s need to match each other, which adds to the redundancy problems. For example, theGlobalCurve
'sCurveKind<3>
might be a circle, but the correspondingCurve
'sCurveKind<2>
might be a line, if the curve is defined on a round surface.This is going to get worse. Once we support helical sweeps, we need to represent helical curves, and a
CurveKind<2>::Helix
doesn't make any sense. We need to splitCurveKind
into separate enums for 2D and 3D.Interlude 2: Let's split
CurveKind
intoSurfacePath
/GlobalPath
CurveKind
is one of those names that I've never been happy with. But you gotta do what you gotta do to make progress. Can't just sit there for a few months, staring at the screen, trying to come up with better names for things.Fortunately, that kind of problem tends to take care of itself over time. As it did in this case. Let's just call it a "path". That nomenclature is already used in the context of sweeps. We can have
SurfacePath
for 2D paths, andGlobalPath
for 3D paths.This is a change that can be done right now, with
Curve
owning aSurfacePath
andGlobalCurve
owning aGlobalPath
. And I think this would serve us well going forward.Problem 3:
GlobalCurve
needs to be normalized?Sometimes we need to know whether two
Curve
s are the same curve in 3D space. Actually, this is not completely true. What we do need to know though, is whether twoHalfEdge
s are the same edge in 3D space (#993), but we can't have the one without the other (#993 (comment)).In that linked comment, I've been musing about normalizing
GlobalCurve
s, or even normalizing allCurve
s/GlobalCurve
s, but as I noted there, neither is satisfactory. Those solutions seem error-prone.But if problems 1 and 2 were addressed, and thus
Curve
/GlobalCurve
were no longer redundant, then normalizingGlobalCurve
would no longer be necessary, becauseGlobalCurve
would no longer contain any geometry that required normalization.What would
GlobalCurve
contain though? Let's look at that next.Proposal
Now might be as good a time as any to mention my first thoughts on this topic. My conclusion there isn't valid (we still need
GlobalCurve
, as per the requirements laid out in problem 3), and I wasn't fully clear on the need for 3D paths (we'd definitely need a struct to represent them, as per problem 2 and interlude 2). I'm just noting it here for the sake of completeness, and to provide some context on the thinking that lead to this issue.So, given I'm so much smarter now, having written all this, what do I believe
GlobalCurve
should be, going forward?Well, as noted previously, due to the redundancy with
Curve
and theSurface
it references,GlobalCurve
no longer needs to contain any geometry. But we do still need some way to know whether theGlobalCurve
s referenced by two differentCurve
s are supposed to be the same. And all we need for that is some kind of ID value, andGlobalCurve
would become a wrapper around that ID.I see this play out like this:
Curve
, instead of computing its associatedGlobalCurve
, we generate a unique ID and build theGlobalCurve
from that.Curve
that is known to be the same in global space as the originalCurve
in 3D space (when sweeping said originalCurve
, for example), we make the newCurve
reference the sameGlobalCurve
(just like we already do).Curve
s that are known to be the same in global space (Surface
/Surface
intersection testing, for example). Just create one newGlobalCurve
and make them both reference it (again, no change from what we do now).So the question is, how do we generate unique IDs for the new
GlobalCurve
? Two options, as far as I can see:While I'm not above employing a hack or two to make progress, hidden global state might be taking it a step too far. And centralized object storage is probably a good idea anyway, so maybe just do that first.
Conclusion
So in conclusion, I've laid out problems that make the current
Curve
/GlobalCurve
/CurveKind
structure unsuitable for the next batch of design challenges we'll be facing, and I've presented a proposal (along a few distinct-but-related sub-proposals) for addressing that.These are the action items resulting from that:
GlobalCurve
approximations #1082).GlobalCurve
in curve approximation (see Problem 1; addressed in BypassGlobalCurve
in curve approximation #1096).CurveKind
intoSurfacePath
/GlobalPath
(see Interlude 2; addressed in ReplaceCurveKind
withSurfacePath
/GlobalPath
#1081).GlobalCurve
s (see Implement lightweight, centralized object storage #1021; addressed in Implement centralized object storage #1108).GlobalCurve
in curve sweeping (addressed in BypassGlobalCurve
in sweep algorithm #1111)GlobalCurve
into a wrapper around a unique curve ID (see Proposal).Bonus points for giving the same "wrapper around an ID" treatment to
GlobalEdge
, which is redundant withHalfEdge
in similar ways thatGlobalCurve
is redundant withCurve
. As far as I can tell, this isn't causing any problems right now though, but once centralized object storage is in place, it could be a nice simplification.Unless anyone (maybe myself?) convinces me that a clever hack is the right solution to unblock the last item, I consider this issue blocked on #1021. Labeling https://github.com/hannobraun/Fornjot/labels/status%3A%20blocked.
The text was updated successfully, but these errors were encountered: