Skip to content

Releases: ianmackenzie/elm-geometry

3.3.0

17 Feb 17:53
Compare
Choose a tag to compare

This release of elm-geometry adds a new Rectangle3d module and type. That's it, that's the release =)

3.2.0

07 Feb 23:55
Compare
Choose a tag to compare

elm-geometry 3.2 is out! This release brings a handful of small but useful new features requested (and in many cases contributed) by the community:

  • Arc2d.midpoint, Arc3d.midpoint and EllipticalArc2d.midpoint for getting midpoints of circular and elliptical arcs (see #115, thanks @w0rm!)
  • Polygon2d.centroid for getting the centroid (center of mass) of a polygon (see #117, thanks @w0rm!)
  • Axis2d.throughPoints and Axis3d.throughPoints for constructing axes that pass through two given points (thanks @MartinSStewart for the request)
  • Several functions for getting point coordinates and vector/direction components as tuples, bringing back some feature parity with previous versions of elm-geometry (thanks @MartinSStewart for the request):
    • Point2d.coordinates, Point2d.coordinatesIn
    • Point3d.coordinates, Point3d.coordinatesIn
    • Vector2d.components, Vector3d.components
    • Direction2d.components, Direction3d.components
  • Several functions for doing more sophisticated and/or convenient type-safe arithmetic on vectors:
    • Vector2d.half, Vector2d.twice as convenient shortand for Vector2d.scaleBy
    • Vector2d.sum to get the sum of a list of vectors
    • Vector2d.product and Vector2d.times to multiply a vector by a scalar Quantity (analogous to Quantity.times)
    • Vector2d.over and Vector2d.over_ to divide a vector by a scalar Quantity (analogous to Quantity.over and Quantity.over_)
    • plus all the same functions for Vector3d

3.1.0

03 Dec 23:52
Compare
Choose a tag to compare

This release adds a new Cylinder3d type and corresponding module, for representing/manipulating/querying 3D cylinders.

3.0.0

22 Nov 06:28
Compare
Choose a tag to compare

elm-geometry 3.0 is out! This release brings some small but breaking changes from the interim 2.0 release, and should hopefully now be stable for the foreseeable future.

Block3d

elm-geometry now has a Block3d type which is basically a 3D version of Rectangle2d, useful for defining rectangular blocks in 3D. Unlike a BoundingBox3d, a Block3d does not have to be axis-aligned - it can be arbitrarily rotated, mirrored and otherwise transformed).

Bounding box constructor changes

A few bounding box related functions have been moved around/renamed for clarity/discoverability, with the end result that they're actually a bit closer to where they used to be in elm-geometry 1.x. Specifically:

  • Most of the existing bounding box hull functions have been renamed to aggregate; for example BoundingBox2d.hull3 is now BoundingBox2d.aggregate3.
  • The existing BoundingBox2d.hull2 and BoundingBox3d.hull2 functions have been renamed to union (not strictly the correct term mathematically speaking, but convenient).
  • Most of the existing point hull functions have been moved to the corresponding bounding box modules; for example Point3d.hull is now BoundingBox3d.hull (with the same signature).
  • The existing Point2d.hull2 and BoundingBox3d.hull2 have been moved/renamed to BoundingBox2d.from and BoundingBox3d.from (matching how things worked in 1.x).

Basically, all functions that construct bounding boxes are now in bounding box modules. In addition, hullOfN and aggregateOfN functions have been added that basically act as combinations of hullOf/hullN and aggregateOf/aggregateN respectively.

See #107 for relevant discussion - thanks @MartinSStewart!

Miscellaneous

  • The Arc.SweptAngle has been renamed to simply SweptAngle for simplicity (didn't seem like that was likely to cause too many module name conflicts).
  • There is now an Axis3d.intersectionWithPlane function to find the intersection of an axis and a plane in 3D.
  • The Circle2d and Sphere3d modules now have some convenient atPoint and atOrigin constructors.
  • The LineSegment2d and LineSegment3d modules now have fromPointAndVector constructors as suggested in #110.
  • The type signatures of Frame2d.atOrigin and Frame3d.atOrigin have been loosened slightly to match Frame2d.atPoint Point2d.origin and Frame3d.atPoint Point3d.atOrigin; this was a case where the initial design was a bit too strict about coordinate system tracking and prevented some perfectly valid code from compiling. Thanks @unsoundscapes for some useful feedback here!
  • Rectangle2d.withAxes has been renamed back to Rectangle2d.centeredOn; I've gone back and forth on this one but I think centeredOn is more descriptive and readable.

Full diff

I think all the changes in 3.0 have been discussed above, but I may have missed something! Here's what elm diff reported for elm-geometry 3.0 relative to 2.0:

---- ADDED MODULES - MINOR ----

    Block3d
    SweptAngle


---- REMOVED MODULES - MAJOR ----

    Arc.SweptAngle


---- Axis3d - MINOR ----

    Added:
        intersectionWithPlane :
            Geometry.Types.Plane3d units coordinates
            -> Axis3d.Axis3d units coordinates
            -> Maybe.Maybe (Point3d.Point3d units coordinates)


---- BoundingBox2d - MAJOR ----

    Added:
        aggregate :
            BoundingBox2d.BoundingBox2d units coordinates
            -> List.List (BoundingBox2d.BoundingBox2d units coordinates)
            -> BoundingBox2d.BoundingBox2d units coordinates
        aggregate3 :
            BoundingBox2d.BoundingBox2d units coordinates
            -> BoundingBox2d.BoundingBox2d units coordinates
            -> BoundingBox2d.BoundingBox2d units coordinates
            -> BoundingBox2d.BoundingBox2d units coordinates
        aggregateN :
            List.List (BoundingBox2d.BoundingBox2d units coordinates)
            -> Maybe.Maybe (BoundingBox2d.BoundingBox2d units coordinates)
        aggregateOf :
            (a -> BoundingBox2d.BoundingBox2d units coordinates)
            -> a
            -> List.List a
            -> BoundingBox2d.BoundingBox2d units coordinates
        aggregateOfN :
            (a -> BoundingBox2d.BoundingBox2d units coordinates)
            -> List.List a
            -> Maybe.Maybe (BoundingBox2d.BoundingBox2d units coordinates)
        from :
            Point2d.Point2d units coordinates
            -> Point2d.Point2d units coordinates
            -> BoundingBox2d.BoundingBox2d units coordinates
        hullOfN :
            (a -> Point2d.Point2d units coordinates)
            -> List.List a
            -> Maybe.Maybe (BoundingBox2d.BoundingBox2d units coordinates)
        union :
            BoundingBox2d.BoundingBox2d units coordinates
            -> BoundingBox2d.BoundingBox2d units coordinates
            -> BoundingBox2d.BoundingBox2d units coordinates

    Removed:
        hull2 :
            BoundingBox2d units coordinates
            -> BoundingBox2d units coordinates
            -> BoundingBox2d units coordinates

    Changed:
      - hull :
            BoundingBox2d units coordinates
            -> List (BoundingBox2d units coordinates)
            -> BoundingBox2d units coordinates
      + hull :
            Point2d.Point2d units coordinates
            -> List.List (Point2d.Point2d units coordinates)
            -> BoundingBox2d.BoundingBox2d units coordinates

      - hull3 :
            BoundingBox2d units coordinates
            -> BoundingBox2d units coordinates
            -> BoundingBox2d units coordinates
            -> BoundingBox2d units coordinates
      + hull3 :
            Point2d.Point2d units coordinates
            -> Point2d.Point2d units coordinates
            -> Point2d.Point2d units coordinates
            -> BoundingBox2d.BoundingBox2d units coordinates

      - hullN :
            List (BoundingBox2d units coordinates)
            -> Maybe (BoundingBox2d units coordinates)
      + hullN :
            List.List (Point2d.Point2d units coordinates)
            -> Maybe.Maybe (BoundingBox2d.BoundingBox2d units coordinates)

      - hullOf :
            (a -> BoundingBox2d units coordinates)
            -> a
            -> List a
            -> BoundingBox2d units coordinates
      + hullOf :
            (a -> Point2d.Point2d units coordinates)
            -> a
            -> List.List a
            -> BoundingBox2d.BoundingBox2d units coordinates



---- BoundingBox3d - MAJOR ----

    Added:
        aggregate :
            BoundingBox3d.BoundingBox3d units coordinates
            -> List.List (BoundingBox3d.BoundingBox3d units coordinates)
            -> BoundingBox3d.BoundingBox3d units coordinates
        aggregate3 :
            BoundingBox3d.BoundingBox3d units coordinates
            -> BoundingBox3d.BoundingBox3d units coordinates
            -> BoundingBox3d.BoundingBox3d units coordinates
            -> BoundingBox3d.BoundingBox3d units coordinates
        aggregateN :
            List.List (BoundingBox3d.BoundingBox3d units coordinates)
            -> Maybe.Maybe (BoundingBox3d.BoundingBox3d units coordinates)
        aggregateOf :
            (a -> BoundingBox3d.BoundingBox3d units coordinates)
            -> a
            -> List.List a
            -> BoundingBox3d.BoundingBox3d units coordinates
        aggregateOfN :
            (a -> BoundingBox3d.BoundingBox3d units coordinates)
            -> List.List a
            -> Maybe.Maybe (BoundingBox3d.BoundingBox3d units coordinates)
        from :
            Point3d.Point3d units coordinates
            -> Point3d.Point3d units coordinates
            -> BoundingBox3d.BoundingBox3d units coordinates
        hullOfN :
            (a -> Point3d.Point3d units coordinates)
            -> List.List a
            -> Maybe.Maybe (BoundingBox3d.BoundingBox3d units coordinates)
        union :
            BoundingBox3d.BoundingBox3d units coordinates
            -> BoundingBox3d.BoundingBox3d units coordinates
            -> BoundingBox3d.BoundingBox3d units coordinates

    Removed:
        hull2 :
            BoundingBox3d units coordinates
            -> BoundingBox3d units coordinates
            -> BoundingBox3d units coordinates

    Changed:
      - hull :
            BoundingBox3d units coordinates
            -> List (BoundingBox3d units coordinates)
            -> BoundingBox3d units coordinates
      + hull :
            Point3d.Point3d units coordinates
            -> List.List (Point3d.Point3d units coordinates)
            -> BoundingBox3d.BoundingBox3d units coordinates

      - hull3 :
            BoundingBox3d units coordinates
            -> BoundingBox3d units coordinates
            -> BoundingBox3d units coordinates
            -> BoundingBox3d units coordinates
      + hull3 :
            Point3d.Point3d units coordinates
            -> Point3d.Point3d units coordinates
            -> Point3d.Point3d units coordinates
            -> BoundingBox3d.BoundingBox3d units coordinates

      - hullN :
            List (BoundingBox3d units coordinates)
            -> Maybe (BoundingBox3d units coordinates)
      + hullN :
            List.List (Point3d.Point3d units coordinates)
            -> Maybe.Maybe (BoundingBox3d.BoundingBox3d units coordinates)

      - hullOf :
            (a -> BoundingBox3d units coordinates)
            -> a
            -> List a
            -> BoundingBox3d units coordinates
      + hullOf :
            (a -> Point3d.Point3d units coordinates)
            -> a
            -> List.List a
            -> BoundingBox3d.BoundingBox3d units coordinates



---- Circle2d - MINOR ----

    Added:
        atOrigin :
            Quantity.Quantity Basics.Float units
            -> Circle2d.C...
Read more

2.0.0

12 Sep 03:06
Compare
Choose a tag to compare

elm-geometry 2.0 is out! For now this is a bit of a preview release; it's feature-complete and I'm pretty happy with the API, but the docs are not yet as complete as they should be. Feel free to try out the new version, but please let me (@ianmackenzie) know on Slack if you find any gaps or errors in the documentation.

Units and coordinate systems

The main new feature in this release (that will break all your code) is the addition of type-level tracking of the units and coordinate systems associated with different values. Most types now have two type parameters; where in elm-geometry 1.x you'd have a plain

Point3d

in elm-geometry 2.x you'll have a

Point3d units coordinates

instead. Individual coordinate/component values are now Quantity values from elm-units, so you might construct points using

Point2d.xy (Pixels.pixels 100) (Pixels.pixels 200)
Point3d.xyz (Length.meters 2) (Length.meters 3) (Length.meters 1)

or, to be more concise (especially when prototyping/experimenting),

Point2d.pixels 100 200
Point3d.meters 2 3 1

Unlike units, which can generally be inferred to be a concrete type from usage, the coordinates type variable is largely "optional": you can choose to annotate particular values as being in particular coordinate systems (and then the compiler can check whether those annotations are consistent with each other), but you can also often ignore it entirely. The only time a coordinates type is actually required is if you need to add a type annotation to a top level value; and in the simple case, where everything is in one coordinate system, you can do something like

type World
    = World

and then annotate points as (for example)

myPoint : Point3d Meters World
myPoint =
    Point3d.meters 5 10 20

If different values have different units and coordinate systems, we of course need ways to convert between them! The Frame2d, Frame3d and SketchPlane3d types now both exist in a particular coordinate system and define their own coordinate system; this means that functions like placeIn and relativeTo now let you convert between different coordinate systems. (They always did, really, now it's just official at the type level.) For units, there are new at and at_ functions in each module that act just like the corresponding functions at and at_ functions in the Quantity module.

Streamlined primitive construction

In addition to the new units/coordinate system, point/vector/direction construction has been streamlined and made significantly more flexible. For example, here are several ways of defining the same point:

-- Most flexible, useful with Json.Decode.map3 etc.
Point3d.xyz (Length.meters 2) (Length.meters 3) (Length.meters 1)

-- Very succinct, especially handy for experimentation/REPL use
Point3d.meters 2 3 1

-- Flexible conversion from plain Elm data types
Point3d.fromRecord Length.meters { x = 2, y = 3, z = 1 }
Point3d.fromTuple Length.meters ( 2, 3, 1 )
Point3d.fromRecord Length.centimeters { x = 200, y = 300, z = 100 }
Point3d.fromTuple Length.centimeters ( 200, 300, 100 )

-- Zero-overhead construction from records that happen
-- to be in 'base' units (meters or pixels)
Point3d.fromMeters { x = 2, y = 3, z = 1 }

It's also much easier to construct directions:

Direction2d.fromAngle (Angle.degrees 30) -- like before
Direction2d.degrees 30 -- handy shorthand

Direction3d.xy (Angle.degrees 45) -- an angle on the XY plane
Direction3d.yz (Angle.degrees 60) -- an angle on the YZ plane

Direction3d.xyZ (Angle.degrees 45) -- azimuth in XY...
    (Angle.degrees 30) -- ...and elevation towards +Z


Direction3d.zxY (Angle.degrees 45) -- azimuth in ZX...
    (Angle.degrees 30) -- ...and elevation towards +Y

Miscellaneous additions

While I was working on units and coordinate systems in the coordinate-systems branch, several new features were contributed by various members of the community; these have all now been merged into 2.0:

  • expandBy and offsetBy functions for BoundingBox2d and BoundingBox3d to allow expanding or contracting bounding boxes. (The former only ever expands, and returns a plain bounding box; the latter also supports contraction but returns a Maybe to handle the case where the bounding box contracts to nothing.) Thanks @MrL1605!
  • midpoint functions for QuadraticSpline2d, QuadraticSpline3d, CubicSpline2d and CubicSpline3d as a convenient way to find the point half way along a spline by arc length (not parameter value). Thanks @MrL1605!
  • centroid functions for Polyline2d and Polyline3d to find the center of mass of a polyline. Thanks @davcamer!
  • Polygon2d.contains to check whether a given point is contained within a given polygon. Thanks @gampleman!
  • Circle2d.intersectsBoundingBox to check whether a circle touches a bounding box. Thanks @SebastianKG!

More efficient/type-safe centroid and hull functions

I took the opportunity to fix one thing that's been bothering me a bit: functions that compute the centroid or hull (bounding box) of a list of points have to return a Maybe so that they can return Nothing if given an empty list. There are now a few different versions of those functions for different use cases, for example:

-- If you definitely have at least one point (no Maybe!)
Point3d.centroid : 
    Point3d units coordinates -- first point
    -> List (Point3d units coordinates) -- rest
    -> Point3d units coordinates

-- If you have exactly 3 points (efficient - no list allocation/manipulation)
Point3d.centroid3 :
    Point3d units coordinates
    -> Point3d units coordinates
    -> Point3d units coordinates
    -> Point3d units coordinates

-- If you have an unknown number of points (as before)
Point3d.centroidN :
    List (Point3d units coordinates)
    -> Maybe (Point3d units coordinates)

This means that if you've pattern-matched a list and already have it in head/tail form, then you don't have to deal with the (impossible) Nothing case.

As part of this change a few hull-type functions were renamed/moved around; for example BoundingBox2d.from is now Point2d.hull2 and BoundingBox2d.containingPoints is now Point2d.hullN.

Simplified bounding box overlap/separation checks

The existing BoundingBox#d.overlappingBy and BoundingBox#d.separatedBy functions are very flexible but difficult to describe, inefficient to implement and generally a bit confusing. They've now been simplified to what are hopefully the most common/useful cases:

BoundingBox2d.overlappingByAtLeast :
    Quantity Float units
    -> BoundingBox2d units coordinates
    -> BoundingBox2d units coordinates
    -> Bool

BoundingBox2d.separatedByAtLeast:
    Quantity Float units
    -> BoundingBox2d units coordinates
    -> BoundingBox2d units coordinates
    -> Bool

(Plus similar for BoundingBox3d.)

There are several more smaller changes and probably some I'm forgetting; check out the API docs and reach out if you're confused/curious about anything!

1.3.0

18 Apr 00:58
Compare
Choose a tag to compare
1.3.0 Pre-release
Pre-release

elm-geometry 1.3.0 is out! This is a minor release with a few nice additions, all contributed by the community:

  • expandBy and offsetBy functions for BoundingBox2d and BoundingBox3d to allow expanding or contracting bounding boxes. (The former only ever expands, and returns a plain bounding box; the latter also supports contraction but returns a Maybe to handle the case where the bounding box contracts to nothing.) Thanks @MrL1605!
  • midpoint functions for QuadraticSpline2d, QuadraticSpline3d, CubicSpline2d and CubicSpline3d as a convenient way to find the point half way along a spline by arc length (not parameter value). Thanks @MrL1605!
  • centroid functions for Polyline2d and Polyline3d to find the center of mass of a polyline. Thanks @davcamer!
  • Polygon2d.contains to check whether a given point is contained within a given polygon. Thanks @gampleman!

1.2.1

20 Sep 01:42
Compare
Choose a tag to compare

Add images to Voronoi/Delaunay docs

1.2.0

20 Sep 01:32
Compare
Choose a tag to compare

elm-geometry 1.2 is out! The major feature in this release is support for generation of Delaunay triangulations and Voronoi diagrams:

Voronoi diagram

Huge thanks to @folkertdev for all his work on this in #58!

Additionally, this release adds a Rectangle2d module for directly creating and manipulating rectangles (which, unlike the similar BoundingBox2d, can be rotated/mirrored etc.).

Last (and least, really), a few functions have been renamed - the old names have been kept for now but will likely be removed in the next major release:

1.1 1.2
BoundingBox2d.centroid BoundingBox2d.centerPoint
BoundingBox3d.centroid BoundingBox3d.centerPoint
Frame2d.xy Frame2d.atOrigin
Frame3d.xyz Frame3d.atOrigin

Finally, shoutout to @dmy for the excellent elm-doc-preview tool. I used it quite a bit when finishing up the documentation for this release, and it made things really pleasant. Highly recommended to other package authors to let you quickly iterate on your documentation before release!

1.1.0

22 Aug 02:15
Compare
Choose a tag to compare

First release for Elm 0.19! This is almost identical to the last published release for Elm 0.18 (1.0.2), but contains a handful of new functions:

  • LineSegment2d.intersectionWithAxis : Axis2d -> LineSegment2d -> Maybe Point2d
  • Point2d.centroid : List Point2d -> Maybe Point2d
  • Point3d.centroid : List Point3d -> Maybe Point3d

1.0.0

01 Jul 03:42
Compare
Choose a tag to compare

elm-geometry 1.0 is out! This is effectively version 3.0 of opensolid/geometry, with quite a few changes since opensolid/geometry 2.1.0:

  • Renamed all modules to remove OpenSolid. prefix
  • Removed JSON encoding/decoding modules and split Interval and Scalar modules out into their own packages
  • Added new ParameterValue and SweptAngle modules
  • Refactored curve evaluation functions such as pointOn
  • Improved Polygon2d functionality significantly
  • Streamlined constructors for many geometric types
  • Updated spline constructors
  • Added several new useful functions
  • Renamed several functions
  • Removed a few deprecated functions

In general, to update code written for opensolid/geometry to use elm-geometry, first remove the OpenSolid. prefix from all imports. Then, follow the compiler error messages and look within these release notes to see how individual function calls should be updated (for example, search for Vector3d.with to see that it has been replaced by Vector3d.withLength). That said, these release notes are not exhaustive, so if you run into anything not covered here, come ask in the #geometry channel on the Elm Slack!

Renamed modules

Modules no longer include an OpenSolid. prefix, so imports are now more succinct:

-- opensolid/geometry
import OpenSolid.Point3d as Point3d exposing (Point3d)

-- elm-geometry
import Point3d exposing (Point3d)

The module names in elm-geometry have been chosen to try to avoid conflicts with other published Elm packages, but if you do find a conflict, please file an issue.

Removed modules

opensolid/geometry included built-in support for JSON encoding and decoding, but after some discussion on Discourse and Slack it was decided to remove this functionality from elm-geometry. It may reappear later, but likely as a separate package.

A couple of modules have been moved out into their own packages:

New modules

Curve.ParameterValue

Evaluating points on curves now requires you to pass a ParameterValue instead of a plain Float. A ParameterValue is effectively a Float that is guaranteed to be between 0 and 1, which adds some extra type safety to curve evaluation. The ParameterValue module includes functions for constructing ParameterValues from plain Floats, and several convenient functions for constructing ranges of evenly-spaced parameter values.

Arc.SweptAngle

The Arc.SweptAngle module replaces the duplicate SweptAngle types and values that existed the Arc2d and EllipticalArc2d modules:

-- opensolid/geometry
Arc2d.smallPositive : Arc2d.SweptAngle
EllipticalArc2d.smallPositive : EllipticalArc2d.SweptAngle

-- elm-geometry
SweptAngle.smallPositive : SweptAngle.SweptAngle

Refactored curve evaluation

A very common and important operation in elm-geometry is evaluating positions and tangent directions at various points along a curve (such as an arc or a cubic Bezier spline). A few related changes have been made in elm-geometry to make this more type-safe and explicit. First of all, as mentioned above, curve evaluation functions now generally take ParameterValue arguments instead of plain Float ones:

-- opensolid/geometry
CubicSpline3d.pointOn : CubicSpline3d -> Float -> Point3d

-- elm-geometry
CubicSpline3d.pointOn : CubicSpline3d -> ParameterValue -> Point3d

You can construct a ParameterValue using ParameterValue.clamped, but for the common case of evaluating many points at once there are also functions for directly generating a list of parameter values and then evaluating curves at those values. For example, to get 11 points on a cubic spline (including the start and end points), you could use

pointsOnSpline =
    cubicSpline |> CubicSpline3d.pointsAt (ParameterValue.steps 10)

Note that if you take 1 step along a curve you get 2 points (start and end), if you take 2 steps along a curve you get 3 points (start, middle and end), if you take 10 steps along a curve you get 11 points, etc.

Curves now also support evaluating tangent directions and 'samples' (point/tangent direction pairs), but only if the curve is nondegenerate. If a curve is actually just a single point (e.g. a spline where all control points are equal), then the curve is said to be degenerate and the tangent direction is undefined. All curve types (Arc3d, EllipticalArc2d, CubicSpline3d etc.) now have functions like

CubicSpline3d.nondegenerate : CubicSpline3d -> Result Point3d CubicSpline3d.Nondegenerate

to attempt to convert a curve to its guaranteed-nondegenerate form. If the curve is in fact degenerate (consists of a single point) then you will get an Err with that point instead. Once you have a Nondegenerate value, you can then use it to evaluate tangent directions and samples:

CubicSpline3d.tangentDirection : CubicSpline3d.Nondegenerate -> ParameterValue -> Direction3d
CubicSpline3d.sample : CubicSpline3d.Nondegenerate -> ParameterValue -> ( Point3d, Direction3d )

For example, if you wanted to animate along a cubic spline, you might call CubicSpline3d.nondegenerate first to try to get a nondegenerate curve. If that succeeds, then use the resulting Nondegenerate value to evaluate points and tangent directions along the curve. If it fails, then you should apply special-case logic - perhaps drop that curve entirely from your animation path, or display your animated object in a fixed position (using the point returned in the Err case) with some default orientation.

Polygon2d improvements

The Polygon2d module has been improved significantly in this release. First of all, polygons can now have holes, which necessitated several changes:

  • Polygon2d.fromVertices has been renamed to Polygon2d.singleLoop to emphasize that it constructs a polygon without holes
  • Polygon2d.with has been added to construct polygons with holes
  • Polygon2d.outerLoop and Polygon2d.innerLoops accessors have been added
  • Polygon2d.clockwiseArea, Polygon2d.counterclockwiseArea and Polygon2d.mapVertices have been removed since they could not easily be made to work with the new polygon representation

Polygon2d.convexHull has been added to compute the convex hull of a set of points (thanks @gampleman!):

Convex hull of a set of points

Finally, polygons can now be triangulated, so you can define a polygon just by specifying its outline (and holes, if it has any)

Polygon with hole

then use Polygon2d.triangulate to turn that polygon into a list of triangles:

Polygon triangulation

This is primarily useful for WebGL rendering but has many other applications.

Streamlined constructors

There was a significant push in this release to streamline geometry construction functions. Many functions that previously took a single record argument have been reworked to take multiple arguments instead, with the following goals in mind:

  • Reduce verbosity
  • Support partial application/work well with |>, map, map2 etc.
  • Try not to sacrifice too much clarity/explicitness

For example,

-- opensolid/geometry
Vector2d.with { length = 3, direction = Direction2d.x }

can now be written as

-- elm-geometry
Vector2d.withLength 3 Direction2d.x

Similarly,

-- opensolid/geometry
Axis2d.with { originPoint = point, direction = direction }

can now be written as

-- elm-geometry
Axis2d.through point direction
-- OR
Axis2d.withDirection direction point

Having both versions allow you to do different things with partial application:

-- A list of axes in different directions all passing through the same origin point
List.map (Axis2d.through point) directions

-- A list of parallel axes (all having the same direction) through different points
List.map (Axis2d.withDirection direction) points

Many other constructors have been similarly updated:

opensolid/geometry elm-geometry
Arc2d.fromEndpoints Arc2d.withRadius: Float -> SweptAngle -> Point2d -> Point2d -> Maybe Arc2d
Arc2d.with Arc2d.sweptAround: Point2d -> Float -> Point2d -> Arc2d
Arc3d.around Arc3d.sweptAround: Axis3d -> Float -> Point3d -> Arc3d
Axis3d.with Axis3d.through: Point3d -> Direction3d -> Axis3d
Axis3d.withDirection: Direction3d -> Point3d -> Axis3d
Circle2d.with Circle2d.withRadius: Float -> Point2d -> Circle2d
Circle3d.with Circle3d.withRadius: Float -> Direction3d -> Point3d -> Circle3d
Direction3d.with Direction3d.fromAzimuthAndElevation: ( Float, Float ) -> Direction3d
Frame2d.with Frame2d.withXDirection: Direction2d -> Point2d -> Frame2d
Frame2d.withYDirection: Direction2d -> Point2d -> Frame2d
Frame3d.with Frame3d.withXDirection: Direction3d -> Point3d -> Frame3d
Frame3d.withYDirection: Direction3d -> Point3d -> Frame3d
`Frame3...
Read more