// Copyright 2015 Charles Staats III
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// smoothcontour3
// An Asymptote module for drawing smooth implicitly defined surfaces
// author: Charles Staats III
// charles dot staats dot iii at gmail dot com

import graph_settings;  // for nmesh
import three;
import math;

/***********************************************/
/******** CREATING BEZIER PATCHES **************/
/******** WITH SPECIFIED NORMALS  **************/
/***********************************************/

// The weight given to minimizing the sum of squares of
// the mixed partials at the corners of the bezier patch.
// If this weight is zero, the result is undefined in
// places and can be rather wild even where it is
// defined.
// The struct is used to as a namespace.
struct pathwithnormals_settings {
 static real wildnessweight = 1e-3;
}
private from pathwithnormals_settings unravel wildnessweight;

// The Bernstein basis polynomials of degree 3:
real B03(real t) { return (1-t)^3; }
real B13(real t) { return 3*t*(1-t)^2; }
real B23(real t) { return 3*t^2*(1-t); }
real B33(real t) { return t^3; }

private typedef real function(real);
function[] bernstein = new function[] {B03, B13, B23, B33};

// This function attempts to produce a Bezier patch
// with the specified boundary path and normal directions.
// For instance, the patch should be normal to
// u0normals[0] at (0, 0.25),
// normal to u0normals[1] at (0, 0.5), and
// normal to u0normals[2] at (0, 0.75).
// The actual normal (as computed by the patch.normal() function)
// may be parallel to the specified normal, antiparallel, or
// even zero.
//
// A small amount of deviation is allowed in order to stabilize
// the algorithm (by keeping the mixed partials at the corners from
// growing too large).
//
// Note that the specified normals are projected to be orthogonal to
// the specified boundary path. However, the entries in the array
// remain intact.
patch patchwithnormals(path3 external, triple[] u0normals, triple[] u1normals,
                      triple[] v0normals, triple[] v1normals)
{
 assert(cyclic(external));
 assert(length(external) == 4);
 assert(u0normals.length == 3);
 assert(u1normals.length == 3);
 assert(v0normals.length == 3);
 assert(v1normals.length == 3);

 triple[][] controlpoints = new triple[4][4];
 controlpoints[0][0] = point(external,0);
 controlpoints[1][0] = postcontrol(external,0);
 controlpoints[2][0] = precontrol(external,1);
 controlpoints[3][0] = point(external,1);
 controlpoints[3][1] = postcontrol(external,1);
 controlpoints[3][2] = precontrol(external,2);
 controlpoints[3][3] = point(external,2);
 controlpoints[2][3] = postcontrol(external,2);
 controlpoints[1][3] = precontrol(external,3);
 controlpoints[0][3] = point(external,3);
 controlpoints[0][2] = postcontrol(external,3);
 controlpoints[0][1] = precontrol(external, 4);

 real[][] matrix = new real[24][12];
 for (int i = 0; i < matrix.length; ++i)
   for (int j = 0; j < matrix[i].length; ++j)
     matrix[i][j] = 0;
 real[] rightvector = new real[24];
 for (int i = 0; i < rightvector.length; ++i)
   rightvector[i] = 0;

 void addtocoeff(int i, int j, int count, triple coeffs) {
   if (1 <= i && i <= 2 && 1 <= j && j <= 2) {
     int position = 3 * (2 * (i-1) + (j-1));
     matrix[count][position] += coeffs.x;
     matrix[count][position+1] += coeffs.y;
     matrix[count][position+2] += coeffs.z;
   } else {
     rightvector[count] -= dot(controlpoints[i][j], coeffs);
   }
 }

 void addtocoeff(int i, int j, int count, real coeff) {
   if (1 <= i && i <= 2 && 1 <= j && j <= 2) {
     int position = 3 * (2 * (i-1) + (j-1));
     matrix[count][position] += coeff;
     matrix[count+1][position+1] += coeff;
     matrix[count+2][position+2] += coeff;
   } else {
     rightvector[count] -= controlpoints[i][j].x * coeff;
     rightvector[count+1] -= controlpoints[i][j].y * coeff;
     rightvector[count+2] -= controlpoints[i][j].z * coeff;
   }
 }

 int count = 0;

 void apply_u0(int j, real a, triple n) {
   real factor = 3 * bernstein[j](a);
   addtocoeff(0,j,count,-factor*n);
   addtocoeff(1,j,count,factor*n);
 }
 void apply_u0(real a, triple n) {
   triple tangent = dir(external, 4-a);
   n -= dot(n,tangent)*tangent;
   n = unit(n);
   for (int j = 0; j < 4; ++j) {
     apply_u0(j,a,n);
   }
   ++count;
 }
 apply_u0(0.25, u0normals[0]);
 apply_u0(0.5, u0normals[1]);
 apply_u0(0.75, u0normals[2]);

 void apply_u1(int j, real a, triple n) {
   real factor = 3 * bernstein[j](a);
   addtocoeff(3,j,count,factor*n);
   addtocoeff(2,j,count,-factor*n);
 }
 void apply_u1(real a, triple n) {
   triple tangent = dir(external, 1+a);
   n -= dot(n,tangent)*tangent;
   n = unit(n);
   for (int j = 0; j < 4; ++j)
     apply_u1(j,a,n);
   ++count;
 }
 apply_u1(0.25, u1normals[0]);
 apply_u1(0.5, u1normals[1]);
 apply_u1(0.75, u1normals[2]);

 void apply_v0(int i, real a, triple n) {
   real factor = 3 * bernstein[i](a);
   addtocoeff(i,0,count,-factor*n);
   addtocoeff(i,1,count,factor*n);
 }
 void apply_v0(real a, triple n) {
   triple tangent = dir(external, a);
   n -= dot(n,tangent) * tangent;
   n = unit(n);
   for (int i = 0; i < 4; ++i)
     apply_v0(i,a,n);
   ++count;
 }
 apply_v0(0.25, v0normals[0]);
 apply_v0(0.5, v0normals[1]);
 apply_v0(0.75, v0normals[2]);

 void apply_v1(int i, real a, triple n) {
   real factor = 3 * bernstein[i](a);
   addtocoeff(i,3,count,factor*n);
   addtocoeff(i,2,count,-factor*n);
 }
 void apply_v1(real a, triple n) {
   triple tangent = dir(external, 3-a);
   n -= dot(n,tangent)*tangent;
   n = unit(n);
   for (int i = 0; i < 4; ++i)
     apply_v1(i,a,n);
   ++count;
 }
 apply_v1(0.25, v1normals[0]);
 apply_v1(0.5, v1normals[1]);
 apply_v1(0.75, v1normals[2]);

 addtocoeff(0,0,count,9*wildnessweight);
 addtocoeff(1,1,count,9*wildnessweight);
 addtocoeff(0,1,count,-9*wildnessweight);
 addtocoeff(1,0,count,-9*wildnessweight);
 count+=3;
 addtocoeff(3,3,count,9*wildnessweight);
 addtocoeff(2,2,count,9*wildnessweight);
 addtocoeff(3,2,count,-9*wildnessweight);
 addtocoeff(2,3,count,-9*wildnessweight);
 count+=3;
 addtocoeff(0,3,count,9*wildnessweight);
 addtocoeff(1,2,count,9*wildnessweight);
 addtocoeff(1,3,count,-9*wildnessweight);
 addtocoeff(0,2,count,-9*wildnessweight);
 count += 3;
 addtocoeff(3,0,count,9*wildnessweight);
 addtocoeff(2,1,count,9*wildnessweight);
 addtocoeff(3,1,count,-9*wildnessweight);
 addtocoeff(2,0,count,-9*wildnessweight);
 count += 3;

 real[] solution = leastsquares(matrix, rightvector, warn=false);
 if (solution.length == 0) { // if the matrix was singular
   write("Warning: unable to solve matrix for specifying edge normals "
         + "on bezier patch. Using coons patch.");
   return patch(external);
 }

 for (int i = 1; i <= 2; ++i) {
   for (int j = 1; j <= 2; ++j) {
     int position = 3 * (2 * (i-1) + (j-1));
     controlpoints[i][j] = (solution[position],
                            solution[position+1],
                            solution[position+2]);
   }
 }

 return patch(controlpoints);
}

