ianmackenzie
released this
Assets
2
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
IntervalandScalarmodules out into their own packages - Added new
ParameterValueandSweptAnglemodules - Refactored curve evaluation functions such as
pointOn - Improved
Polygon2dfunctionality 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.Intervalhas been split out intoianmackenzie/elm-intervalOpenSolid.Scalarhas 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 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.SweptAngleRefactored 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 -> Point3dYou 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.Nondegenerateto 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.fromVerticeshas been renamed toPolygon2d.singleLoopto emphasize that it constructs a polygon without holesPolygon2d.withhas been added to construct polygons with holesPolygon2d.outerLoopandPolygon2d.innerLoopsaccessors have been addedPolygon2d.clockwiseArea,Polygon2d.counterclockwiseAreaandPolygon2d.mapVerticeshave 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,map2etc. - 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.xSimilarly,
-- opensolid/geometry
Axis2d.with { originPoint = point, direction = direction }can now be written as
-- elm-geometry
Axis2d.through point direction
-- OR
Axis2d.withDirection direction pointHaving 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) pointsMany 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 -> Axis3dAxis3d.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 -> Frame2dFrame2d.withYDirection: Direction2d -> Point2d -> Frame2d |
Frame3d.with |
Frame3d.withXDirection: Direction3d -> Point3d -> Frame3dFrame3d.withYDirection: Direction3d -> Point3d -> Frame3dFrame3d.withZDirection: Direction3d -> Point3d -> Frame3d |
Plane3d.with |
Plane3d.through: Point3d -> Direction3d -> Plane3dPlane3d.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 5means "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 pointOnCircleBounding boxes can now be constructed for splines:
CubicSpline2d.boundingBox : CubicSpline2d -> BoundingBox2d
CubicSpline3d.boundingBox : CubicSpline3d -> BoundingBox3d
QuadraticSpline2d.boundingBox : QuadraticSpline2d -> BoundingBox2d
QuadraticSpline3d.boundingBox : QuadraticSpline3d -> BoundingBox3dIt'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 -> Vector3dIf 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 -> QuadraticSpline3dFrame2d.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 ) -> Frame3dPoint2d values can now be constructed directly from their polar coordinates with respect to a particular Frame2d:
Point2d.fromPolarCoordinatesIn : Frame2d -> ( Float, Float ) -> Point2dFinally, like Direction2d, Vector2d values can now also be conveniently (and efficiently) rotated 90 degrees clockwise or counterclockwise:
Vector2d.rotateClockwise : Vector2d -> Vector2d
Vector3d.rotateCounterclockwise : Vector2d -> Vector2dRenamed 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 |