Skip to content
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

Merged
merged 71 commits into from
May 25, 2023
Merged

feat: Geometric Queries #1428

merged 71 commits into from
May 25, 2023

Conversation

keenancrane
Copy link
Collaborator

@keenancrane keenancrane commented May 24, 2023

image

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 shape S, point p, and ray v, finds the first intersection point q along the ray p+tv. If there are no intersections, the point p is returned.
  • rayIntersectNormal(S,p,v) — returns the unit normal at the point of intersection computed by rayIntersect, 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 shape S and point p, returns a point q on S closest to p. If q 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 shape S and point p, returns the closest point q on the visibility silhouette of S relative to p (equivalently: the closest point at which q-p is orthogonal to the normal n at q). As with closestPoint, if there is more than one closest silhouette point (as in the case of a circle), an arbitrary choice is made. As with rayIntersect, if there are no silhouette points relative to p, the return value is p.

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 READMEs for more information:

  1. geometric-queries
  2. geometric-queries/closest-point
  3. geometric-queries/closest-silhouette-point
  4. geometric-queries/ray-intersect
  5. 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 (where query is one of rayIntersect, rayIntersectNormal, closestPoint, or closestSilhouettePoint), which provides some high-level logic---most importantly, querying groups by recursively calling queryShape on all children. "Medium-level" functions perform queries for individual shape type (queryCircle, queryRectangle, etc.). Finally, "low-level" functions queryCoords perform queries common to multiple shapes (e.g., line-like shapes or ellipse-like shapes). Finally, there are Style-level functions, generally named just query().

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 of Infinity); 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 point q must be orthogonal to the tangent at q, 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 as f(t) := (a cos(t), b sin(t)), a good initial guess for the closest point is atan2(a p_y, b p_x), where p_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 Circles).

Missing shapes. The most notable missing shape is Path, which can be comprised of straight segments, Bézier curves and circular/elliptical arcs. To handle Path 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 for Polyline and Polygon, 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 a Circle), 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).

@github-actions
Copy link

github-actions bot commented May 25, 2023

± Registry diff

D	closest-point/test.svg
A	geometric-queries/closest-point/test-group.svg
A	geometric-queries/closest-point/test.svg
A	geometric-queries/closest-silhouette-point/test.svg
A	geometric-queries/ray-intersect/test-group.svg
A	geometric-queries/ray-intersect/test.svg
A	geometric-queries/test.svg
A	ray-tracing/bidirectional.svg
A	ray-tracing/next-event-estimation.svg
A	ray-tracing/path-trace.svg

📊 Performance

Key

Note that each bar component rounds up to the nearest 100ms, so each full bar is an overestimate by up to 400ms.

     0s   1s   2s   3s   4s   5s   6s   7s   8s
     |    |    |    |    |    |    |    |    |
name ▝▀▀▀▀▀▀▀▀▀▚▄▄▄▄▄▄▄▄▞▀▀▀▀▀▀▀▀▀▀▚▄▄▄▄▄▄▄▄▄▖
      compiling labeling optimizing rendering

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

                                                          0s   1s   2s   3s   4s   5s   6s   7s
                                                          |    |    |    |    |    |    |    |