// This function attempts to produce a Bezier triangle
// with the specified boundary path and normal directions at the
// edge midpoints. The bezier triangle should be normal to
// n1 at point(external, 0.5),
// normal to n2 at point(external, 1.5), and
// normal to n3 at point(external, 2.5).
// The actual normal (as computed by the patch.normal() function)
// may be parallel to the specified normal, antiparallel, or
// even zero.
//
// A small amount of deviation is allowed in order to stabilize
// the algorithm (by keeping the mixed partials at the corners from
// growing too large).
patch trianglewithnormals(path3 external, triple n1,
                         triple n2, triple n3) {
 assert(cyclic(external));
 assert(length(external) == 3);
 // Use the formal symbols a3, a2b, abc, etc. to denote the control points,
 // following the Wikipedia article on Bezier triangles.
 triple a3 = point(external, 0), a2b = postcontrol(external, 0),
   ab2 = precontrol(external, 1), b3 = point(external, 1),
   b2c = postcontrol(external, 1), bc2 = precontrol(external, 2),
   c3 = point(external, 2), ac2 = postcontrol(external, 2),
   a2c = precontrol(external, 0);

 // Use orthogonal projection to ensure that the normal vectors are
 // actually normal to the boundary path.
 triple tangent = dir(external, 0.5);
 n1 -= dot(n1,tangent)*tangent;
 n1 = unit(n1);

 tangent = dir(external, 1.5);
 n2 -= dot(n2,tangent)*tangent;
 n2 = unit(n2);

 tangent = dir(external, 2.5);
 n3 -= dot(n3,tangent)*tangent;
 n3 = unit(n3);

 real wild = 2 * wildnessweight;
 real[][] matrix = { {n1.x, n1.y, n1.z},
                     {n2.x, n2.y, n2.z},
                     {n3.x, n3.y, n3.z},
                     {      wild,          0,          0},
                     {         0,       wild,          0},
                     {         0,          0,       wild} };
 real[] rightvector =
   { dot(n1, (a3 + 3a2b + 3ab2 + b3 - 2a2c - 2b2c)) / 4,
     dot(n2, (b3 + 3b2c + 3bc2 + c3 - 2ab2 - 2ac2)) / 4,
     dot(n3, (c3 + 3ac2 + 3a2c + a3 - 2bc2 - 2a2b)) / 4 };

 // The inner control point that minimizes the sum of squares of
 // the mixed partials on the corners.
 triple tameinnercontrol =
   ((a2b + a2c - a3) + (ab2 + b2c - b3) + (ac2 + bc2 - c3)) / 3;
 rightvector.append(wild * new real[]
                    {tameinnercontrol.x, tameinnercontrol.y, tameinnercontrol.z});
 real[] solution = leastsquares(matrix, rightvector, warn=false);
 if (solution.length == 0) { // if the matrix was singular
   write("Warning: unable to solve matrix for specifying edge normals "
         + "on bezier triangle. Using coons triangle.");
   return patch(external);
 }
 triple innercontrol = (solution[0], solution[1], solution[2]);
 return patch(external, innercontrol);
}

// A wrapper for the previous functions when the normal direction
// is given as a function of direction. The wrapper can also
// accommodate cyclic boundary paths of between one and four
// segments, although the results are best by far when there
// are three or four segments.
patch patchwithnormals(path3 external, triple normalat(triple)) {
 assert(cyclic(external));
 assert(1 <= length(external) && length(external) <= 4);
 if (length(external) == 3) {
   triple n1 = normalat(point(external, 0.5));
   triple n2 = normalat(point(external, 1.5));
   triple n3 = normalat(point(external, 2.5));
   return trianglewithnormals(external, n1, n2, n3);
 }
 while (length(external) < 4) external = external -- cycle;
 triple[] u0normals = new triple[3];
 triple[] u1normals = new triple[3];
 triple[] v0normals = new triple[3];
 triple[] v1normals = new triple[3];
 for (int i = 1; i <= 3; ++i) {
   v0normals[i-1] = unit(normalat(point(external, i/4)));
   u1normals[i-1] = unit(normalat(point(external, 1 + i/4)));
   v1normals[i-1] = unit(normalat(point(external, 3 - i/4)));
   u0normals[i-1] = unit(normalat(point(external, 4 - i/4)));
 }
 return patchwithnormals(external, u0normals, u1normals, v0normals, v1normals);
}

/***********************************************/
/********* DUAL CUBE GRAPH UTILITY *************/
/***********************************************/

// Suppose a plane intersects a (hollow) cube, and
// does not intersect any vertices. Then its intersection
// with cube forms a cycle. The goal of the code below
// is to reconstruct the order of the cycle
// given only an unordered list of which edges the plane
// intersects.
//
// Basically, the question is this: If we know the points
// in which a more-or-less planar surface intersects the
// edges of cube, how do we connect those points?
//
// When I wrote the code, I was thinking in terms of the
// dual graph of a cube, in which "vertices" are really
// faces of the cube and "edges" connect those "vertices."

// An enum for the different "vertices" (i.e. faces)
// available. NULL_VERTEX is primarily intended as a
// return value to indicate the absence of a desired
// vertex.
private int NULL_VERTEX = -1;
private int XHIGH = 0;
private int XLOW = 1;
private int YHIGH = 2;
private int YLOW = 3;
private int ZHIGH = 4;
private int ZLOW = 5;

// An unordered set of nonnegative integers.
// Since the intent is to use
// only the six values from the enum above, no effort
// was made to use scalable algorithms.
struct intset {
 private bool[] ints = new bool[0];
 private int size = 0;

 bool contains(int item) {
   assert(item >= 0);
   if (item >= ints.length) return false;
   return ints[item];
 }

 // Returns true if the item was added (i.e., was
 // not already present).
 bool add(int item) {
   assert(item >= 0);
   while (item >= ints.length) ints.push(false);
   if (ints[item]) return false;
   ints[item] = true;
   ++size;
   return true;
 }

 int[] elements() {
   int[] toreturn;
   for (int i = 0; i < ints.length; ++i) {
     if (ints[i]) toreturn.push(i);
   }
   return toreturn;
 }

 int size() { return size; }
}

// A map from integers to sets of integers. Again, no
// attempt is made to use scalable data structures.
struct int_to_intset {
 int[] keys = new int[0];
 intset[] values = new intset[0];

 void add(int key, int value) {
   for (int i = 0; i < keys.length; ++i) {
     if (keys[i] == key) {
       values[i].add(value);
       return;
     }
   }
   keys.push(key);
   intset newset;
   values.push(newset);
   newset.add(value);
 }

 private int indexOf(int key) {
   for (int i = 0; i < keys.length; ++i) {
     if (keys[i] == key) return i;
   }
   return -1;
 }

