diff --git a/coords.scad b/coords.scad
index 2b7035d..19f3406 100644
--- a/coords.scad
+++ b/coords.scad
@@ -143,15 +143,21 @@ function xy_to_polar(x,y=undef) = let(
 
 
 // Function: project_plane()
-// Usage:
-//   xy = project_plane(point, a, b, c);
-//   xy = project_plane(point, [A,B,C]];
+// Usage: With 3 Points
+//   xyz = project_plane(point, a, b, c);
+// Usage: With Pointlist
+//   xyz = project_plane(point, POINTLIST);
+// Usage: With Plane Definition [A,B,C,D] Where Ax+By+Cz=D
+//   xyz = project_plane(point, PLANE);
 // Description:
-//   Given three points defining a plane, returns the projected planar [X,Y] coordinates of the
-//   closest point to a 3D `point`.  The origin of the planar coordinate system [0,0] will be at point
-//   `a`, and the Y+ axis direction will be towards point `b`.  This coordinate system can be useful
-//   in taking a set of nearly coplanar points, and converting them to a pure XY set of coordinates
-//   for manipulation, before convering them back to the original 3D plane.
+//   Converts the given 3D point from global coordinates to the 2D planar coordinates of the closest
+//   point on the plane.  This coordinate system can be useful in taking a set of nearly coplanar
+//   points, and converting them to a pure XY set of coordinates for manipulation, before converting
+//   them back to the original 3D plane.
+//   Can be called one of three ways:
+//   - Given three points, `a`, `b`, and `c`, the planar coordinate system will have `[0,0]` at point `a`, and the Y+ axis will be towards point `b`.
+//   - Given a list of points, finds three reasonably spaced non-collinear points in the list and uses them as points `a`, `b`, and `c` as above.
+//   - Given a plane definition `[A,B,C,D]` where `Ax+By+Cz=D`, the closest point on that plane to the global origin at `[0,0,0]` will be the planar coordinate origin `[0,0]`.
 // Arguments:
 //   point = The 3D point, or list of 3D points to project into the plane's 2D coordinate system.
 //   a = A 3D point that the plane passes through.  Used to define the plane.
@@ -164,8 +170,14 @@ function xy_to_polar(x,y=undef) = let(
 //   xy2 = project_plane(pt, [a,b,c]);
 function project_plane(point, a, b, c) =
 	is_undef(b) && is_undef(c) && is_list(a)? let(
-		indices = find_noncollinear_points(a)
-	) project_plane(point, a[indices[0]], a[indices[1]], a[indices[2]]) :
+		mat = is_vector(a,4)? plane_transform(a) :
+			assert(is_path(a) && len(a)>=3)
+			plane_transform(plane_from_points(a)),
+		pts = is_vector(point)? point2d(apply(mat,point)) :
+			is_path(point)? path2d(apply(mat,point)) :
+			is_region(point)? [for (x=point) path2d(apply(mat,x))] :
+			assert(false, "point must be a 3D point, path, or region.")
+	) pts :
 	assert(is_vector(a))
 	assert(is_vector(b))
 	assert(is_vector(c))
@@ -180,13 +192,18 @@ function project_plane(point, a, b, c) =
 
 
 // Function: lift_plane()
-// Usage:
+// Usage: With 3 Points
 //   xyz = lift_plane(point, a, b, c);
-//   xyz = lift_plane(point, [A,B,C]);
+// Usage: With Pointlist
+//   xyz = lift_plane(point, POINTLIST);
+// Usage: With Plane Definition [A,B,C,D] Where Ax+By+Cz=D
+//   xyz = lift_plane(point, PLANE);
 // Description:
-//   Given three points defining a plane, converts a planar [X,Y] coordinate to the actual
-//   corresponding 3D point on the plane.  The origin of the planar coordinate system [0,0]
-//   will be at point `a`, and the Y+ axis direction will be towards point `b`.
+//   Converts the given 2D point from planar coordinates to the global 3D coordinates of the point on the plane.
+//   Can be called one of three ways:
+//   - Given three points, `a`, `b`, and `c`, the planar coordinate system will have `[0,0]` at point `a`, and the Y+ axis will be towards point `b`.
+//   - Given a list of points, finds three non-collinear points in the list and uses them as points `a`, `b`, and `c` as above.
+//   - Given a plane definition `[A,B,C,D]` where `Ax+By+Cz=D`, the closest point on that plane to the global origin at `[0,0,0]` will be the planar coordinate origin `[0,0]`.
 // Arguments:
 //   point = The 2D point, or list of 2D points in the plane's coordinate system to get the 3D position of.
 //   a = A 3D point that the plane passes through.  Used to define the plane.
@@ -194,8 +211,15 @@ function project_plane(point, a, b, c) =
 //   c = A 3D point that the plane passes through.  Used to define the plane.
 function lift_plane(point, a, b, c) =
 	is_undef(b) && is_undef(c) && is_list(a)? let(
-		indices = find_noncollinear_points(a)
-	) lift_plane(point, a[indices[0]], a[indices[1]], a[indices[2]]) :
+		mat = is_vector(a,4)? plane_transform(a) :
+			assert(is_path(a) && len(a)>=3)
+			plane_transform(plane_from_points(a)),
+		imat = matrix_inverse(mat),
+		pts = is_vector(point)? apply(imat,point3d(point)) :
+			is_path(point)? apply(imat,path3d(point)) :
+			is_region(point)? [for (x=point) apply(imat,path3d(x))] :
+			assert(false, "point must be a 2D point, path, or region.")
+	) pts :
 	assert(is_vector(a))
 	assert(is_vector(b))
 	assert(is_vector(c))