set-theory-domain/tree-venn                               ▝▀▚▚
set-theory-domain/tree-tree                               ▝▀▞▖
set-theory-domain/tree-venn-3d                            ▝▚▚
group-theory/quaternion-multiplication-table              ▝▀▀▚▄▚
group-theory/quaternion-cayley-graph                      ▝▀▚▚
atoms-and-bonds/wet-floor                                 ▝▚▀▚
atoms-and-bonds/one-water-molecule                        ▝▞▖
set-theory-domain/continuousmap                           ▝▞▖
linear-algebra-domain/two-vectors-perp                    ▝▚▚
molecules/nitricacid-lewis                                ▝▀▀▚▀▀▖
exterior-algebra/vector-wedge                             ▝▚▚
shape-spec/all-shapes                                     ▝▚▞▄▄▄▖
shape-spec/arrowheads                                     ▝▞▖
graph-domain/textbook/sec1/fig1                           ▝▀▀▞▀▖
graph-domain/textbook/sec1/fig2                           ▝▀▀▀▞▀▖
graph-domain/textbook/sec1/fig3                           ▝▀▀▀▚▀▚
graph-domain/textbook/sec1/fig4                           ▝▀▀▀▀▞▀▀▖
graph-domain/textbook/sec1/fig5                           ▝▀▀▀▀▀▞▀▀▀▖
graph-domain/textbook/sec1/fig6                           ▝▀▀▀▀▀▀▀▀▀▀▀▀▀▚▀▀▀▀▀▀▀▀▀▀▀▀▀▀▖
graph-domain/textbook/sec1/fig7                           ▝▀▚▚
graph-domain/textbook/sec1/fig8a                          ▝▀▀▀▀▚▀▀▀▀▚
graph-domain/textbook/sec1/fig8b                          ▝▀▀▀▚▀▀▚
graph-domain/textbook/sec1/fig9                           ▝▀▀▚▀▖
graph-domain/textbook/sec1/fig10                          ▝▀▀▞▖
graph-domain/textbook/sec1/fig11                          ▝▀▀▀▀▞▀▖
graph-domain/textbook/sec1/fig12                          ▝▀▀▀▀▀▞▀▀▀▀▚
graph-domain/textbook/sec1/fig13                          ▝▀▀▀▞▀▚
graph-domain/textbook/sec2/fig3                           ▝▀▀▀▀▀▀▀▀▞▀▀▀▀▖
graph-domain/textbook/sec2/fig4                           ▝▀▀▀▀▚▀▀▀▚
graph-domain/textbook/sec2/fig5                           ▝▀▀▀▀▀▀▀▞▀▀▀▀▖
graph-domain/textbook/sec2/fig6                           ▝▀▀▀▀▀▀▀▀▚▀▀▀▀▀▀▖
graph-domain/textbook/sec2/fig9                           ▝▀▀▀▀▀▀▀▀▀▀▀▚▀▀▀▀▀▀▀▖
graph-domain/textbook/sec2/fig10a                         ▝▀▀▚▀▀▚
graph-domain/textbook/sec2/fig10b                         ▝▀▀▀▞▀▖
graph-domain/textbook/sec2/fig11a                         ▝▀▞▖
graph-domain/textbook/sec2/fig11b                         ▝▀▞▖
graph-domain/textbook/sec2/fig11c                         ▝▀▚▚
graph-domain/textbook/sec2/fig12                          ▝▀▚▚
graph-domain/textbook/sec2/fig13                          ▝▀▀▀▀▀▀▀▀▀▀▀▀▀▀▞▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▚
graph-domain/textbook/sec2/fig14                          ▝▀▀▀▞▚
graph-domain/textbook/sec2/fig16b                         ▝▀▀▞▖
geometry-domain/textbook_problems/c05p13                  ▝▀▀▞▖
geometry-domain/textbook_problems/c01p01                  ▝▀▚▚
geometry-domain/textbook_problems/c03p01                  ▝▀▚▚
geometry-domain/textbook_problems/c05p01                  ▝▀▚▚
geometry-domain/textbook_problems/ex                      ▝▀▀▚▀▖
triangle-mesh-3d/two-triangles                            ▝▚▚
random-sampling/test                                      ▝▀▀▀▚▚
geometry-domain/textbook_problems/c11p12                  ▝▀▀▞▖
word-cloud/example                                        ▝▀▞▚
geometry-domain/siggraph-teaser                           ▝▀▀▞▖
minkowski-tests/maze/non-convex                           ▝▚▚
lagrange-bases/lagrange-bases                             ▝▚▚
hypergraph/hypergraph                                     ▝▀▀▚▀▀▀▀▀▀▀▀▀▚
persistent-homology/persistent-homology                   ▝▀▀▞▀▀▀▀▀▀▀▀▀▄
walk-on-spheres/laplace-estimator                         ▝▀▄▚
walk-on-spheres/poisson-estimator                         ▝▀▚▀▖
walk-on-spheres/nested-estimator                          ▝▀▀▞▀▀▚
walk-on-spheres/offcenter-estimator                       ▝▀▞▀▖
shape-distance/points-around-star                         ▝▀▀▀▚▚
shape-distance/points-around-polyline                     ▝▀▀▞▖
shape-distance/points-around-line                         ▝▀▞▖
shape-distance/lines-around-rect                          ▝▚▚
fake-3d-linear-algebra/projection                         ▝▞▖
animation/center-shrink-circle                            ▝▞▖
structural-formula/molecules/caffeine                     ▝▀▀▞▚
mobius/mobius                                             ▝▞▖
molecules/glutamine                                       ▝▀▞▖
matrix-ops/tests/matrix-matrix-addition                   ▝▚▚
matrix-ops/tests/matrix-matrix-division-elementwise       ▝▚▚
matrix-ops/tests/matrix-matrix-multiplication-elementwise ▝▚▚
matrix-ops/tests/matrix-matrix-multiplication             ▝▚▚
matrix-ops/tests/matrix-matrix-subtraction                ▝▚▚
matrix-ops/tests/matrix-transpose                         ▝▚▚
matrix-ops/tests/matrix-vector-left-multiplication        ▝▚▚
matrix-ops/tests/matrix-vector-right-multiplication       ▝▚▚
matrix-ops/tests/scalar-vector-division                   ▝▚▚
matrix-ops/tests/scalar-vector-left-multiplication        ▝▚▚
matrix-ops/tests/scalar-vector-right-multiplication       ▝▚▚
matrix-ops/tests/vector-vector-addition                   ▝▚▚
matrix-ops/tests/vector-vector-division-elementwise       ▝▚▚
matrix-ops/tests/vector-vector-multiplication-elementwise ▝▀▞▖
matrix-ops/tests/vector-vector-outerproduct               ▝▀▞▖
matrix-ops/tests/vector-vector-subtraction                ▝▚▚
logic-circuit-domain/half-adder                           ▝▀▞▖
curve-examples/cubic-bezier                               ▝▚▀▀▖
triangle-mesh-2d/diagrams/cotan-formula                   ▝▀▚▀▖
triangle-mesh-2d/diagrams/concyclic-pair                  ▝▀▞▚
triangle-mesh-2d/diagrams/halfedge-mesh                   ▝▀▞▖
triangle-mesh-2d/diagrams/relative-orientation            ▝▀▞▖
triangle-mesh-2d/diagrams/triangle-centers                ▝▀▞▖
triangle-mesh-2d/diagrams/angle-equivalence               ▝▀▀▞▀▀▖
graph-domain/textbook/sec5/ex32                           ▝▀▀▀▀▀▚▀▀▀▀▀▀▀▖
curve-examples/open-elastic-curve                         ▝▀▚▀▚
curve-examples/closed-elastic-curve                       ▝▀▚▀▀▖
graph-domain/other-examples/arpanet                       ▝▀▀▀▀▀▀▀▀▞▀▀▀▀▀▀▀▀▀▖
graph-domain/other-examples/nyc-subway                    ▝▀▀▀▀▀▚▀▀▀▀▀▖
curve-examples/blobs                                      ▝▀▀▀▀▀▀▞▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▖
curve-examples/space-curves                               ▝▀▀▀▀▀▞▀▀▀▚
solid/example                                             ▐
geometric-queries/ray-intersect/test-group                ▝▀▀▀▀▀▀▀▀▚▚
ray-tracing/path-trace                                    ▝▀▀▞▖
ray-tracing/bidirectional                                 ▝▀▚▚
ray-tracing/next-event-estimation                         ▝▀▀▞▖
geometric-queries/test                                    ▝▀▞▖
geometric-queries/closest-point/test-group                ▝▚▚
geometric-queries/closest-point/test                      ▝▚▚
geometric-queries/closest-silhouette-point/test           ▝▚▚
geometric-queries/ray-intersect/test                      ▝▀▀▀▀▀▀▚▚▖

@keenancrane keenancrane marked this pull request as ready for review May 25, 2023 15:37
Copy link
Collaborator

@samestep samestep left a 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.

Copy link
Collaborator

@samestep samestep left a 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
Copy link
Collaborator

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?

@keenancrane
Copy link
Collaborator Author

@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 .substance files.

Will also shorten the example names.

@keenancrane
Copy link
Collaborator Author

@samestep Done shortening names and removing variations from .substance files. Let me know if you spot anything else.

@samestep
Copy link
Collaborator

@samestep Done shortening names and removing variations from .substance files. Let me know if you spot anything else.

Thanks @keenancrane! Looks like you forgot to update packages/examples/src/registry.json after changing those filenames; could you fix that file?

Copy link
Collaborator

@samestep samestep left a 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!

@keenancrane keenancrane merged commit 092063a into main May 25, 2023
10 checks passed
@keenancrane keenancrane deleted the closest-ray-hit branch May 25, 2023 20:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants