diff --git a/math.scad b/math.scad
index f8dc707..5ec3209 100644
--- a/math.scad
+++ b/math.scad
@@ -438,8 +438,11 @@ function lcm(a,b=[]) =
 //   sum([1,2,3]);  // returns 6.
 //   sum([[1,2,3], [3,4,5], [5,6,7]]);  // returns [9, 12, 15]
 function sum(v, dflt=0) =
+    is_vector(v) ? [for(i=v) 1]*v :
     assert(is_consistent(v), "Input to sum is non-numeric or inconsistent")
-    len(v) == 0 ? dflt : _sum(v,v[0]*0);
+    is_vector(v[0]) ? [for(i=v) 1]*v :
+    len(v) == 0 ? dflt :
+                  _sum(v,v[0]*0);
 
 function _sum(v,_total,_i=0) = _i>=len(v) ? _total : _sum(v,_total+v[_i], _i+1);
 
@@ -896,7 +899,13 @@ function count_true(l, nmax=undef, i=0, cnt=0) =
 //   data[len(data)-1].  This function uses a symetric derivative approximation
 //   for internal points, f'(t) = (f(t+h)-f(t-h))/2h.  For the endpoints (when closed=false) the algorithm
 //   uses a two point method if sufficient points are available: f'(t) = (3*(f(t+h)-f(t)) - (f(t+2*h)-f(t+h)))/2h.
+// 
+//   If `h` is a vector then it is assumed to be nonuniform, with h[i] giving the sampling distance
+//   between data[i+1] and data[i], and the data values will be linearly resampled at each corner
+//   to produce a uniform spacing for the derivative estimate.  At the endpoints a single point method
+//   is used: f'(t) = (f(t+h)-f(t))/h.  
 function deriv(data, h=1, closed=false) =
+    is_vector(h) ? _deriv_nonuniform(data, h, closed=closed) :
     let( L = len(data) )
     closed? [
         for(i=[0:1:L-1])
@@ -916,6 +925,28 @@ function deriv(data, h=1, closed=false) =
     ];
 
 
+function _dnu_calc(f1,fc,f2,h1,h2) =
+    let(
+        f1 = h2<h1 ? lerp(fc,f1,h2/h1) : f1 , 
+        f2 = h1<h2 ? lerp(fc,f2,h1/h2) : f2
+    )
+    (f2-f1) / 2 / min([h1,h2]);
+
+
+function _deriv_nonuniform(data, h, closed) =
+    assert(len(h) == len(data)-(closed?0:1),str("Vector valued h must be length ",len(data)-(closed?0:1)))
+    let(
+      L = len(data)
+    )
+    closed? [for(i=[0:1:L-1])
+    	        _dnu_calc(data[(L+i-1)%L], data[i], data[(i+1)%L], select(h,i-1), h[i]) ]
+    : [
+        (data[1]-data[0])/h[0],
+        for(i=[1:1:L-2]) _dnu_calc(data[i-1],data[i],data[i+1], h[i-1],h[i]),
+        (data[L-1]-data[L-2])/h[L-2]                            
+      ];
+
+
 // Function: deriv2()
 // Usage: deriv2(data, [h], [closed])
 // Description:
diff --git a/paths.scad b/paths.scad
index ad8b732..654a427 100644
--- a/paths.scad
+++ b/paths.scad
@@ -149,6 +149,21 @@ function path_length(path,closed=false) =
     sum([for (i = [0:1:len(path)-2]) norm(path[i+1]-path[i])])+(closed?norm(path[len(path)-1]-path[0]):0);
 
 
+// Function: path_segment_lengths()
+// Usage:
+//   path_segment_lengths(path,[closed])
+// Description:
+//   Returns list of the length of each segment in a path
+// Arguments:
+//   path = path to measure
+//   closed = true if the path is closed.  Default: false
+function path_segment_lengths(path, closed=false) =
+    [
+      for (i=[0:1:len(path)-2]) norm(path[i+1]-path[i]),
+      if (closed) norm(path[0]-path[len(path)-1])
+     ]; 
+
+
 // Function: path_pos_from_start()
 // Usage:
 //   pos = path_pos_from_start(path,length,[closed]);
@@ -280,13 +295,35 @@ function path_closest_point(path, pt) =
 
 
 // Function: path_tangents()
-// Usage: path_tangents(path, [closed])
+// Usage: path_tangents(path, [closed], [uniform])
 // Description:
 //   Compute the tangent vector to the input path.  The derivative approximation is described in deriv().
-//   The returns vectors will be normalized to length 1.
-function path_tangents(path, closed=false) =
+//   The returns vectors will be normalized to length 1.  If any derivatives are zero then
+//   the function fails with an error.  If you set `uniform` to false then the sampling is
+//   assumed to be non-uniform and the derivative is computed with adjustments to produce corrected
+//   values.
+// Arguments:
+//   path = path to find the tagent vectors for
+//   closed = set to true of the path is closed.  Default: false
+//   uniform = set to false to correct for non-uniform sampling.  Default: true
+// Example: A shape with non-uniform sampling gives distorted derivatives that may be undesirable
+//   rect = square([10,3]);
+//   tangents = path_tangents(rect,closed=true);
+//   stroke(rect,closed=true, width=0.1);
+//   color("purple")
+//       for(i=[0:len(tangents)-1])
+//           stroke([rect[i]-tangents[i], rect[i]+tangents[i]],width=.1, endcap2="arrow2");
+// Example: A shape with non-uniform sampling gives distorted derivatives that may be undesirable
+//   rect = square([10,3]);
+//   tangents = path_tangents(rect,closed=true,uniform=false);
+//   stroke(rect,closed=true, width=0.1);
+//   color("purple")
+//       for(i=[0:len(tangents)-1])
+//           stroke([rect[i]-tangents[i], rect[i]+tangents[i]],width=.1, endcap2="arrow2");
+function path_tangents(path, closed=false, uniform=true) =
     assert(is_path(path))
-    [for(t=deriv(path,closed=closed)) unit(t)];
+    !uniform ? [for(t=deriv(path,closed=closed, h=path_segment_lengths(path,closed))) unit(t)]
+             : [for(t=deriv(path,closed=closed)) unit(t)];
 
 
 // Function: path_normals()
diff --git a/vnf.scad b/vnf.scad
index 6021669..97b09f5 100644
--- a/vnf.scad
+++ b/vnf.scad
@@ -357,17 +357,13 @@ module vnf_polyhedron(vnf, convexity=2) {
 //   Returns the volume enclosed by the given manifold VNF.   The VNF must describe a valid polyhedron with consistent face direction and
 //   no holes; otherwise the results are undefined.  Returns a positive volume if face direction is clockwise and a negative volume
 //   if face direction is counter-clockwise.
+
+// Algorithm adapted/simplified from: https://wwwf.imperial.ac.uk/~rn/centroid.pdf
 function vnf_volume(vnf) =
     let(verts = vnf[0])
     sum([
          for(face=vnf[1], j=[1:1:len(face)-2])
-             let(
-                 v0 = verts[face[0]],
-                 v1 = verts[face[j]],
-                 v2 = verts[face[j+1]],
-                 n = cross(v2-v0,v1-v0)
-             )
-             v0 * n
+             cross(verts[face[j+1]], verts[face[j]]) * verts[face[0]]
     ])/6;