 int[] get(int key) {
   int i = indexOf(key);
   if (i < 0) return new int[0];
   else return values[i].elements();
 }

 int numvalues(int key) {
   int i = indexOf(key);
   if (i < 0) return 0;
   else return values[i].size();
 }

 int numkeys() {
   return keys.length;
 }
}

// A struct intended to represent an undirected edge between
// two "vertices."
struct edge {
 int start;
 int end;
 void operator init(int a, int b) {
   start = a;
   end = b;
 }
 bool bordersvertex(int v) { return start == v || end == v; }
}

string operator cast(edge e) {
 int a, b;
 if (e.start <= e.end) {a = e.start; b = e.end;}
 else {a = e.end; b = e.start; }
 return (string)a + " <-> " + (string)b;
}

bool operator == (edge a, edge b) {
 if (a.start == b.start && a.end == b.end) return true;
 if (a.start == b.end && a.end == b.start) return true;
 return false;
}

string operator cast(edge[] edges) {
 string toreturn = "{ ";
 for (int i = 0; i < edges.length; ++i) {
   toreturn += edges[i];
   if (i < edges.length-1) toreturn += ", ";
 }
 return toreturn + " }";
}

// Finally, the function that strings together a list of edges
// into a cycle. It makes assumptions that hold true if the
// list of edges did in fact come from a plane intersection
// containing no vertices of the cube. For instance, such a
// plane can contain at most two noncollinear points of any
// one face; consequently, no face can border more than two of
// the selected edges.
//
// If the underlying assumptions prove to be false, the function
// returns null.
int[] makecircle(edge[] edges) {
 if (edges.length == 0) return new int[0];
 int_to_intset graph;
 for (edge e : edges) {
   graph.add(e.start, e.end);
   graph.add(e.end, e.start);
 }
 int currentvertex = edges[0].start;
 int startvertex = currentvertex;
 int lastvertex = NULL_VERTEX;
 int[] toreturn = new int[0];
 do {
   toreturn.push(currentvertex);
   int[] adjacentvertices = graph.get(currentvertex);
   if (adjacentvertices.length != 2) return null;
   for (int v : adjacentvertices) {
     if (v != lastvertex) {
       lastvertex = currentvertex;
       currentvertex = v;
       break;
     }
   }
 } while (currentvertex != startvertex);
 if (toreturn.length != graph.numkeys()) return null;
 toreturn.cyclic = true;
 return toreturn;
}

/***********************************************/
/********** PATHS BETWEEN POINTS ***************/
/***********************************************/
// Construct paths between two points with additional
// constraints; for instance, the path must be orthogonal
// to a certain vector at each of the endpoints, must
// lie within a specified plane or a specified face
// of a rectangular solid,....

// A vector (typically a normal vector) at a specified position.
struct positionedvector {
 triple position;
 triple direction;
 void operator init(triple position, triple direction) {
   this.position = position;
   this.direction = direction;
 }
}

string operator cast(positionedvector vv) {
 return "position: " + (string)(vv.position) + " vector: " + (string)vv.direction;
}

// The angle, in degrees, between two vectors.
real angledegrees(triple a, triple b) {
 real dotprod = dot(a,b);
 real lengthprod = max(abs(a) * abs(b), abs(dotprod));
 if (lengthprod == 0) return 0;
 return aCos(dotprod / lengthprod);
}

// A path (single curved segment) between two points. At each point
// is specified a vector orthogonal to the path.
path3 pathbetween(positionedvector v1, positionedvector v2) {
 triple n1 = unit(v1.direction);
 triple n2 = unit(v2.direction);

 triple p1 = v1.position;
 triple p2 = v2.position;
 triple delta = p2-p1;

 triple dir1 = delta - dot(delta, n1)*n1;
 triple dir2 = delta - dot(delta, n2)*n2;
 return p1 {dir1} .. {dir2} p2;
}

// Assuming v1 and v2 are linearly independent, returns an array {a, b}
// such that a v1 + b v2 is the orthogonal projection of toproject onto
// the span of v1 and v2. If v1 and v2 are dependent, returns an empty array
// (if warn==false) or throws an error (if warn==true).
real[] projecttospan_findcoeffs(triple toproject, triple v1, triple v2,
                               bool warn=false) {
 real[][] matrix = {{v1.x, v2.x},
                    {v1.y, v2.y},
                    {v1.z, v2.z}};
 real[] desiredanswer = {toproject.x, toproject.y, toproject.z};
 return leastsquares(matrix, desiredanswer, warn=warn);
}

// Project the triple toproject into the span of a and b, but restrict
// to the quarter-plane of linear combinations a v1 + b v2 such that
// a >= mincoeff and b >= mincoeff. If v1 and v2 are linearly dependent,
// return a random (positive) linear combination.
triple projecttospan(triple toproject, triple v1, triple v2,
                    real mincoeff = 0.05) {
 real[] coeffs = projecttospan_findcoeffs(toproject, v1, v2, warn=false);
 real a, b;
 if (coeffs.length == 0) {
   a = mincoeff + unitrand();
   b = mincoeff + unitrand();
 } else {
   a = max(coeffs[0], mincoeff);
   b = max(coeffs[1], mincoeff);
 }
 return a*v1 + b*v2;
}

// A path between two specified vertices of a cyclic path. The
// path tangent at each endpoint is guaranteed to lie within the
// quarter-plane spanned by positive linear combinations of the
// tangents of the two outgoing paths at that endpoint.
path3 pathbetween(path3 edgecycle, int vertex1, int vertex2) {
 triple point1 = point(edgecycle, vertex1);
 triple point2 = point(edgecycle, vertex2);

 triple v1 = -dir(edgecycle, vertex1, sign=-1);
 triple v2 =  dir(edgecycle, vertex1, sign= 1);
 triple direction1 = projecttospan(unit(point2-point1), v1, v2);

 v1 = -dir(edgecycle, vertex2, sign=-1);
 v2 =  dir(edgecycle, vertex2, sign= 1);
 triple direction2 = projecttospan(unit(point1-point2), v1, v2);

 return point1 {direction1} .. {-direction2} point2;
}

