1.0.0
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
andScalar
modules out into their own packages - Added new
ParameterValue
andSweptAngle
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:
OpenSolid.Interval
has been split out intoianmackenzie/elm-interval
OpenSolid.Scalar
has been split out intoianmackenzie/elm-float-extra
, with theInterval
-related functions moved toelm-interval
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 ParameterValue
s from plain Float
s, 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 toPolygon2d.singleLoop
to emphasize that it constructs a polygon without holesPolygon2d.with
has been added to construct polygons with holesPolygon2d.outerLoop
andPolygon2d.innerLoops
accessors have been addedPolygon2d.clockwiseArea
,Polygon2d.counterclockwiseArea
andPolygon2d.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!):
Finally, polygons can now be triangulated, so you can define a polygon just by specifying its outline (and holes, if it has any)
then use Polygon2d.triangulate
to turn that polygon into a list of triangles:
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 Frame3d.withZDirection: Direction3d -> Point3d -> Frame3d |
Plane3d.with |
Plane3d.through: Point3d -> Direction3d -> Plane3d Plane3d.withNormalDirection: Direction3d -> Point3d -> Plane3d |
SketchPlane3d.with |
SketchPlane3d.withNormalDirection: Direction3d -> Point3d -> SketchPlane3d |
Sphere3d.with |
Sphere3d.withRadius: Float -> Point3d -> Sphere3d |
Vector3d.with |
Vector3d.withLength : Float -> Direction3d -> Vector3d |
Updated spline constructors
The quadratic and cubic spline constructors previously known as fromControlPoints
have been updated to avoid the use of large tuples, and generally be consistent with constructors in other modules:
-- opensolid/geometry
QuadraticSpline2d.fromControlPoints ( p1, p2, p3 )
CubicSpline3d.fromControlPoints ( p1, p2, p3, p4 )
-- elm-geometry
QuadraticSpline2d.with
{ startPoint = p1
, controlPoint = p2
, endPoint = p3
}
CubicSpline3d.with
{ startPoint = p1
, startControlPoint = p2
, endControlPoint = p3
, endPoint = p4
}
Similarly, CubicSpline#d.hermite
has been replaced by fromEndpoints
:
-- opensolid/geometry
CubicSpline3d.hermite ( p1, v1 ) ( p2, v2 )
-- elm-geometry
CubicSpline3d.fromEndpoints
{ startPoint = p1
, startDerivative = v1
, endPoint = p2
, endDerivative = v2
}
New functions
As a small but very useful change, all modules with translateBy
functions now also have translateIn
functions; for example,
triangle |> Triangle3d.translateIn Direction3d.y 5
means "translate triangle
in the Y direction by 5 units".
Arc2d.from
has been added as a new and convenient way to construct arcs from a start point, end point and swept angle:
cornerArc =
Arc2d.from startPoint endPoint (degrees 90)
A new version of Arc2d.with
has also been added:
semicircle =
Arc2d.with
{ centerPoint = Point2d.origin
, radius = 3
, startAngle = degrees 0
, sweptAngle = degrees 180
}
Circle2d.sweptAround
has been added to construct a circle by sweeping one point around a center point:
circle =
Circle2d.sweptAround centerPoint pointOnCircle
Bounding boxes can now be constructed for splines:
CubicSpline2d.boundingBox : CubicSpline2d -> BoundingBox2d
CubicSpline3d.boundingBox : CubicSpline3d -> BoundingBox3d
QuadraticSpline2d.boundingBox : QuadraticSpline2d -> BoundingBox2d
QuadraticSpline3d.boundingBox : QuadraticSpline3d -> BoundingBox3d
It's now possible to compute the second derivative for cubic splines as well as quadratic ones:
CubicSpline2d.secondDerivative : CubicSpline2d -> ParameterValue -> Vector2d
CubicSpline3d.secondDerivative : CubicSpline3d -> ParameterValue -> Vector3d
If you have an arc length parameterized curve, you can now recover the original curve from it:
CubicSpline2d.fromArcLengthParameterized : CubicSpline2d.ArcLengthParameterized -> CubicSpline2d
CubicSpline3d.fromArcLengthParameterized : CubicSpline3d.ArcLengthParameterized -> CubicSpline3d
EllipticalArc2d.fromArcLengthParameterized : EllipticalArc2d.ArcLengthParameterized -> EllipticalArc2d
QuadraticSpline2d.fromArcLengthParameterized : QuadraticSpline2d.ArcLengthParameterized -> QuadraticSpline2d
QuadraticSpline3d.fromArcLengthParameterized : QuadraticSpline3d.ArcLengthParameterized -> QuadraticSpline3d
Frame2d.atCoordinates
has been added as a convenient shorthand for Frame2d.atPoint
+Point2d.fromCoordinates
, and similar for Frame3d
:
Frame2d.atCoordinates : ( Float, Float ) -> Frame2d
Frame3d.atCoordinates : ( Float, Float, Float ) -> Frame3d
Point2d
values can now be constructed directly from their polar coordinates with respect to a particular Frame2d
:
Point2d.fromPolarCoordinatesIn : Frame2d -> ( Float, Float ) -> Point2d
Finally, like Direction2d
, Vector2d
values can now also be conveniently (and efficiently) rotated 90 degrees clockwise or counterclockwise:
Vector2d.rotateClockwise : Vector2d -> Vector2d
Vector3d.rotateCounterclockwise : Vector2d -> Vector2d
Renamed functions
Several functions have been renamed to try to be more descriptive, read better in pipelines, reduce circular dependencies between modules, or be more consistent with how other functions are named:
opensolid/geometry |
elm-geometry |
---|---|
Axis2d.flip |
Axis2d.reverse |
Axis3d.flip |
Axis3d.reverse |
BoundingBox2d.hullOf |
BoundingBox2d.aggregate |
BoundingBox3d.hullOf |
BoundingBox3d.aggregate |
BoundingBox2d.with |
BoundingBox2d.fromExtrema |
BoundingBox3d.with |
BoundingBox3d.fromExtrema |
Circle3d.around |
Circle3d.sweptAround |
Direction2d.angle |
Direction2d.toAngle |
Direction2d.flip |
Direction2d.reverse |
Direction3d.flip |
Direction3d.reverse |
Frame2d.flipX |
Frame2d.reverseX |
Frame2d.flipY |
Frame2d.reverseY |
Frame3d.flipX |
Frame3d.reverseX |
Frame3d.flipY |
Frame3d.reverseY |
Frame3d.flipZ |
Frame3d.reverseZ |
LineSegment2d.normalDirection |
LineSegment2d.perpendicularDirection |
LineSegment3d.normalDirection |
LineSegment3d.perpendicularDirection |
Plane3d.flip |
Plane3d.reverseNormal |
Point2d.hull |
BoundingBox2d.from |
Point3d.hull |
BoundingBox3d.from |
Point2d.hullOf |
BoundingBox2d.containingPoints |
Point3d.hullOf |
BoundingBox3d.containingPoints |
Point2d.in_ |
Point2d.fromCoordinatesIn |
Point3d.in_ |
Point2d.fromCoordinatesIn |
SketchPlane3d.flipX |
SketchPlane3d.reverseX |
SketchPlane3d.flipY |
SketchPlane3d.reverseY |
SketchPlane3d.plane |
SketchPlane3d.toPlane |
Vector2d.flip |
Vector2d.reverse |
Vector3d.flip |
Vector3d.reverse |
Removed functions
A few functions have been removed in this release, but each one was simply a deprecated alias for a better-named replacement:
Removed | Replacement |
---|---|
BoundingBox2d.overlaps |
BoundingBox2d.intersects |
BoundingBox3d.overlaps |
BoundingBox3d.intersects |
Point2d.distanceAlong |
Point2d.signedDistanceAlong |
Point3d.distanceAlong |
Point3d.signedDistanceAlong |