#
#--> linsysplot( E, x=a..b, y=c..d, z=e..f, ... )
#
# Graph the linear system E in x,y,z on [a..b,c..d,e..f] in 3D.
# The equations have to be input either in the form
#
#    a*x+b*y+c*z=d or a*x+b*y+c*z-d  where a,b,c,d are real numbers.
#
# Each linear equation is the equation of a plane in 3D space.
# We draw the plane of each equation in a different color, and we label each
# plane by its equation.  For each pair of equations we compute the line of
# intersection, if any, and draw that in black.
# Then you will be able to see visually the solution set of the system.
# If there are three equations with a unique solution, and the solution
# lies inside [a..b,c..d,e..f] then it will be the point of intersection
# of the three planes and also of the three black lines.
# Interactively rotating the graph will help one to see the planes and
# their intersections.  Also changing to style wireframe will help.
#
# o There may be one or more equations.
# o The optional argument colors = [red, green, blue, ...] may be given
#
# Author: Michael Monagan
# July 2002.

linsysplot3d := 
proc(sys::{set({algebraic,equation}),list({algebraic,equation})},
     xrange::name=numeric..numeric,
     yrange::name=numeric..numeric,
     zrange::name=numeric..numeric)
local x,y,z,xval,yval,zval,i,j,k,eqns,unks,opts,cols,dist,b,c,e,s,m,n,
     lines,planes,texts,sols,domain,boundaries,remaining;

x := lhs(xrange);
y := lhs(yrange);
z := lhs(zrange);
unks := {x,y,z};
eqns := [op(sys)]; # preserve ordering of input
eqns := map(proc(e) if type(e,`=`) then lhs(e)-rhs(e) else e fi end, eqns);
n := nops(eqns);
for e in eqns do
    if not type(e,linear(unks)) then ERROR("equations must be linear",e) fi;
od;
cols := [seq( COLOR(HUE,1.0*i/n), i=1..n )]; # default colours
opts := [args[5..nargs]]; # look through options for user defined colours
for i to nops(opts) do
    if member(lhs(opts[i]),{color,colors,colour,colours}) then
       cols := op(2,opts[i]);
       opts := subsop(i=NULL,opts);
       if not type(cols,{list,set}) then cols := [cols$n]
       elif not nops(cols)=n then ERROR("wrong number of colours",cols)
       fi;
       break;
    fi;
od;
lines := NULL; # these are the lines of intersection (drawn in black)
for i from 1 to n-1 do
    for j from i+1 to n do
        sols := solve( {eqns[i],eqns[j]}, {x,y,z} );
        if sols=NULL then next fi; # planes do not intersect
        sols := remove( evalb, sols );
        if nops(sols)=2 then # the intersection is a line
           if map(lhs,sols)={y,z} then domain := xrange
           elif map(lhs,sols)={x,z} then domain := yrange
           elif map(lhs,sols)={x,y} then domain := zrange
           else ERROR("bug",sols);
           fi;
	   # The following gives the parametrization of the line
           sols := subs(sols,[x,y,z]); 
           lines := lines, plots[spacecurve]( sols, domain,
               thickness=3, shading=none, colour=black ); 
        fi
   od
od;
domain := xrange,yrange,zrange;
planes := NULL;
texts := NULL;
for i to n do
    boundaries := NULL;
    for xval in [op(rhs(xrange))] do
    for yval in [op(rhs(yrange))] do
        zval := [traperror(fsolve( subs(x=xval,y=yval,eqns[i]), z ))];
        if not type(zval,[numeric]) then next fi;
        zval := zval[1];
        if zval >= lhs(rhs(zrange)) and zval <= rhs(rhs(zrange)) then
           boundaries := boundaries, [xval,yval,zval]
        fi;
    od od;
    for xval in [op(rhs(xrange))] do
    for zval in [op(rhs(zrange))] do
        yval := [traperror(fsolve( subs(x=xval,z=zval,eqns[i]), y ))];
        if not type(yval,[numeric]) then next fi;
        yval := yval[1];
        if yval >= lhs(rhs(yrange)) and yval <= rhs(rhs(yrange)) then 
           boundaries := boundaries, [xval,yval,zval]
        fi;
    od od;
    for yval in [op(rhs(yrange))] do
    for zval in [op(rhs(zrange))] do
        xval := [traperror(fsolve( subs(y=yval,z=zval,eqns[i]), x ))];
        if not type(xval,[numeric]) then next fi;
        xval := xval[1];
        if xval >= lhs(rhs(xrange)) and xval <= rhs(rhs(xrange)) then 
           boundaries := boundaries, [xval,yval,zval]
        fi;
    od od;
    # The points computed on the boundary lie in the same plane but they
    # may not be convex.  To put then in convex order we sort them by
    # starting with the first point then repeatedly choosing the closest.
    b := [boundaries];
    boundaries := [b[1]];
    remaining := b[2..-1];
    while remaining <> [] do
         c := boundaries[-1];
         dist := seq( add((b[k]-c[k])^2,k=1..3), b=remaining );
         m := min(dist); member(m,[dist],'k');
         boundaries := [op(boundaries),remaining[k]];
         remaining := subsop(k=NULL,remaining);
    od;
    if nops(boundaries) < 3 then next fi;
    boundaries := [op(boundaries),boundaries[1]];
    planes := planes, plottools[polygon]( boundaries, color=cols[i], op(opts) );
    s := convert(sys[i],string);  # use equation as input by user
    texts := texts, plots[textplot3d]([op(boundaries[1]), s]);
od:
plots[display3d]( [planes,lines,texts], 
                  labels=[convert(x,string),convert(y,string),convert(z,string)],
                  view=[rhs(xrange),rhs(yrange),rhs(zrange)],
                  op(opts) );

end:

#save `plots/linsysplot3d`, `../linsysplot3d.m`;
#savelib(`plots/linsysplot3d`);

#interface(plotdevice=x11);
#sys := {x+y+z=0,x-y+z=1,y-z=2}:
#linsysplot3d(sys,x=-5..5,y=-5..5,z=-5..5);
#sys := {x+y+z=0,x-y+z=1,x+z=2}:
#linsysplot3d(sys,x=-5..5,y=-5..5,z=-5..5,colours=[cyan,magenta,yellow]);