// This function applies a heuristic to choose two "opposite"
// vertices (separated by three segments) of edgecycle, which
// is required to be a cyclic path consisting of 5 or 6 segments.
// The two chosen vertices are pushed to savevertices.
//
// The function returns a path between the two chosen vertices. The
// path tangent at each endpoint is guaranteed to lie within the
// quarter-plane spanned by positive linear combinations of the
// tangents of the two outgoing paths at that endpoint.
path3 bisector(path3 edgecycle, int[] savevertices) {
 real mincoeff = 0.05;
 assert(cyclic(edgecycle));
 int n = length(edgecycle);
 assert(n >= 5 && n <= 6);
 triple[] forwarddirections = sequence(new triple(int i) {
     return dir(edgecycle, i, sign=1);
   }, n);
 forwarddirections.cyclic = true;
 triple[] backwarddirections = sequence(new triple(int i) {
     return -dir(edgecycle, i, sign=-1);
   }, n);
 backwarddirections.cyclic = true;
 real[] angles = sequence(new real(int i) {
     return angledegrees(forwarddirections[i], backwarddirections[i]);
   }, n);
 angles.cyclic = true;
 int lastindex = (n == 5 ? 4 : 2);
 real maxgoodness = 0;
 int chosenindex = -1;
 triple directionout, directionin;
 for (int i = 0; i <= lastindex; ++i) {
   int opposite = i + 3;
   triple vec = unit(point(edgecycle, opposite) - point(edgecycle, i));
   real[] coeffsbegin = projecttospan_findcoeffs(vec, forwarddirections[i],
                                                 backwarddirections[i]);
   if (coeffsbegin.length == 0) continue;
   coeffsbegin[0] = max(coeffsbegin[0], mincoeff);
   coeffsbegin[1] = max(coeffsbegin[1], mincoeff);

   real[] coeffsend = projecttospan_findcoeffs(-vec, forwarddirections[opposite],
                                               backwarddirections[opposite]);
   if (coeffsend.length == 0) continue;
   coeffsend[0] = max(coeffsend[0], mincoeff);
   coeffsend[1] = max(coeffsend[1], mincoeff);

   real goodness = angles[i] * angles[opposite] * coeffsbegin[0] * coeffsend[0]
       * coeffsbegin[1] * coeffsend[1];
   if (goodness > maxgoodness) {
     maxgoodness = goodness;
     directionout = coeffsbegin[0] * forwarddirections[i] +
         coeffsbegin[1] * backwarddirections[i];
     directionin = -(coeffsend[0] * forwarddirections[opposite] +
                     coeffsend[1] * backwarddirections[opposite]);
     chosenindex = i;
   }
 }
 if (chosenindex == -1) {
   savevertices.push(0);
   savevertices.push(3);
   return pathbetween(edgecycle, 0, 3);
 } else {
   savevertices.push(chosenindex);
   savevertices.push(chosenindex+3);
   return point(edgecycle, chosenindex) {directionout} ..
     {directionin} point(edgecycle, chosenindex + 3);
 }
}

// A path between two specified points (with specified normals) that lies
// within a specified face of a rectangular solid.
path3 pathinface(positionedvector v1, positionedvector v2,
                triple facenorm, triple edge1normout, triple edge2normout)
{
 triple dir1 = cross(v1.direction, facenorm);
 real dotprod = dot(dir1, edge1normout);
 if (dotprod > 0) dir1 = -dir1;
 // Believe it or not, this "tiebreaker" is actually relevant at times,
 // for instance, when graphing the cone x^2 + y^2 = z^2 over the region
 // -1 <= x,y,z <= 1.
 else if (dotprod == 0 && dot(dir1, v2.position - v1.position) < 0) dir1 = -dir1;

 triple dir2 = cross(v2.direction, facenorm);
 dotprod = dot(dir2, edge2normout);
 if (dotprod < 0) dir2 = -dir2;
 else if (dotprod == 0 && dot(dir2, v2.position - v1.position) < 0) dir2 = -dir2;

 return v1.position {dir1} .. {dir2} v2.position;
}

triple normalout(int face) {
 if (face == XHIGH) return X;
 else if (face == YHIGH) return Y;
 else if (face == ZHIGH) return Z;
 else if (face == XLOW) return -X;
 else if (face == YLOW) return -Y;
 else if (face == ZLOW) return -Z;
 else return O;
}

// A path between two specified points (with specified normals) that lies
// within a specified face of a rectangular solid.
path3 pathinface(positionedvector v1, positionedvector v2,
                int face, int edge1face, int edge2face) {
 return pathinface(v1, v2, normalout(face), normalout(edge1face),
                   normalout(edge2face));
}

/***********************************************/
/******** DRAWING IMPLICIT SURFACES ************/
/***********************************************/

// DEPRECATED
// Quadrilateralization:
// Produce a surface (array of *nondegenerate* Bezier patches) with a
// specified three-segment boundary. The surface should approximate the
// zero locus of the specified f with its specified gradient.
//
// If it is not possible to produce the desired result without leaving the
// specified rectangular region, returns a length-zero array.
//
// Dividing a triangle into smaller quadrilaterals this way is opposite
// the usual trend in mathematics. However, *before the introduction of bezier
// triangles,* the pathwithnormals algorithm
// did a poor job of choosing a good surface when the boundary path did
// not consist of four positive-length segments.
patch[] triangletoquads(path3 external, real f(triple), triple grad(triple),
                       triple a, triple b) {
 static real epsilon = 1e-3;
 assert(length(external) == 3);
 assert(cyclic(external));

 triple c0 = point(external, 0);
 triple c1 = point(external, 1);
 triple c2 = point(external, 2);

 triple center = (c0 + c1 + c2) / 3;
 triple n = unit(cross(c1-c0, c2-c0));

 real g(real t) { return f(center + t*n); }

 real tmin = -realMax, tmax = realMax;
 void absorb(real t) {
   if (t < 0) tmin = max(t,tmin);
   else tmax = min(t,tmax);
 }
 if (n.x != 0) {
   absorb((a.x - center.x) / n.x);
   absorb((b.x - center.x) / n.x);
 }
 if (n.y != 0) {
   absorb((a.y - center.y) / n.y);
   absorb((b.y - center.y) / n.y);
 }
 if (n.z != 0) {
   absorb((a.z - center.z) / n.z);
   absorb((b.z - center.z) / n.z);
 }

 real fa = g(tmin);
 real fb = g(tmax);
 if ((fa > 0 && fb > 0) || (fa < 0 && fb < 0)) {
   return new patch[0];
 } else {
   real t = findroot(g, tmin, tmax, fa=fa, fb=fb);
   center += t * n;
 }

 n = unit(grad(center));

 triple m0 = point(external, 0.5);
 positionedvector m0 = positionedvector(m0, unit(grad(m0)));
 triple m1 = point(external, 1.5);
 positionedvector m1 = positionedvector(m1, unit(grad(m1)));
 triple m2 = point(external, 2.5);
 positionedvector m2 = positionedvector(m2, unit(grad(m2)));
 positionedvector c = positionedvector(center, unit(grad(center)));

 path3 pathto_m0 = pathbetween(c, m0);
 path3 pathto_m1 = pathbetween(c, m1);
 path3 pathto_m2 = pathbetween(c, m2);

 path3 quad0 = subpath(external, 0, 0.5)
   & reverse(pathto_m0)
   & pathto_m2
   & subpath(external, -0.5, 0)
   & cycle;
 path3 quad1 = subpath(external, 1, 1.5)
   & reverse(pathto_m1)
   & pathto_m0
   & subpath(external, 0.5, 1)
   & cycle;
 path3 quad2 = subpath(external, 2, 2.5)
   & reverse(pathto_m2)
   & pathto_m1
   & subpath(external, 1.5, 2)
   & cycle;

 return new patch[] {patchwithnormals(quad0, grad),
     patchwithnormals(quad1, grad),
     patchwithnormals(quad2, grad)};
}