diff --git a/distributors.scad b/distributors.scad
index 5111df5..b008113 100644
--- a/distributors.scad
+++ b/distributors.scad
@@ -998,9 +998,13 @@ module ovoid_spread(r=undef, d=undef, n=100, cone_ang=90, scale=[1,1,1], perp=tr
 // Example:
 //   mirror_copy(UP+BACK, cp=[0,-5,-5]) rot(from=UP, to=BACK+UP) cylinder(d1=10, d2=0, h=20);
 //   color("blue",0.25) translate([0,-5,-5]) rot(from=UP, to=BACK+UP) cube([15,15,0.01], center=true);
-module mirror_copy(v=[0,0,1], offset=0, cp=[0,0,0])
+module mirror_copy(v=[0,0,1], offset=0, cp)
 {
-	nv = v/norm(v);
+	cp = is_vector(v,4)? plane_normal(v) * v[3] :
+		is_vector(cp)? cp :
+		is_num(cp)? cp*unit(v) :
+		[0,0,0];
+	nv = is_vector(v,4)? plane_normal(v) : unit(v);
 	off = nv*offset;
 	if (cp == [0,0,0]) {
 		translate(off) {
diff --git a/geometry.scad b/geometry.scad
index a5fe783..e5c4a15 100644
--- a/geometry.scad
+++ b/geometry.scad
@@ -84,6 +84,23 @@ function collinear_indexed(points, a, b, c, eps=EPSILON) =
 	) collinear(p1, p2, p3, eps);
 
 
+// Function: points_are_collinear()
+// Usage:
+//   points_are_collinear(points);
+// Description:
+//   Given a list of points, returns true if all points in the list are collinear.
+// Arguments:
+//   points = The list of points to test.
+//   eps = How much variance is allowed in testing that each point is on the same line.  Default: `EPSILON` (1e-9)
+function points_are_collinear(points, eps=EPSILON) =
+	let(
+		a = furthest_point(points[0], points),
+		b = furthest_point(points[a], points),
+		pa = points[a],
+		pb = points[b]
+	) all([for (pt = points) collinear(pa, pb, pt, eps=eps)]);
+
+
 // Function: distance_from_line()
 // Usage:
 //   distance_from_line(line, pt);
@@ -584,38 +601,14 @@ function plane3pt_indexed(points, i1, i2, i3) =
 	) plane3pt(p1,p2,p3);
 
 
-// Function: plane_intersection()
-// Usage:
-//   plane_intersection(plane1, plane2, [plane3])
-// Description:
-//   Compute the point which is the intersection of the three planes, or the line intersection of two planes.
-//   If you give three planes the intersection is returned as a point.  If you give two planes the intersection
-//   is returned as a list of two points on the line of intersection.  If any of the input planes are parallel
-//   then returns undef.  
-function plane_intersection(plane1,plane2,plane3) =
-	is_def(plane3)? let(
-		matrix = [for(p=[plane1,plane2,plane3]) select(p,0,2)],
-		rhs = [for(p=[plane1,plane2,plane3]) p[3]]
-	) linear_solve(matrix,rhs) :
-	let(
-		normal = cross(plane_normal(plane1), plane_normal(plane2))
-	) approx(norm(normal),0) ? undef :
-	let(
-		matrix = [for(p=[plane1,plane2]) select(p,0,2)],
-		rhs = [for(p=[plane1,plane2]) p[3]],
-		point = linear_solve(matrix,rhs)
-	) is_undef(point)? undef :
-	[point, point+normal];
-
-
 // Function: plane_from_normal()
 // Usage:
-//   plane_from_normal(normal, pt)
+//   plane_from_normal(normal, [pt])
 // Description:
 //   Returns a plane defined by a normal vector and a point.
 // Example:
 //   plane_from_normal([0,0,1], [2,2,2]);  // Returns the xy plane passing through the point (2,2,2)
-function plane_from_normal(normal, pt) =
+function plane_from_normal(normal, pt=[0,0,0]) =
   concat(normal, [normal*pt]);
 
 
@@ -632,6 +625,12 @@ function plane_from_normal(normal, pt) =
 //   points = The list of points to find the plane of.
 //   fast = If true, don't verify that all points in the list are coplanar.  Default: false
 //   eps = How much variance is allowed in testing that each point is on the same plane.  Default: `EPSILON` (1e-9)
+// Example:
+//   xyzpath = rot(45, v=[-0.3,1,0], p=path3d(star(n=6,id=70,d=100), 70));
+//   plane = plane_from_points(xyzpath);
+//   #stroke(xyzpath,closed=true);
+//   cp = centroid(xyzpath);
+//   move(cp) rot(from=UP,to=plane_normal(plane)) anchor_arrow();
 function plane_from_points(points, fast=false, eps=EPSILON) =
 	let(
 		points = deduplicate(points),
@@ -646,6 +645,39 @@ function plane_from_points(points, fast=false, eps=EPSILON) =
 	) all_coplanar? plane : undef;
 
 
+// Function: plane_from_polygon()
+// Usage:
+//   plane_from_polygon(points, [fast], [eps]);
+// Description:
+//   Given a 3D planar polygon, returns the cartesian equation of a plane.
+//   Returns [A,B,C,D] where Ax+By+Cz=D is the equation of the plane.
+//   If not all the points in the polygon are coplanar, then `undef` is returned.
+//   If `fast` is true, then a polygon where not all points are coplanar will
+//   result in an invalid plane value, as all coplanar checks are skipped.
+// Arguments:
+//   poly = The planar 3D polygon to find the plane of.
+//   fast = If true, don't verify that all points in the polygon are coplanar.  Default: false
+//   eps = How much variance is allowed in testing that each point is on the same plane.  Default: `EPSILON` (1e-9)
+// Example:
+//   xyzpath = rot(45, v=[0,1,0], p=path3d(star(n=5,step=2,d=100), 70));
+//   plane = plane_from_polygon(xyzpath);
+//   #stroke(xyzpath,closed=true);
+//   cp = centroid(xyzpath);
+//   move(cp) rot(from=UP,to=plane_normal(plane)) anchor_arrow();
+function plane_from_polygon(poly, fast=false, eps=EPSILON) =
+	let(
+		poly = deduplicate(poly),
+		n = polygon_normal(poly),
+		plane = [n.x, n.y, n.z, n*poly[0]]
+	) fast? plane : let(
+		all_coplanar = [
+			for (pt = poly)
+			if (!coplanar(plane,pt,eps=eps)) 1
+		] == []
+	) all_coplanar? plane :
+	undef;
+
+
 // Function: plane_normal()
 // Usage:
 //   plane_normal(plane);
@@ -654,6 +686,48 @@ function plane_from_points(points, fast=false, eps=EPSILON) =
 function plane_normal(plane) = unit([for (i=[0:2]) plane[i]]);
 
 
+// Function: plane_offset()
+// Usage:
+//   d = plane_offset(plane);
+// Description:
+//   Returns D, or the scalar offset of the plane from the origin. This can be a negative value.
+//   The absolute value of this is the distance of the plane from the origin at its closest approach.
+function plane_offset(plane) = plane[3];
+
+
+// Function: plane_transform()
+// Usage:
+//   mat = plane_transform(plane);
+// Description:
+//   Given a plane definition `[A,B,C,D]`, where `Ax+By+Cz=D`, returns a 3D affine
+//   transformation matrix that will rotate and translate from points on that plane
+//   to points on the XY plane.  You can generally then use `path2d()` to drop the
+//   Z coordinates, so you can work with the points in 2D.
+// Arguments:
+//   plane = The `[A,B,C,D]` plane definition where `Ax+By+Cz=D` is the formula of the plane.
+// Example:
+//   xyzpath = move([10,20,30], p=yrot(25, p=path3d(circle(d=100))));
+//   plane = plane_from_points(xyzpath);
+//   mat = plane_transform(plane);
+//   xypath = path2d(apply(mat, xyzpath));
+//   #stroke(xyzpath,closed=true);
+//   stroke(xypath,closed=true);
+function plane_transform(plane) =
+	let(
+		n = plane_normal(plane),
+		cp = n * plane[3]
+	) rot(from=n, to=UP) * move(-cp);
+
+
+// Function: plane_point_nearest_origin()
+// Usage:
+//   pt = plane_point_nearest_origin(plane);
+// Description:
+//   Returns the point on the plane that is closest to the origin.
+function plane_point_nearest_origin(plane) =
+	plane_normal(plane) * plane[3];
+
+
 // Function: distance_from_plane()
 // Usage:
 //   distance_from_plane(plane, point)
@@ -808,21 +882,28 @@ function polygon_line_intersection(poly, line, bounded=false, eps=EPSILON) =
 	res[0];
 
 
-// Function: points_are_collinear()
+// Function: plane_intersection()
 // Usage:
-//   points_are_collinear(points);
+//   plane_intersection(plane1, plane2, [plane3])
 // Description:
-//   Given a list of points, returns true if all points in the list are collinear.
-// Arguments:
-//   points = The list of points to test.
-//   eps = How much variance is allowed in testing that each point is on the same line.  Default: `EPSILON` (1e-9)
-function points_are_collinear(points, eps=EPSILON) =
+//   Compute the point which is the intersection of the three planes, or the line intersection of two planes.
+//   If you give three planes the intersection is returned as a point.  If you give two planes the intersection
+//   is returned as a list of two points on the line of intersection.  If any of the input planes are parallel
+//   then returns undef.  
+function plane_intersection(plane1,plane2,plane3) =
+	is_def(plane3)? let(
+		matrix = [for(p=[plane1,plane2,plane3]) select(p,0,2)],
+		rhs = [for(p=[plane1,plane2,plane3]) p[3]]
+	) linear_solve(matrix,rhs) :
 	let(
-		a = furthest_point(points[0], points),
-		b = furthest_point(points[a], points),
-		pa = points[a],
-		pb = points[b]
-	) all([for (pt = points) collinear(pa, pb, pt, eps=eps)]);
+		normal = cross(plane_normal(plane1), plane_normal(plane2))
+	) approx(norm(normal),0) ? undef :
+	let(
+		matrix = [for(p=[plane1,plane2]) select(p,0,2)],
+		rhs = [for(p=[plane1,plane2]) p[3]],
+		point = linear_solve(matrix,rhs)
+	) is_undef(point)? undef :
+	[point, point+normal];
 
 
 // Function: coplanar()
@@ -1289,7 +1370,7 @@ function centroid(poly) =
 // Usage:
 //   point_in_polygon(point, path, [eps])
 // Description:
-//   This function tests whether the given point is inside, outside or on the boundary of
+//   This function tests whether the given 2D point is inside, outside or on the boundary of
 //   the specified 2D polygon using the Winding Number method.
 //   The polygon is given as a list of 2D points, not including the repeated end point.
 //   Returns -1 if the point is outside the polyon.
@@ -1299,7 +1380,7 @@ function centroid(poly) =
 //   But the polygon cannot have holes (it must be simply connected).
 //   Rounding error may give mixed results for points on or near the boundary.
 // Arguments:
-//   point = The point to check position of.
+//   point = The 2D point to check position of.
 //   path = The list of 2D path points forming the perimeter of the polygon.
 //   eps = Acceptable variance.  Default: `EPSILON` (1e-9)
 function point_in_polygon(point, path, eps=EPSILON) =
@@ -1334,7 +1415,7 @@ function polygon_is_clockwise(path) =
 // Usage:
 //   clockwise_polygon(path);
 // Description:
-//   Given a polygon path, returns the clockwise winding version of that path.
+//   Given a 2D polygon path, returns the clockwise winding version of that path.
 function clockwise_polygon(path) =
 	polygon_is_clockwise(path)? path : reverse_polygon(path);
 
@@ -1343,7 +1424,7 @@ function clockwise_polygon(path) =
 // Usage:
 //   ccw_polygon(path);
 // Description:
-//   Given a polygon path, returns the counter-clockwise winding version of that path.
+//   Given a 2D polygon path, returns the counter-clockwise winding version of that path.
 function ccw_polygon(path) =
 	polygon_is_clockwise(path)? reverse_polygon(path) : path;
 
@@ -1357,5 +1438,23 @@ function reverse_polygon(poly) =
 	let(lp=len(poly)) [for (i=idx(poly)) poly[(lp-i)%lp]];
 
 
+// Function: polygon_normal()
+// Usage:
+//   n = polygon_normal(poly);
+// Description:
+//   Given a 3D planar polygon, returns a unit-length normal vector for the
+//   clockwise orientation of the polygon. 
+// Example:
+function polygon_normal(poly) =
+	let(
+		poly = path3d(cleanup_path(poly)),
+		p0 = poly[0],
+		n = sum([
+			for (i=[1:1:len(poly)-2])
+			cross(poly[i+1]-p0, poly[i]-p0)
+		])
+	) unit(n);
+
+
 
 // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
diff --git a/mutators.scad b/mutators.scad
index 0868994..56d5d8a 100644
--- a/mutators.scad
+++ b/mutators.scad
@@ -31,9 +31,13 @@
 //   half_of(DOWN+LEFT, s=200) sphere(d=150);
 // Example(2D):
 //   half_of([1,1], planar=true) circle(d=50);
-module half_of(v=UP, cp=[0,0,0], s=1000, planar=false)
+module half_of(v=UP, cp, s=1000, planar=false)
 {
-	cp = is_num(cp)? cp*unit(v) : cp;
+	cp = is_vector(v,4)? assert(cp==undef, "Don't use cp with plane definition.") plane_normal(v) * v[3] :
+		is_vector(cp)? cp :
+		is_num(cp)? cp*unit(v) :
+		[0,0,0];
+	v = is_vector(v,4)? plane_normal(v) : v;
 	if (cp != [0,0,0]) {
 		translate(cp) half_of(v=v, s=s, planar=planar) translate(-cp) children();
 	} else if (planar) {
diff --git a/skin.scad b/skin.scad
index 84d0d96..269d840 100644
--- a/skin.scad
+++ b/skin.scad
@@ -474,13 +474,14 @@ function _skin_core(profiles, caps) =
 
 
 // Function: subdivide_and_slice()
-// Usage: subdivide_and_slice(profiles, slices, [numpoints], [method], [closed])
-// Description: Subdivides the input profiles to have length `numpoints` where
-//   `numpoints` must be at least as big as the largest input profile.
-//   By default `numpoints` is set equal to the length of the largest profile.
-//   You can set `numpoints="lcm"` to sample to the least common multiple of
-//   all curves, which will avoid sampling artifacts but may produce a huge output.
-//   After subdivision, profiles are sliced.
+// Usage:
+//   subdivide_and_slice(profiles, slices, [numpoints], [method], [closed])
+// Description:
+//   Subdivides the input profiles to have length `numpoints` where `numpoints` must be at least as
+//   big as the largest input profile.  By default `numpoints` is set equal to the length of the
+//   largest profile.  You can set `numpoints="lcm"` to sample to the least common multiple of all
+//   curves, which will avoid sampling artifacts but may produce a huge output.  After subdivision,
+//   profiles are sliced.
 // Arguments:
 //   profiles = profiles to operate on
 //   slices = number of slices to insert between each pair of profiles.  May be a vector
diff --git a/version.scad b/version.scad
index 1b10651..19cd8d5 100644
--- a/version.scad
+++ b/version.scad
@@ -8,7 +8,7 @@
 //////////////////////////////////////////////////////////////////////
 
 
-BOSL_VERSION = [2,0,284];
+BOSL_VERSION = [2,0,285];
 
 
 // Section: BOSL Library Version Functions