-
Notifications
You must be signed in to change notification settings - Fork 279
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
feat: Geometric Queries #1428
feat: Geometric Queries #1428
Conversation
± Registry diff
📊 PerformanceKeyNote that each bar component rounds up to the nearest 100ms, so each full bar is an overestimate by up to 400ms.
If a row has only one bar instead of four, that means it's not a trio and the bar just shows the total time spent for that example, again rounded up to the nearest 100ms. Data
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incredible, thanks for doing this! I'm still reviewing the actual code, but I was wondering if you could change some of the trio filenames so their extended pathnames aren't quite so long? E.g.:
current | suggested |
---|---|
geometric-queries/closest-point/test-closest-point-group |
geometric-queries/closest-point/test-group |
geometric-queries/closest-point/test-closest-point |
geometric-queries/closest-point/test |
geometric-queries/closest-silhouette-point/test-closest-silhouette-point |
geometric-queries/closest-silhouette-point/test |
geometric-queries/ray-intersect/test-ray-intersect-group |
geometric-queries/ray-intersect/test-group |
geometric-queries/ray-intersect/test-ray-intersect |
geometric-queries/ray-intersect/test |
geometric-queries/test-geometric-queries |
geometric-queries/test |
This just makes the keys easier to read in the autogenerated PR registry comments, by removing some redundancy, and reduces the width of that comment so it's readable without horizontal scrolling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another very minor comment after reviewing the easy parts of the code (I still need to look at your TypeScript and Style changes); I'm curious, is there a particular reason you put the variation both in a comment in the Substance program and also in the trio file?
@@ -0,0 +1,11 @@ | |||
-- variation: TopsailBoar92821 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason you write a variation here different from the one in the corresponding *.trio.json
file?
@samestep No great reason for putting the variations in both places—I thought maybe if the example gets divorced from the repo (e.g., someone is copy-paste-modifying it in their own local directory). But then, if they're changing it the variation doesn't really matter. So, I'll remove them from the Will also shorten the example names. |
from substance programs.
@samestep Done shortening names and removing variations from |
Thanks @keenancrane! Looks like you forgot to update |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great to me!
This PR adds support for several fundamental geometric queries across (almost) all SVG shapes, including aggregate shapes defined by a
Group
, and exposes them in Style. It also adds several tests and examples, and fixes errors in earlier implementations of geometric queries.All methods compute queries exactly, with the single exception of
closestPointEllipse
(which provides an extremely good numerical approximation).Style functions
The functionality of this PR is exposed through four Style functions (implemented in
Functions.ts
):rayIntersect(S,p,v)
— given a shapeS
, pointp
, and rayv
, finds the first intersection pointq
along the rayp+tv
. If there are no intersections, the pointp
is returned.rayIntersectNormal(S,p,v)
— returns the unit normal at the point of intersection computed byrayIntersect
, oriented consistently with the direction of the ray (i.e., such that the inner product of the normal with the ray direction is nonpositive).closestPoint(S,p)
— given a shapeS
and pointp
, returns a pointq
onS
closest top
. Ifq
is not unique, the choice of closest point is arbitrary—e.g., for a polygon, it will depend on the order of points in the input. Note however that this situation is rare/degenerate, since the set of query points with more than one closest point is a 1-dimensional subset of the 2-dimensional plane.closestSilhouettePoint(S,p)
— given a shapeS
and pointp
, returns the closest pointq
on the visibility silhouette ofS
relative top
(equivalently: the closest point at whichq-p
is orthogonal to the normaln
atq
). As withclosestPoint
, if there is more than one closest silhouette point (as in the case of a circle), an arbitrary choice is made. As withrayIntersect
, if there are no silhouette points relative top
, the return value isp
.Though these queries are exposed in a particular way in Style, their implementation can in future PRs be used in/generalized to other contexts. For instance, it would be no trouble to return a list of all ray-shape intersections, rather than just the closest one (especially since this list is already generated in the process of computing the closest intersection).
Tests and Examples
A fairly extensive set of examples are used to validate the implementation; several of these examples have also been included in the Gallery to help highlight the available functionality. Here's the list of examples; see their respective
README
s for more information:geometric-queries
geometric-queries/closest-point
geometric-queries/closest-silhouette-point
geometric-queries/ray-intersect
ray-tracing
The first four examples are essentially test cases for the core functionality; the final example (
ray-tracing
) is a more fleshed-out application example aimed at diagrams for rendering algorithms.Implementation
Each geometric query has a "catch all" function
queryShape
(wherequery
is one ofrayIntersect
,rayIntersectNormal
,closestPoint
, orclosestSilhouettePoint
), which provides some high-level logic---most importantly, querying groups by recursively callingqueryShape
on all children. "Medium-level" functions perform queries for individual shape type (queryCircle
,queryRectangle
, etc.). Finally, "low-level" functionsqueryCoords
perform queries common to multiple shapes (e.g., line-like shapes or ellipse-like shapes). Finally, there are Style-level functions, generally named justquery()
.Some queries may yield an empty result (e.g., there is no intersection with the given ray, or there is no silhouette point relative to a given query point). In this case, the low- and medium-level functions produce a point at infinity, using coordinates with the constant
Infinity
. This convention makes it easy to find the closest point, by iterating over candidates and taking the closest one (since points at infinity automatically produce a distance ofInfinity
); it also nicely avoids use of additional flags or types beyond standard numerical values. Style-level methods make the ultimate decision about how to return well-behaved output---in particular, if there are overall no results for a given query, these methods just return the query point (which is usually quite convenient and easy to handle in the context of Style programs).Ellipse closest point
The only query that is not exact is the closest point to an ellipse. Here, a necessary (but not sufficient) condition is that the segment between the query point
p
and the closest pointq
must be orthogonal to the tangent atq
, which amounts to solving a high-degree polynomial equation. Rather than use a general root finder, one can take advantage of an intelligent initial guess plus Newton's method. In particular, for an ellipse parameterized asf(t) := (a cos(t), b sin(t))
, a good initial guess for the closest point isatan2(a p_y, b p_x)
, wherep_x
,p_y
are the x- and y- coordinates of the query point. (This guess basically just computes the angle of the query point, accounting for the radii of the ellipse.) From here, Newton's method converges extremely quickly; even just one iteration works quite well. Moreover, one iteration of Newton's method amounts to a calculation not much longer than any other closest point query. To be conservative, we currently set the number of iterations to 2, which makes a small visual difference in some cases.Note that the Newton-based implementation supplants an earlier sampling-based implementation, which was far less efficient and did not in general produce correct results. Previously generated ellipse closest point tests had bogus reference values, and were removed (@wodeni said this was ok. 😉). Also, an earlier
closest-point
example, which was not in the registry, was no longer working, and was removed.Open and Future Issues
Queries beyond closest. Although the high-level "outer" methods (exposed to Style) currently return just the closest query point, it would be straightforward to return, e.g., the furthest point---or a list of all points. In the latter case, we would need a meaningful mechanism to use such a list within Style (or, we could use the list from the API---e.g., to draw all intersections along a ray as a collection of generated
Circle
s).Missing shapes. The most notable missing shape is
Path
, which can be comprised of straight segments, Bézier curves and circular/elliptical arcs. To handlePath
shapes in full generality, the main challenge is implementing geometric queries for the latter two (and especially Bézier curves, since arcs can more or less be handled by specializing circle/ellipse routines). It seems sensible for a future PR to focus exclusively on queries for segments of Bézier curves. Querying an entire path then amounts to the strategy already used forPolyline
andPolygon
, i.e., just iterate over constituent segments of the path.Refactoring shape-specific queries. Many of the routines currently take
Shape
types as arguments. We may want to refactor these methods to take just the raw data (e.g., a point and a radius rather than aCircle
), and also to expose these in Style so that users can use these queries without needing to construct a shape (which may not need to be drawn explicitly).