// Attempts to fill the path external (which should by a cyclic path consisting of
// three segments) with bezier triangle(s). Returns an empty array if it fails.
//
// In more detail: A single bezier triangle is computed using trianglewithnormals. The normals of
// the resulting triangle at the midpoint of each edge are computed. If any of these normals
// is in the negative f direction, the external triangle is subdivided into four external triangles
// and the same procedure is applied to each. If one or more of them has an incorrectly oriented
// edge normal, the function gives up and returns an empty array.
//
// Thus, the returned array consists of 0, 1, or 4 bezier triangles; no other array lengths
// are possible.
//
// This function assumes that the path orientation is consistent with f (and its gradient)
// -- i.e., that
// at a corner, (tangent in) x (tangent out) is in the positive f direction.
patch[] maketriangle(path3 external, real f(triple),
                    triple grad(triple), bool allowsubdivide = true) {
 assert(cyclic(external));
 assert(length(external) == 3);
 triple m1 = point(external, 0.5);
 triple n1 = unit(grad(m1));
 triple m2 = point(external, 1.5);
 triple n2 = unit(grad(m2));
 triple m3 = point(external, 2.5);
 triple n3 = unit(grad(m3));
 patch beziertriangle = trianglewithnormals(external, n1, n2, n3);
 if (dot(n1, beziertriangle.normal(0.5, 0)) >= 0 &&
     dot(n2, beziertriangle.normal(0.5, 0.5)) >= 0 &&
     dot(n3, beziertriangle.normal(0, 0.5)) >= 0)
   return new patch[] {beziertriangle};

 if (!allowsubdivide) return new patch[0];

 positionedvector m1 = positionedvector(m1, n1);
 positionedvector m2 = positionedvector(m2, n2);
 positionedvector m3 = positionedvector(m3, n3);
 path3 p12 = pathbetween(m1, m2);
 path3 p23 = pathbetween(m2, m3);
 path3 p31 = pathbetween(m3, m1);
 patch[] triangles = maketriangle(p12 & p23 & p31 & cycle, f, grad=grad,
                                  allowsubdivide=false);
 if (triangles.length < 1) return new patch[0];

 triangles.append(maketriangle(subpath(external, -0.5, 0.5) & reverse(p31) & cycle,
                               f, grad=grad, allowsubdivide=false));
 if (triangles.length < 2) return new patch[0];

 triangles.append(maketriangle(subpath(external, 0.5, 1.5) & reverse(p12) & cycle,
                               f, grad=grad, allowsubdivide=false));
 if (triangles.length < 3) return new patch[0];

 triangles.append(maketriangle(subpath(external, 1.5, 2.5) & reverse(p23) & cycle,
                               f, grad=grad, allowsubdivide=false));
 if (triangles.length < 4) return new patch[0];

 return triangles;
}


// Returns true if the point is "nonsingular" (in the sense that the magnitude
// of the gradient is not too small) AND very close to the zero locus of f
// (assuming f is locally linear).
bool check_fpt_zero(triple testpoint, real f(triple), triple grad(triple)) {
 real testval = f(testpoint);
 real slope = abs(grad(testpoint));
 static real tolerance = 2*rootfinder_settings.roottolerance;
 return !(slope > tolerance && abs(testval) / slope > tolerance);
}

// Returns true if pt lies within the rectangular solid with
// opposite corners at a and b.
bool checkptincube(triple pt, triple a, triple b) {
 real xmin = a.x;
 real xmax = b.x;
 real ymin = a.y;
 real ymax = b.y;
 real zmin = a.z;
 real zmax = b.z;
 if (xmin > xmax) { real t = xmax; xmax=xmin; xmin=t; }
 if (ymin > ymax) { real t = ymax; ymax=ymin; ymin=t; }
 if (zmin > zmax) { real t = zmax; zmax=zmin; zmin=t; }

 return ((xmin <= pt.x) && (pt.x <= xmax) &&
         (ymin <= pt.y) && (pt.y <= ymax) &&
         (zmin <= pt.z) && (pt.z <= zmax));

}

// A convenience function for combining the previous two tests.
bool checkpt(triple testpt, real f(triple), triple grad(triple),
            triple a, triple b) {
 return checkptincube(testpt, a, b) &&
   check_fpt_zero(testpt, f, grad);
}

// Attempts to fill in the boundary cycle with a collection of
// patches to approximate smoothly the zero locus of f. If unable to
// do so while satisfying certain checks, returns null.
// This is distinct from returning an empty
// array, which merely indicates that the boundary cycle is too small
// to be worth filling in.
patch[] quadpatches(path3 edgecycle, positionedvector[] corners,
                   real f(triple), triple grad(triple),
                   triple a, triple b, bool usetriangles) {
 assert(corners.cyclic);

 // The tolerance for considering two points "essentially identical."
 static real tolerance = 2.5 * rootfinder_settings.roottolerance;

 // If there are two neighboring vertices that are essentially identical,
 // unify them into one.
 for (int i = 0; i < corners.length; ++i) {
   if (abs(corners[i].position - corners[i+1].position) < tolerance) {
     if (corners.length == 2) return new patch[0];
     corners.delete(i);
     edgecycle = subpath(edgecycle, 0, i)
         & subpath(edgecycle, i+1, length(edgecycle))
         & cycle;
     --i;
     assert(length(edgecycle) == corners.length);
   }
 }

 static real areatolerance = tolerance^2;

 assert(corners.length >= 2);
 if (corners.length == 2) {
   // If the area is too small, just ignore it; otherwise, subdivide.
   real area0 = abs(cross(-dir(edgecycle, 0, sign=-1, normalize=false),
                          dir(edgecycle, 0, sign=1, normalize=false)));
   real area1 = abs(cross(-dir(edgecycle, 1, sign=-1, normalize=false),
                          dir(edgecycle, 1, sign=1, normalize=false)));
   if (area0 < areatolerance && area1 < areatolerance) return new patch[0];
   else return null;
 }
 if (length(edgecycle) > 6) abort("too many edges: not possible.");

 for (int i = 0; i < length(edgecycle); ++i) {
   if (angledegrees(dir(edgecycle,i,sign=1),
                    dir(edgecycle,i+1,sign=-1)) > 80) {
     return null;
   }
 }

 if (length(edgecycle) == 3) {
   patch[] toreturn = usetriangles ? maketriangle(edgecycle, f, grad)
       : triangletoquads(edgecycle, f, grad, a, b);
   if (toreturn.length == 0) return null;
   else return toreturn;
 }
 if (length(edgecycle) == 4) {
   return new patch[] {patchwithnormals(edgecycle, grad)};
 }

 int[] bisectorindices;
 path3 middleguide = bisector(edgecycle, bisectorindices);

 triple testpoint = point(middleguide, 0.5);
 if (!checkpt(testpoint, f, grad, a, b)) {
   return null;
 }

 patch[] toreturn = null;
 path3 firstpatch = subpath(edgecycle, bisectorindices[0], bisectorindices[1])
   & reverse(middleguide) & cycle;
 if (length(edgecycle) == 5) {
   path3 secondpatch = middleguide
       & subpath(edgecycle, bisectorindices[1], 5+bisectorindices[0]) & cycle;
   toreturn = usetriangles ? maketriangle(secondpatch, f, grad)
     : triangletoquads(secondpatch, f, grad, a, b);
   if (toreturn.length == 0) return null;
   toreturn.push(patchwithnormals(firstpatch, grad));
 } else {
   // now length(edgecycle) == 6
   path3 secondpatch = middleguide
     & subpath(edgecycle, bisectorindices[1], 6+bisectorindices[0])
     & cycle;
   toreturn = new patch[] {patchwithnormals(firstpatch, grad),
       patchwithnormals(secondpatch, grad)};
 }
 return toreturn;
}

// Numerical gradient of a function
typedef triple vectorfunction(triple);
vectorfunction nGrad(real f(triple)) {
 static real epsilon = 1e-3;
 return new triple(triple v) {
   return ( (f(v + epsilon*X) - f(v - epsilon*X)) / (2 epsilon),
            (f(v + epsilon*Y) - f(v - epsilon*Y)) / (2 epsilon),
            (f(v + epsilon*Z) - f(v - epsilon*Z)) / (2 epsilon) );
 };
}

// A point together with a value at that location.
struct evaluatedpoint {
 triple pt;
 real value;
 void operator init(triple pt, real value) {
   this.pt = pt;
   this.value = value;
 }
}

triple operator cast(evaluatedpoint p) { return p.pt; }

// Compute the values of a function at every vertex of an nx by ny by nz
// array of rectangular solids.
evaluatedpoint[][][] make3dgrid(triple a, triple b, int nx, int ny, int nz,
                               real f(triple), bool allowzero = false)
{
 evaluatedpoint[][][] toreturn = new evaluatedpoint[nx+1][ny+1][nz+1];
 for (int i = 0; i <= nx; ++i) {
   for (int j = 0; j <= ny; ++j) {
     for (int k = 0; k <= nz; ++k) {
       triple pt = (interp(a.x, b.x, i/nx),
                    interp(a.y, b.y, j/ny),
                    interp(a.z, b.z, k/nz));
       real value = f(pt);
       if (value == 0 && !allowzero) value = 1e-5;
       toreturn[i][j][k] = evaluatedpoint(pt, value);
     }
   }
 }
 return toreturn;
}

// The following utilities make, for instance, slice(A, i, j, k, l)
// equivalent to what A[i:j][k:l] ought to mean for two- and three-
// -dimensional arrays of evaluatedpoints and of positionedvectors.
typedef evaluatedpoint T;
T[][] slice(T[][] a, int start1, int end1, int start2, int end2) {
 T[][] toreturn = new T[end1-start1][];
 for (int i = start1; i < end1; ++i) {
   toreturn[i-start1] = a[i][start2:end2];
 }
 return toreturn;
}
T[][][] slice(T[][][] a, int start1, int end1,
             int start2, int end2,
             int start3, int end3) {
 T[][][] toreturn = new T[end1-start1][][];
 for (int i = start1; i < end1; ++i) {
   toreturn[i-start1] = slice(a[i], start2, end2, start3, end3);
 }
 return toreturn;
}
typedef positionedvector T;
T[][] slice(T[][] a, int start1, int end1, int start2, int end2) {
 T[][] toreturn = new T[end1-start1][];
 for (int i = start1; i < end1; ++i) {
   toreturn[i-start1] = a[i][start2:end2];
 }
 return toreturn;
}
T[][][] slice(T[][][] a, int start1, int end1,
             int start2, int end2,
             int start3, int end3) {
 T[][][] toreturn = new T[end1-start1][][];
 for (int i = start1; i < end1; ++i) {
   toreturn[i-start1] = slice(a[i], start2, end2, start3, end3);
 }
 return toreturn;
}

// An object of class gridwithzeros stores the values of a function at each vertex
// of a three-dimensional grid, together with zeros of the function along edges
// of the grid and the gradient of the function at each such zero.
struct gridwithzeros {
 int nx, ny, nz;
 evaluatedpoint[][][] corners;
 positionedvector[][][] xdirzeros;
 positionedvector[][][] ydirzeros;
 positionedvector[][][] zdirzeros;
 triple grad(triple);
 real f(triple);
 int maxdepth;
 bool usetriangles;

 // Populate the edges with zeros that have a sign change and are not already
 // populated.
 void fillzeros() {
   for (int j = 0; j < ny+1; ++j) {
     for (int k = 0; k < nz+1; ++k) {
       real y = corners[0][j][k].pt.y;
       real z = corners[0][j][k].pt.z;
       real f_along_x(real t) { return f((t, y, z)); }
       for (int i = 0; i < nx; ++i) {
         if (xdirzeros[i][j][k] != null) continue;
         evaluatedpoint start = corners[i][j][k];
         evaluatedpoint end = corners[i+1][j][k];
         if ((start.value > 0 && end.value > 0) || (start.value < 0 && end.value < 0))
           xdirzeros[i][j][k] = null;
         else {
           triple root = (0,y,z);
           root += X * findroot(f_along_x, start.pt.x, end.pt.x,
                                fa=start.value, fb=end.value);
           triple normal = grad(root);
           xdirzeros[i][j][k] = positionedvector(root, normal);
         }
       }
     }
   }

   for (int i = 0; i < nx+1; ++i) {
     for (int k = 0; k < nz+1; ++k) {
       real x = corners[i][0][k].pt.x;
       real z = corners[i][0][k].pt.z;
       real f_along_y(real t) { return f((x, t, z)); }
       for (int j = 0; j < ny; ++j) {
         if (ydirzeros[i][j][k] != null) continue;
         evaluatedpoint start = corners[i][j][k];
         evaluatedpoint end = corners[i][j+1][k];
         if ((start.value > 0 && end.value > 0) || (start.value < 0 && end.value < 0))
           ydirzeros[i][j][k] = null;
         else {
           triple root = (x,0,z);
           root += Y * findroot(f_along_y, start.pt.y, end.pt.y,
                                fa=start.value, fb=end.value);
           triple normal = grad(root);
           ydirzeros[i][j][k] = positionedvector(root, normal);
         }
       }
     }
   }

   for (int i = 0; i < nx+1; ++i) {
     for (int j = 0; j < ny+1; ++j) {
       real x = corners[i][j][0].pt.x;
       real y = corners[i][j][0].pt.y;
       real f_along_z(real t) { return f((x, y, t)); }
       for (int k = 0; k < nz; ++k) {
         if (zdirzeros[i][j][k] != null) continue;
         evaluatedpoint start = corners[i][j][k];
         evaluatedpoint end = corners[i][j][k+1];
         if ((start.value > 0 && end.value > 0) || (start.value < 0 && end.value < 0))
           zdirzeros[i][j][k] = null;
         else {
           triple root = (x,y,0);
           root += Z * findroot(f_along_z, start.pt.z, end.pt.z,
                                fa=start.value, fb=end.value);
           triple normal = grad(root);
           zdirzeros[i][j][k] = positionedvector(root, normal);
         }
       }
     }
   }
 }

 // Fill in the grid vertices and the zeros along edges. Each cube starts at
 // depth one and the depth increases each time it subdivides; maxdepth is the
 // maximum subdivision depth. When a cube at maxdepth cannot be resolved to
 // patches, it is left empty.
 void operator init(int nx, int ny, int nz,
                    real f(triple), triple a, triple b,
                    int maxdepth = 6, bool usetriangles) {
   this.nx = nx;
   this.ny = ny;
   this.nz = nz;
   grad = nGrad(f);
   this.f = f;
   this.maxdepth = maxdepth;
   this.usetriangles = usetriangles;
   corners = make3dgrid(a, b, nx, ny, nz, f);
   xdirzeros = new positionedvector[nx][ny+1][nz+1];
   ydirzeros = new positionedvector[nx+1][ny][nz+1];
   zdirzeros = new positionedvector[nx+1][ny+1][nz];

   for (int i = 0; i <= nx; ++i) {
     for (int j = 0; j <= ny; ++j) {
       for (int k = 0; k <= nz; ++k) {
         if (i < nx) xdirzeros[i][j][k] = null;
         if (j < ny) ydirzeros[i][j][k] = null;
         if (k < nz) zdirzeros[i][j][k] = null;
       }
     }
   }

   fillzeros();
 }

 // Doubles nx, ny, and nz by halving the sizes of the cubes along the x, y, and z
 // directions (resulting in 8 times as many cubes). Already existing data about
 // function values and zeros is copied; vertices and edges with no such pre-existing
 // data are populated.
 //
 // Returns true if subdivide succeeded, false if it failed (because maxdepth
 // was exceeded).
 bool subdivide() {
   if (maxdepth <= 1) {
     return false;
   }
   --maxdepth;
   triple a = corners[0][0][0];
   triple b = corners[nx][ny][nz];
   nx *= 2;
   ny *= 2;
   nz *= 2;
   evaluatedpoint[][][] oldcorners = corners;
   corners = new evaluatedpoint[nx+1][ny+1][nz+1];
   for (int i = 0; i <= nx; ++i) {
     for (int j = 0; j <= ny; ++j) {
       for (int k = 0; k <= nz; ++k) {
         if (i % 2 == 0 && j % 2 == 0 && k % 2 == 0) {
           corners[i][j][k] = oldcorners[quotient(i,2)][quotient(j,2)][quotient(k,2)];
         } else {
           triple pt = (interp(a.x, b.x, i/nx),
                        interp(a.y, b.y, j/ny),
                        interp(a.z, b.z, k/nz));
           real value = f(pt);
           if (value == 0) value = 1e-5;
           corners[i][j][k] = evaluatedpoint(pt, value);
         }
       }
     }
   }

   positionedvector[][][] oldxdir = xdirzeros;
   xdirzeros = new positionedvector[nx][ny+1][nz+1];
   for (int i = 0; i < nx; ++i) {
     for (int j = 0; j < ny + 1; ++j) {
       for (int k = 0; k < nz + 1; ++k) {
         if (j % 2 != 0 || k % 2 != 0) {
           xdirzeros[i][j][k] = null;
         } else {
           positionedvector zero = oldxdir[quotient(i,2)][quotient(j,2)][quotient(k,2)];
           if (zero == null) {
             xdirzeros[i][j][k] = null;
             continue;
           }
           real x = zero.position.x;
           if (x > interp(a.x, b.x, i/nx) && x < interp(a.x, b.x, (i+1)/nx)) {
             xdirzeros[i][j][k] = zero;
           } else {
             xdirzeros[i][j][k] = null;
           }
         }
       }
     }
   }

   positionedvector[][][] oldydir = ydirzeros;
   ydirzeros = new positionedvector[nx+1][ny][nz+1];
   for (int i = 0; i < nx+1; ++i) {
     for (int j = 0; j < ny; ++j) {
       for (int k = 0; k < nz + 1; ++k) {
         if (i % 2 != 0 || k % 2 != 0) {
           ydirzeros[i][j][k] = null;
         } else {
           positionedvector zero = oldydir[quotient(i,2)][quotient(j,2)][quotient(k,2)];
           if (zero == null) {
             ydirzeros[i][j][k] = null;
             continue;
           }
           real y = zero.position.y;
           if (y > interp(a.y, b.y, j/ny) && y < interp(a.y, b.y, (j+1)/ny)) {
             ydirzeros[i][j][k] = zero;
           } else {
             ydirzeros[i][j][k] = null;
           }
         }
       }
     }
   }

   positionedvector[][][] oldzdir = zdirzeros;
   zdirzeros = new positionedvector[nx+1][ny+1][nz];
   for (int i = 0; i < nx + 1; ++i) {
     for (int j = 0; j < ny + 1; ++j) {
       for (int k = 0; k < nz; ++k) {
         if (i % 2 != 0 || j % 2 != 0) {
           zdirzeros[i][j][k] = null;
         } else {
           positionedvector zero = oldzdir[quotient(i,2)][quotient(j,2)][quotient(k,2)];
           if (zero == null) {
             zdirzeros[i][j][k] = null;
             continue;
           }
           real z = zero.position.z;
           if (z > interp(a.z, b.z, k/nz) && z < interp(a.z, b.z, (k+1)/nz)) {
             zdirzeros[i][j][k] = zero;
           } else {
             zdirzeros[i][j][k] = null;
           }
         }
       }
     }
   }

   fillzeros();
   return true;
 }

 // Forward declaration of the draw method, which will be called by drawcube().
 patch[] draw(bool[] reportactive = null);

 // Construct the patches, assuming that we are working
 // with a single cube (nx = ny = nz = 1). This method will subdivide the
 // cube if necessary. The parameter reportactive should be an array of
 // length 6. Setting an entry to true indicates that the surface abuts the
 // corresponding face (according to the earlier enum), and thus that the
 // algorithm should be sure that something is drawn in the cube sharing
 // that face--even if all the vertices of that cube have the same sign.
 patch[] drawcube(bool[] reportactive = null) {
   // First, determine which edges (if any) actually have zeros on them.
   edge[] zeroedges = new edge[0];
   positionedvector[] zeros = new positionedvector[0];

   int currentface, nextface;

   void pushifnonnull(positionedvector v) {
     if (v != null) {
       zeroedges.push(edge(currentface, nextface));
       zeros.push(v);
     }
   }
   positionedvector findzero(int face1, int face2) {
     edge e = edge(face1, face2);
     for (int i = 0; i < zeroedges.length; ++i) {
       if (zeroedges[i] == e) return zeros[i];
     }
     return null;
   }

   currentface = XLOW;
   nextface = YHIGH;
   pushifnonnull(zdirzeros[0][1][0]);
   nextface = YLOW;
   pushifnonnull(zdirzeros[0][0][0]);
   nextface = ZHIGH;
   pushifnonnull(ydirzeros[0][0][1]);
   nextface = ZLOW;
   pushifnonnull(ydirzeros[0][0][0]);

   currentface = XHIGH;
   nextface = YHIGH;
   pushifnonnull(zdirzeros[1][1][0]);
   nextface = YLOW;
   pushifnonnull(zdirzeros[1][0][0]);
   nextface = ZHIGH;
   pushifnonnull(ydirzeros[1][0][1]);
   nextface = ZLOW;
   pushifnonnull(ydirzeros[1][0][0]);

   currentface = YHIGH;
   nextface = ZHIGH;
   pushifnonnull(xdirzeros[0][1][1]);
   currentface = ZHIGH;
   nextface = YLOW;
   pushifnonnull(xdirzeros[0][0][1]);
   currentface = YLOW;
   nextface = ZLOW;
   pushifnonnull(xdirzeros[0][0][0]);
   currentface = ZLOW;
   nextface = YHIGH;
   pushifnonnull(xdirzeros[0][1][0]);

   //Now, string those edges together to make a circle.

   patch[] subdividecube() {
     if (!subdivide()) {
       return new patch[0];
     }
     return draw(reportactive);
   }
   if (zeroedges.length < 3) {
     return subdividecube();
   }
   int[] faceorder = makecircle(zeroedges);
   if (alias(faceorder,null)) {
     return subdividecube();
   }
   positionedvector[] patchcorners = new positionedvector[0];
   for (int i = 0; i < faceorder.length; ++i) {
     patchcorners.push(findzero(faceorder[i], faceorder[i+1]));
   }
   patchcorners.cyclic = true;

   //Now, produce the cyclic path around the edges.
   path3 edgecycle;
   for (int i = 0; i < faceorder.length; ++i) {
     path3 currentpath = pathinface(patchcorners[i], patchcorners[i+1],
                                    faceorder[i+1], faceorder[i],
                                    faceorder[i+2]);
     triple testpoint = point(currentpath, 0.5);
     if (!checkpt(testpoint, f, grad, corners[0][0][0], corners[1][1][1])) {
       return subdividecube();
     }

     edgecycle = edgecycle & currentpath;
   }
   edgecycle = edgecycle & cycle;


   {  // Ensure the outward normals are pointing in the same direction as the gradient.
     triple tangentin = patchcorners[0].position - precontrol(edgecycle, 0);
     triple tangentout = postcontrol(edgecycle, 0) - patchcorners[0].position;
     triple normal = cross(tangentin, tangentout);
     if (dot(normal, patchcorners[0].direction) < 0) {
       edgecycle = reverse(edgecycle);
       patchcorners = patchcorners[-sequence(patchcorners.length)];
       patchcorners.cyclic = true;
     }
   }

   patch[] toreturn = quadpatches(edgecycle, patchcorners, f, grad,
                                  corners[0][0][0], corners[1][1][1], usetriangles);
   if (alias(toreturn, null)) return subdividecube();
   return toreturn;
 }

 // Extracts the specified cube as a gridwithzeros object with
 // nx = ny = nz = 1.
 gridwithzeros getcube(int i, int j, int k) {
   gridwithzeros cube = new gridwithzeros;
   cube.grad = grad;
   cube.f = f;
   cube.nx = 1;
   cube.ny = 1;
   cube.nz = 1;
   cube.maxdepth = maxdepth;
   cube.usetriangles = usetriangles;
   cube.corners = slice(corners,i,i+2,j,j+2,k,k+2);
   cube.xdirzeros = slice(xdirzeros,i,i+1,j,j+2,k,k+2);
   cube.ydirzeros = slice(ydirzeros,i,i+2,j,j+1,k,k+2);
   cube.zdirzeros = slice(zdirzeros,i,i+2,j,j+2,k,k+1);
   return cube;
 }

 // Returns an array of patches representing the surface.
 // The parameter reportactive should be an array of
 // length 6. Setting an entry to true indicates that the surface abuts the
 // corresponding face of the cube that bounds the entire grid.
 //
 // If reportactive == null, it is assumed that this is a top-level call;
 // a dot is printed to stdout for each cube drawn as a very rough
 // progress indicator.
 //
 // If reportactive != null, then it is assumed that the caller had a strong
 // reason to believe that this grid contains a part of the surface; the
 // grid will subdivide all the way to maxdepth if necessary to find points
 // on the surface.
 draw = new patch[](bool[] reportactive = null) {
   if (alias(reportactive, null)) progress(true);
   // A list of all the patches not already drawn but known
   // to contain part of the surface. This "queue" is
   // actually implemented as stack for simplicity, since
   // it does not make any difference. In a multi-threaded
   // version of the algorithm, a queue (shared across all threads)
   // would make more sense than a stack.
   triple[] queue = new triple[0];
   bool[][][] enqueued = new bool[nx][ny][nz];
   for (int i = 0; i < enqueued.length; ++i) {
     for (int j = 0; j < enqueued[i].length; ++j) {
       for (int k = 0; k < enqueued[i][j].length; ++k) {
         enqueued[i][j][k] = false;
       }
     }
   }

   void enqueue(int i, int j, int k) {
     if (i >= 0 && i < nx
         && j >= 0 && j < ny
         && k >= 0 && k < nz
         && !enqueued[i][j][k]) {
       queue.push((i,j,k));
       enqueued[i][j][k] = true;
     }
     if (!alias(reportactive, null)) {
       if (i < 0) reportactive[XLOW] = true;
       if (i >= nx) reportactive[XHIGH] = true;
       if (j < 0) reportactive[YLOW] = true;
       if (j >= ny) reportactive[YHIGH] = true;
       if (k < 0) reportactive[ZLOW] = true;
       if (k >= nz) reportactive[ZHIGH] = true;
     }
   }

   for (int i = 0; i < nx+1; ++i) {
     for (int j = 0; j < ny+1; ++j) {
       for (int k = 0; k < nz+1; ++k) {
         if (i < nx && xdirzeros[i][j][k] != null) {
           for (int jj = j-1; jj <= j; ++jj)
             for (int kk = k-1; kk <= k; ++kk)
               enqueue(i, jj, kk);
         }
         if (j < ny && ydirzeros[i][j][k] != null) {
           for (int ii = i-1; ii <= i; ++ii)
             for (int kk = k-1; kk <= k; ++kk)
               enqueue(ii, j, kk);
         }
         if (k < nz && zdirzeros[i][j][k] != null) {
           for (int ii = i-1; ii <= i; ++ii)
             for (int jj = j-1; jj <= j; ++jj)
               enqueue(ii, jj, k);
         }
       }
     }
   }

   if (!alias(reportactive, null) && queue.length == 0) {
     if (subdivide()) return draw(reportactive);
   }

   patch[] surface = new patch[0];

   while (queue.length > 0) {
     triple coord = queue.pop();
     int i = floor(coord.x);
     int j = floor(coord.y);
     int k = floor(coord.z);
     bool[] reportface = array(6, false);
     patch[] toappend = getcube(i,j,k).drawcube(reportface);
     if (reportface[XLOW]) enqueue(i-1,j,k);
     if (reportface[XHIGH]) enqueue(i+1,j,k);
     if (reportface[YLOW]) enqueue(i,j-1,k);
     if (reportface[YHIGH]) enqueue(i,j+1,k);
     if (reportface[ZLOW]) enqueue(i,j,k-1);
     if (reportface[ZHIGH]) enqueue(i,j,k+1);
     surface.append(toappend);
     if (alias(reportactive, null)) progress();
   }
   if (alias(reportactive, null)) progress(false);
   return surface;
 };
}

// The external interface of this whole module. Accepts exactly one
// function (throws an error if two or zero functions are specified).
// The function should be differentiable. (Whatever you do, do not
// pass in an indicator function!) Ideally, the zero locus of the
// function should be smooth; singularities will significantly slow
// down the algorithm and potentially give bad results.
//
// Returns a plot of the zero locus of the function within the
// rectangular solid with opposite corners at a and b.
//
// Additional parameters:
// n - the number of initial segments in each of the x, y, z directions.
// overlapedges - if true, the patches of the surface are slightly enlarged
//     to compensate for an artifact in which the viewer can see through the
//     boundary between patches. (Some of this may actually be a result of
//     edges not lining up perfectly, but I'm fairly sure a lot of it arises
//     purely as a rendering artifact.)
// nx - override n in the x direction
// ny - override n in the y direction
// nz - override n in the z direction
// maxdepth - the maximum depth to which the algorithm will subdivide in
//     an effort to find patches that closely approximate the true surface.
surface implicitsurface(real f(triple) = null, real ff(real,real,real) = null,
                       triple a, triple b,
                       int n = nmesh,
                       bool keyword overlapedges = false,
                       int keyword nx=n, int keyword ny=n,
                       int keyword nz=n,
                       int keyword maxdepth = 8,
                       bool keyword usetriangles=true) {
 if (f == null && ff == null)
   abort("implicitsurface called without specifying a function.");
 if (f != null && ff != null)
   abort("Only specify one function when calling implicitsurface.");
 if (f == null) f = new real(triple w) { return ff(w.x, w.y, w.z); };
 gridwithzeros grid = gridwithzeros(nx, ny, nz, f, a, b, maxdepth=maxdepth,
                                    usetriangles=usetriangles);
 patch[] patches = grid.draw();
 if (overlapedges) {
   for (int i = 0; i < patches.length; ++i) {
     triple center = (patches[i].triangular ?
                      patches[i].point(1/3, 1/3) : patches[i].point(1/2,1/2));
     transform3 T=shift(center) * scale3(1.03) * shift(-center);
     patches[i] = T * patches[i];
   }
 }
 return surface(...patches);
}