/*
  Aoi.java
  v0.01 Experimenting with 3D concepts and JDK.
  v0.02 Wireframe implementation w/o visibility detection.
  v0.03 Polygon graphics w/ BSP-tree visibility detection.
  v0.04 Object descriptions read from a file.
  Written by Tero Hasu.
  */

import java.awt.*;
import java.io.*;
import java.net.*;

public class Aoi extends java.applet.Applet
{
  public
  static void main(String args[])
  {
    if(args.length==0)
    {
      System.err.println("USAGE: java Aoi filename/URL");
      return;
    }
    isApplet=false;
    new AppletFrame(new Aoi(),XSIZE,YSIZE,args);
  }

  public
  void init()
  {
    String fileName = getParameter("file");
    if(fileName == null) {
      System.err.println("IconView: Missing filename");
      return; // no file name given
    }
    if(isApplet)
    {
      // An applet usually cannot access files directly, so we will
      // consider 'fileName' to contain an URL by default.
      if(!(fileName.length()>7
	   && (fileName.substring(0,7).equals("file://")
	       ||fileName.substring(0,7).equals("http://"))))
      {
	// getDocumentBase() returns URL of the html-file containing
	// the applet tag.
	// getCodeBase() seems to return the directory containing the applet.
	fileName=getCodeBase()+fileName;
      }
    }

    setLayout(new BorderLayout()); // important if using "Center", etc.

    World world = new World();

    try
      world.Load(fileName);
    catch(SyntaxException e) {
      System.err.println("Syntax error encountered");
    }
    catch(MalformedURLException e) {
      System.err.println("Malformed URL");
    }
    catch(IOException e) {
      System.err.println("IO exception");
    }

    worldView = new WorldView(world,XSIZE,YSIZE);

    display = new Display(worldView);
    add("Center",display);
  }  

  public
  boolean handleEvent(Event evt)
  {
    if(evt.id == Event.WINDOW_DESTROY) System.exit(0);
    return super.handleEvent(evt);
  }

  public
  boolean keyDown(Event evt, int key)
  {
    if((evt.modifiers&Event.SHIFT_MASK)!=0)
    {
      if(key==Event.UP) {worldView.MoveIn(); display.ViewChanged();}
      else if(key==Event.DOWN) {worldView.MoveOut(); display.ViewChanged();}
      else if(key=='Z') {worldView.ZoomOut(); display.ViewChanged();}
      else return false;
    }
    else
    {
      if(key==Event.LEFT) {worldView.MoveLeft(); display.ViewChanged();}
      else if(key==Event.RIGHT) {worldView.MoveRight(); display.ViewChanged();}
      else if(key==Event.UP) {worldView.MoveUp(); display.ViewChanged();}
      else if(key==Event.DOWN) {worldView.MoveDown(); display.ViewChanged();}
      else if(key=='z') {worldView.ZoomIn(); display.ViewChanged();}
      else if(key=='j') {
	worldView.RotateCounterClockwiseAboutYAxis();
	display.ViewChanged();}
      else if(key=='k') {
	worldView.RotateClockwiseAboutYAxis();
	display.ViewChanged();}
      else if(key=='i') {
	worldView.RotateCounterClockwiseAboutXAxis();
	display.ViewChanged();}
      else if(key=='m') {
	worldView.RotateClockwiseAboutXAxis();
	display.ViewChanged();}
      else if(key=='d') worldView.DebugInfo();
      else return false;
    }
    return true;
  }

  private
  static boolean isApplet = true;

  private
  static final int XSIZE=301, YSIZE=301;

  private
  World world;

  private
  WorldView worldView;

  private
  Display display;
}

// Decides which parts of given world view will be visible,
// and then updates itself (canvas) with a two-dimensional
// version of the world view.
class Display extends Canvas
{
  public
  Display()
  {
    worldView = null;
  }

  public
  Display(WorldView wV)
  {
    worldView = wV;
    DetermineVisibility();
  }

  public
  void ViewChanged()
  {
    DetermineVisibility();
    repaint();
  }

  public
  void ViewChanged(WorldView vW)
  {
    worldView = vW;
    ViewChanged();
  }

  /* BSP-tree method is used to figure out which polygons are
     visible and which are not. */
  public
  void DetermineVisibility()
  {
    SinglyLinkedList polys = new SinglyLinkedList();
    PolygonSurface pS;
    for(worldView.polygonSurfaces.PositionHead();
	(pS=(PolygonSurface)worldView.polygonSurfaces.Current()) != null;
	worldView.polygonSurfaces.PositionNext())
      polys.Append(pS);
    bspTree = new BSPTree(polys);
    bspTree.Partition(worldView.polygonSurfaces);
  }

  public
  void paint(Graphics g)
  {
    /* Borders (well, not really) and x'-axis and y'-axis.
     */
    g.setColor(Color.yellow);
    g.drawRect(0,0,worldView.xPixels-1,worldView.yPixels-1);
    g.drawLine(worldView.xPixelsDiv2,0,worldView.xPixelsDiv2,
	       worldView.yPixels-1);
    g.drawLine(0,worldView.yPixelsDiv2,worldView.xPixels-1,
	       worldView.yPixelsDiv2);
    /* Now draw worldView.
     */
    bspTree.Draw(g);
  }

  private
  WorldView worldView;

  private
  BSPTree bspTree = null;
}

class BSPTree
{
  public
  BSPTree(SinglyLinkedList polygons)
  {
    root = new BSPTreeNode(polygons);
  }
  public
  void Draw(Graphics g)
  {
    if(!root.Empty())
      root.Draw(g);
  }
  public
  // Argument is actually a list of PolygonSurfaces.
  void Partition(SinglyLinkedList planes)
  {
    //System.out.println("Partitioning with "+planes.length+" planes");
    if(!root.Empty())
    {
      // Go through all of the partitioning planes.
      PolygonSurface pS;
      for(planes.PositionHead();
	  (pS=(PolygonSurface)planes.Current()) != null;
	  planes.PositionNext())
      {
	// Now, go through all of the nodes of the tree in depth-first order,
	// partitioning all nodes with something in them, splitting
	// the node only if necessary.
	//System.out.println("Partitioning with plane ("+planes.length+")");
	//pS.Debug();
	//System.out.println("Using plane (a="+pS.a+" b="+pS.b+" c="+pS.c+
	//		   " d="+pS.d+") to partition");
	Split(root,pS);
      }
    }
  }
  private
  void Split(BSPTreeNode node, PolygonSurface plane)
  {
    if(node.contents != null)
    {
      SinglyLinkedList frontPolys = new SinglyLinkedList();
      SinglyLinkedList behindPolys = new SinglyLinkedList();
      PolygonSurface pS;
      for(node.contents.PositionHead();
	  (pS=(PolygonSurface)node.contents.Current()) != null;
	  node.contents.PositionNext())
      {
	int pos=plane.Position(pS);
	//System.out.println(pos+"=plane.Position(pS)");
	//System.out.println("Where pS is");
	//pS.Debug();
	//System.out.println("and plane is");
	//plane.Debug();
	if((pos&pS.FRONT)!=0 && (pos&pS.BEHIND)!=0)
	{
	  // Splitting the polygon is required.
	  pS.Split(plane,frontPolys,behindPolys);
	} else if((pos&pS.BEHIND)!=0) {
	  //System.out.println("Polygon is behind the plane.");
	  behindPolys.Append(pS);
	} else {
	  //System.out.println("Polygon is on or in front of the plane"
	  //		     +" or on its side.");
	  frontPolys.Append(pS);
	}
      }
      if(frontPolys.length>0 && behindPolys.length>0)
      {
	node.contents = null;
	node.front = new BSPTreeNode(frontPolys);
	node.back = new BSPTreeNode(behindPolys);
      }
    } else {
      Split(node.front,plane);
      Split(node.back,plane);
    }
  }
  /*
    Property.
    */
  private
  BSPTreeNode root;
}

class BSPTreeNode
{
  public
  BSPTreeNode()
  {
    contents = null;
    front = null;
    back = null;
  }
  public
  BSPTreeNode(SinglyLinkedList con)
  {
    contents = con;
    front = null;
    back = null;
  }
  public
  void Draw(Graphics g)
  {
    if(contents!=null)
    {
      PolygonSurface pS;
      for(contents.PositionHead();
	  (pS=(PolygonSurface)contents.Current()) != null;
	  contents.PositionNext())
      {
	pS.Draw(g);
      }
    } else {
      front.Draw(g);
      back.Draw(g);
    }
  }
  public
  boolean Empty()
  {
    return (contents==null) && (front==null) && (back==null);
  }
  /*
    Property.
    */
  public
  SinglyLinkedList contents;
  public
  BSPTreeNode front;
  public
  BSPTreeNode back;
}

/* A particular view into the world. The methods of this class handle
   changes of viewing position and direction, as well as zooming.
   */
class WorldView
{
  public
  WorldView(World w, int x, int y)
  {
    world=w;
    xPixels = x;
    yPixels = y;
    xPixelsDiv2 = xPixels/2;
    yPixelsDiv2 = yPixels/2;
    origo = new Point3(0,0,0);
    b1 = new Point3(1,0,0);
    b2 = new Point3(0,-1,0); // change direction of y-axis
    b3 = new Point3(0,0,1);
    SetViewTransformationMatrix();
    Update();
  }

  public
  void SetViewTransformationMatrix()
  {
    /* Create the coordinate transformation matrix for
       the current values of origo and b1, b2, b3.
       */
    Matrix4x4 translationMatrix = new Matrix4x4();
    translationMatrix.UnitMatrix();
    translationMatrix.m[0][3] = origo.x;
    translationMatrix.m[1][3] = origo.y;
    translationMatrix.m[2][3] = origo.z;
    rotationMatrix = new Matrix4x4(b1.x,b1.y,b1.z,0,
				   b2.x,b2.y,b2.z,0,
				   b3.x,b3.y,b3.z,0,
				   0,0,0,1);

    Matrix4x4 deviceTransformationMatrix =
      new Matrix4x4(pixelsPerUnit,0,0,xPixelsDiv2,
		    0,pixelsPerUnit,0,yPixelsDiv2,
		    0,0,1,0,
		    0,0,0,1);

    // viewTransformationMatrix = 
    // deviceTransformationMatrix * rotationMatrix^(-1) * translationMatrix
    viewTransformationMatrix
      = (translationMatrix.LeftMult(rotationMatrix.Transpose())).
      LeftMult(deviceTransformationMatrix);
  }

  // Updates the list of polygons in viewing coordinates.
  public
  void Update()
  {
    // Might be faster to just modify the existing polygon list,
    // instead of replacing it.
    polygonSurfaces = new SinglyLinkedList();
    PolygonSurface pS;
    for(world.polygonSurfaces.PositionHead();
	(pS=(PolygonSurface)world.polygonSurfaces.Current()) != null;
	world.polygonSurfaces.PositionNext())
    {
      PolygonSurface vPS = pS.LeftMult(viewTransformationMatrix);
      if(vPS.Update())
	polygonSurfaces.Append(vPS);
      else
      {
	System.err.println("vPS.Update() failed!");
	vPS.Debug();
      }
    }
  }

  public
  void RotateClockwiseAboutXAxis()
  {
    // Modify b1, b2, b3.
    // Rotate each clockwise about X' axis.
    // No need for translation, just rotate for a few degrees.

    double b1y = b1.y;
    b1.y = (double)(b1y * Math.cos(-Math.PI/6) - b1.z * Math.sin(-Math.PI/6));
    b1.z = (double)(b1y * Math.sin(-Math.PI/6) + b1.z * Math.cos(-Math.PI/6));

    double b2y = b2.y;
    b2.y = (double)(b2y * Math.cos(-Math.PI/6) - b2.z * Math.sin(-Math.PI/6));
    b2.z = (double)(b2y * Math.sin(-Math.PI/6) + b2.z * Math.cos(-Math.PI/6));

    double b3y = b3.y;
    b3.y = (double)(b3y * Math.cos(-Math.PI/6) - b3.z * Math.sin(-Math.PI/6));
    b3.z = (double)(b3y * Math.sin(-Math.PI/6) + b3.z * Math.cos(-Math.PI/6));

    SetViewTransformationMatrix();
    Update();
  }

  public
  void RotateCounterClockwiseAboutXAxis()
  {
    double b1y = b1.y;
    b1.y = (double)(b1y * Math.cos(Math.PI/6) - b1.z * Math.sin(Math.PI/6));
    b1.z = (double)(b1y * Math.sin(Math.PI/6) + b1.z * Math.cos(Math.PI/6));

    double b2y = b2.y;
    b2.y = (double)(b2y * Math.cos(Math.PI/6) - b2.z * Math.sin(Math.PI/6));
    b2.z = (double)(b2y * Math.sin(Math.PI/6) + b2.z * Math.cos(Math.PI/6));

    double b3y = b3.y;
    b3.y = (double)(b3y * Math.cos(Math.PI/6) - b3.z * Math.sin(Math.PI/6));
    b3.z = (double)(b3y * Math.sin(Math.PI/6) + b3.z * Math.cos(Math.PI/6));

    SetViewTransformationMatrix();
    Update();
  }

  public
  void RotateClockwiseAboutYAxis()
  {
    double b1x = b1.x;
    b1.x = (double)(b1.z * Math.sin(Math.PI/6) + b1x * Math.cos(Math.PI/6));
    b1.z = (double)(b1.z * Math.cos(Math.PI/6) - b1x * Math.sin(Math.PI/6));

    double b2x = b2.x;
    b2.x = (double)(b2.z * Math.sin(Math.PI/6) + b2x * Math.cos(Math.PI/6));
    b2.z = (double)(b2.z * Math.cos(Math.PI/6) - b2x * Math.sin(Math.PI/6));

    double b3x = b3.x;
    b3.x = (double)(b3.z * Math.sin(Math.PI/6) + b3x * Math.cos(Math.PI/6));
    b3.z = (double)(b3.z * Math.cos(Math.PI/6) - b3x * Math.sin(Math.PI/6));

    SetViewTransformationMatrix();
    Update();
  }

  public
  void RotateCounterClockwiseAboutYAxis()
  {
    double b1x = b1.x;
    b1.x = (double)(b1.z * Math.sin(-Math.PI/6) + b1x * Math.cos(-Math.PI/6));
    b1.z = (double)(b1.z * Math.cos(-Math.PI/6) - b1x * Math.sin(-Math.PI/6));

    double b2x = b2.x;
    b2.x = (double)(b2.z * Math.sin(-Math.PI/6) + b2x * Math.cos(-Math.PI/6));
    b2.z = (double)(b2.z * Math.cos(-Math.PI/6) - b2x * Math.sin(-Math.PI/6));

    double b3x = b3.x;
    b3.x = (double)(b3.z * Math.sin(-Math.PI/6) + b3x * Math.cos(-Math.PI/6));
    b3.z = (double)(b3.z * Math.cos(-Math.PI/6) - b3x * Math.sin(-Math.PI/6));

    SetViewTransformationMatrix();
    Update();
  }

  public
  void MoveRight()
  {
    Point4 a = new Point4(-0.25,0,0,0);
    Point4 b = a.LeftMult(rotationMatrix);
    origo.x += b.v[0];
    origo.y += b.v[1];
    origo.z += b.v[2];
    SetViewTransformationMatrix();
    Update();
  }

  public
  void MoveLeft()
  {
    Point4 a = new Point4(0.25,0,0,0);
    Point4 b = a.LeftMult(rotationMatrix);
    origo.x += b.v[0];
    origo.y += b.v[1];
    origo.z += b.v[2];
    SetViewTransformationMatrix();
    Update();
  }

  public
  void MoveUp()
  {
    Point4 a = new Point4(0,0.25,0,0);
    Point4 b = a.LeftMult(rotationMatrix);
    origo.x += b.v[0];
    origo.y += b.v[1];
    origo.z += b.v[2];
    SetViewTransformationMatrix();
    Update();
  }

  public
  void MoveDown()
  {
    Point4 a = new Point4(0,-0.25,0,0);
    Point4 b = a.LeftMult(rotationMatrix);
    origo.x += b.v[0];
    origo.y += b.v[1];
    origo.z += b.v[2];
    SetViewTransformationMatrix();
    Update();
  }

  public
  void MoveOut()
  {
    Point4 a = new Point4(0,0,-0.25,0);
    Point4 b = a.LeftMult(rotationMatrix);
    origo.x += b.v[0];
    origo.y += b.v[1];
    origo.z += b.v[2];
    SetViewTransformationMatrix();
    Update();
  }

  public
  void MoveIn()
  {
    Point4 a = new Point4(0,0,0.25,0);
    Point4 b = a.LeftMult(rotationMatrix);
    origo.x += b.v[0];
    origo.y += b.v[1];
    origo.z += b.v[2];
    SetViewTransformationMatrix();
    Update();
  }

  public
  void ZoomIn()
  {
    pixelsPerUnit /= zoomFactor; // zoomFactor < 1
    SetViewTransformationMatrix();
    Update();
  }

  public
  void ZoomOut()
  {
    pixelsPerUnit *= zoomFactor; // zoomFactor < 1
    SetViewTransformationMatrix();
    Update();
  }

  public
  void DebugInfo()
  {
    /*
    System.out.println("origo=("+origo.x+","+origo.y+","+origo.z+")");
    System.out.println("b1=("+b1.x+","+b1.y+","+b1.z+")");
    System.out.println("b2=("+b2.x+","+b2.y+","+b2.z+")");
    System.out.println("b3=("+b3.x+","+b3.y+","+b3.z+")");
    */
    /*
    Matrix4x4 m1 = new Matrix4x4(1,2,-3,4,2,-2,3,4,-3,2,1,3,1,1,1,1);
    Matrix4x4 m2 = new Matrix4x4(1,1,1,1,2,1,5,-1,-1,3,2,5,-2,2,-4,4);
    m1.Print();
    m2.Print();
    System.out.println("det(m1)="+m1.Determinant());
    System.out.println("det(m2)="+m2.Determinant()); // 30
    Matrix4x4 m1m2 = m2.LeftMult(m1);
    m1m2.Print();
    System.out.println("det(m1*m2)="+m1m2.Determinant());
    */
  }

  /* Property.
   */

  int xPixels = 201;        // x size of view area (odd value)
  int yPixels = 201;        // y size of view area (odd value)
  int xPixelsDiv2;
  int yPixelsDiv2;

  private
  World world;

  private
  Point3 origo;           /* Position of O relative to OŽ stated
			      in '-coordinates. Changes when moving. */
  private
  Point3 b1, b2, b3;      /* Unit vector, non-'-coordinate base vectors. */
  private
  Matrix4x4 rotationMatrix;
  private
  Matrix4x4 viewTransformationMatrix;

  private
  double pixelsPerUnit=40;   /* changes when zooming, unit is 1;
				yes, may be less than 1, we could be dealing
				with large numbers */
  private
  final double zoomFactor = 0.9;

  public
  SinglyLinkedList polygonSurfaces;
}

/* This class contains a description of a world, which currently isn't
   much, if anything, more than a list of polygons specified in
   the world coordinates. This is the data stored/retrieved to/from a file.
   */
class World
{
  public
  World()
  {
    polygonSurfaces = new SinglyLinkedList();
  }
  public
  void Load(String fileName)
       throws SyntaxException, MalformedURLException, IOException
  {
    DataInputStream in;

    if(fileName.length()>7 && fileName.substring(0,7).equals("http://")) {
      URL url = new URL(fileName);
      InputStream urlStream = url.openStream();
      in = new DataInputStream(urlStream);
    } else if(fileName.length()>7 &&
	      fileName.substring(0,7).equals("file://")) {
      FileInputStream fileStream
	= new FileInputStream(fileName.substring(7,fileName.length()));
      in = new DataInputStream(fileStream);
    } else {
      FileInputStream fileStream = new FileInputStream(fileName);
      in = new DataInputStream(fileStream);
    }

    /* Now read the object data.
     */
    String line;
    while((line=in.readLine()) != null)
    {
      //System.out.println(line);

      /* Skip comment lines. */
      if(line.length()==0) continue;
      if(line.charAt(0)=='%') continue;
      if(line.indexOf('(')==-1) continue;

      PolygonSurface poly = new PolygonSurface();
      Vertex firstVertex = null;
      Vertex lastVertex = null;
      int vertexCount = 0; // at least 3 required
      int from = 0;
      int i, j;
      while((i=line.indexOf('(',from)) != -1) {
	j=line.indexOf(')',i+1);
	if(j==-1) throw new SyntaxException();
	String coordStr = line.substring(i+1,j);
	Vertex vertex = new Vertex(coordStr);
	if(vertexCount == 0)
	  firstVertex = vertex;
	vertexCount++;
	if(lastVertex != null) {
	  Edge edge = new Edge(lastVertex,vertex);
	  poly.AddEdge(edge);
	}
	lastVertex = vertex;
	from = j+1;
      }
      if(vertexCount < 3)
	throw new SyntaxException();
      Edge edge = new Edge(lastVertex,firstVertex);
      poly.AddEdge(edge);

      if((i=line.indexOf('[')) != -1)
	if((j=line.indexOf(']',i+1)) != -1) {
	  String colorStr = line.substring(i+1,j);
	  if(colorStr.indexOf(',') != -1)
	    // RGB value given.
	    poly.color = GetRgbColor(colorStr);
	  else if(colorStr.equalsIgnoreCase("black"))
	    poly.color = Color.black;
	  else if(colorStr.equalsIgnoreCase("blue"))
	    poly.color = Color.blue;
	  else if(colorStr.equalsIgnoreCase("cyan"))
	    poly.color = Color.cyan;
	  else if(colorStr.equalsIgnoreCase("darkGray"))
	    poly.color = Color.darkGray;
	  else if(colorStr.equalsIgnoreCase("gray"))
	    poly.color = Color.gray;
	  else if(colorStr.equalsIgnoreCase("green"))
	    poly.color = Color.green;
	  else if(colorStr.equalsIgnoreCase("lightGray"))
	    poly.color = Color.lightGray;
	  else if(colorStr.equalsIgnoreCase("magenta"))
	    poly.color = Color.magenta;
	  else if(colorStr.equalsIgnoreCase("orange"))
	    poly.color = Color.orange;
	  else if(colorStr.equalsIgnoreCase("pink"))
	    poly.color = Color.pink;
	  else if(colorStr.equalsIgnoreCase("red"))
	    poly.color = Color.red;
	  else if(colorStr.equalsIgnoreCase("white"))
	    poly.color = Color.white;
	  else if(colorStr.equalsIgnoreCase("yellow"))
	    poly.color = Color.yellow;
	}

      polygonSurfaces.Append(poly);
    }

    in.close();
  }
  private
  Color GetRgbColor(String colorStr)
       throws SyntaxException
  {
    int i, j;
	    
    j = colorStr.indexOf(',');
    if(j==-1) throw new SyntaxException();
    String intStr = colorStr.substring(0,j);
    Integer integer = Integer.valueOf(intStr);
    int rVal = integer.intValue();

    i = colorStr.indexOf(',',j+1);
    if(i==-1) throw new SyntaxException();
    intStr = colorStr.substring(j+1,i);
    integer = Integer.valueOf(intStr);
    int gVal = integer.intValue();

    j = colorStr.length();
    intStr = colorStr.substring(i+1,j);
    integer = Integer.valueOf(intStr);
    int bVal = integer.intValue();

    return new Color(rVal,gVal,bVal);
  }
  public
  PolygonSurface AddTriangle(double x1, double y1, double z1,
			     double x2, double y2, double z2,
			     double x3, double y3, double z3)
  {
    Vertex v1 = new Vertex(x1,y1,z1);
    Vertex v2 = new Vertex(x2,y2,z2);
    Vertex v3 = new Vertex(x3,y3,z3);
    Edge e1 = new Edge(v1,v2);
    Edge e2 = new Edge(v2,v3);
    Edge e3 = new Edge(v3,v1);
    PolygonSurface poly = new PolygonSurface();
    poly.AddEdge(e1);
    poly.AddEdge(e2);
    poly.AddEdge(e3);
    polygonSurfaces.Append(poly);
    return poly;
  }
  public
  void AddTriangle(double x1, double y1, double z1,
		   double x2, double y2, double z2,
		   double x3, double y3, double z3, Color col)
  {
    AddTriangle(x1,y1,z1,x2,y2,z2,x3,y3,z3).color = col;
  }
  public
  PolygonSurface AddQuadrilateral(double x1, double y1, double z1,
				  double x2, double y2, double z2,
				  double x3, double y3, double z3,
				  double x4, double y4, double z4)
  {
    Vertex v1 = new Vertex(x1,y1,z1);
    Vertex v2 = new Vertex(x2,y2,z2);
    Vertex v3 = new Vertex(x3,y3,z3);
    Vertex v4 = new Vertex(x4,y4,z4);
    Edge e1 = new Edge(v1,v2);
    Edge e2 = new Edge(v2,v3);
    Edge e3 = new Edge(v3,v4);
    Edge e4 = new Edge(v4,v1);
    PolygonSurface poly = new PolygonSurface();
    poly.AddEdge(e1);
    poly.AddEdge(e2);
    poly.AddEdge(e3);
    poly.AddEdge(e4);
    polygonSurfaces.Append(poly);
    return poly;
  }
  public
  void AddQuadrilateral(double x1, double y1, double z1,
			double x2, double y2, double z2,
			double x3, double y3, double z3,
			double x4, double y4, double z4, Color col)
  {
    AddQuadrilateral(x1,y1,z1,x2,y2,z2,x3,y3,z3,x4,y4,z4).color = col;
  }
  /* Property.
   */
  public
  SinglyLinkedList polygonSurfaces;
}

class PolygonSurface
{
  public
  PolygonSurface()
  {
    edges = new SinglyLinkedList();
  }
  public
  PolygonSurface(Color col)
  {
    edges = new SinglyLinkedList();
    color = col;
  }
  public
  void Draw(Graphics g)
  {
    if(!edges.PositionHead()) return;
    Edge edge;
    Vertex vertex = (edge=(Edge)edges.Current()).v1;
    Polygon poly = new Polygon();
    poly.addPoint((int)edge.v1.x,(int)edge.v1.y);
    for(edges.PositionNext();
	(edge=(Edge)edges.Current()) != null;
	edges.PositionNext())
    {
      poly.addPoint((int)edge.v1.x,(int)edge.v1.y);
    }
    poly.addPoint((int)vertex.x,(int)vertex.y);
    g.setColor(color);
    g.fillPolygon(poly);
    g.setColor(Color.black);
    //g.drawPolygon(poly); no, we may not wish to draw all edges
    for(edges.PositionHead();
	(edge=(Edge)edges.Current()) != null;
	edges.PositionNext())
    {
      edge.Draw(g);
    }
  }
  public
  void AddEdge(Edge edge)
  {
    edges.Append(edge);
  }
  public
  PolygonSurface LeftMult(Matrix4x4 l)
  {
    PolygonSurface ps = new PolygonSurface(color);
    Edge e;
    for(edges.PositionHead();
	(e=(Edge)edges.Current()) != null;
	edges.PositionNext())
    {
      ps.AddEdge(e.LeftMult(l));
    }
    return ps;
  }
  // Returns true iff plane equation calculation was successful.
  public
  boolean CalculatePlaneEquation()
  {
    Edge[] e = new Edge[2];
    Edge edge;
    int i;
    for(edges.PositionHead(), i=0;
	i<2 && (edge=(Edge)edges.Current()) != null;
	edges.PositionNext())
    {
      if(i==0) {e[0]=edge; i++;}
      else if(i==1) {if(!edge.Parallel(e[0])) {e[1]=edge; i++;}}
    }
    if(i!=2) return false; // existing vertices do not define a plane

    Vertex[] v = new Vertex[3];
    v[0] = e[0].v1;
    v[1] = e[1].v1;
    v[2] = e[1].v2;

    a = v[0].y*(v[1].z-v[2].z) + v[1].y*(v[2].z-v[0].z)
      + v[2].y*(v[0].z-v[1].z);
    b = v[0].z*(v[1].x-v[2].x) + v[1].z*(v[2].x-v[0].x)
      + v[2].z*(v[0].x-v[1].x);
    c = v[0].x*(v[1].y-v[2].y) + v[1].x*(v[2].y-v[0].y)
      + v[2].x*(v[0].y-v[1].y);
    d = -v[0].x*(v[1].y*v[2].z-v[2].y*v[1].z)
      - v[1].x*(v[2].y*v[0].z-v[0].y*v[2].z)
      - v[2].x*(v[0].y*v[1].z-v[1].y*v[0].z);

    /*
    // Check the result.
    for(edges.PositionHead();
	(edge=(Edge)edges.Current()) != null;
	edges.PositionNext())
      for(i=0; i<2; i++)
      {
	double val = a*v[i].x+b*v[i].y+c*v[i].z+d;
	if(Math.abs(val) > 1e-10)
	  System.err.println("CalculateEquation() failure: "+val+"!");
      }
      */

    return true;
  }
  public
  boolean CheckPointOnPlane(Vertex vx)
  {
    double val = a*vx.x+b*vx.y+c*vx.z+d;
    if(Math.abs(val) > 1e-10) {
      return false;
    }
    return true;
  }
  // Finds extreme values of the x- and y-coordinates of the polygon.
  // Not used, but might be useful for some purpose.
  // Returns false iff there are no vertices.
  public
  boolean UpdateLimits()
  {
    edges.PositionHead();
    Edge edge=(Edge)edges.Current();
    if(edge==null) return false;
    if(edge.v1.x > edge.v2.x) {
      minX=edge.v2.x;
      maxX=edge.v1.x;
    } else {
      minX=edge.v1.x;
      maxX=edge.v2.x;
    }
    if(edge.v1.y > edge.v2.y) {
      minY=edge.v2.y;
      maxY=edge.v1.y;
    } else {
      minY=edge.v1.y;
      maxY=edge.v2.y;
    }
    for(edges.PositionNext();
	(edge=(Edge)edges.Current()) != null;
	edges.PositionNext())
    {
      if(edge.v1.x<minX) minX=edge.v1.x;
      else if(edge.v1.x>maxX) maxX=edge.v1.x;
      if(edge.v2.x<minX) minX=edge.v2.x;
      else if(edge.v2.x>maxX) maxX=edge.v2.x;
      if(edge.v1.y<minY) minY=edge.v1.y;
      else if(edge.v1.y>maxY) maxY=edge.v1.y;
      if(edge.v2.y<minY) minY=edge.v2.y;
      else if(edge.v2.y>maxY) maxY=edge.v2.y;
    }
    return true;
  }
  public
  // Examines how given vertex is positioned in comparison to
  // the polygon plane. Returns one of FRONT, ONPLANE, BEHIND, ONSIDE.
  int Position(Vertex v)
  {
    // We'll add 0.001 pixels of tolerance here to avoid small
    // errors causing some of the points of some polygon to not
    // be on the polygon's "own plane", and therefore cause the
    // polygon to be split in two by it's "own plane" (plane
    // as defined by constants a, b, c, d of the plane equation).
    if(Math.abs(c)<0.001)
    {
      // Plane parallel to the line of view.
      if(Math.abs(a*(v.x)+b*(v.y)+d)<0.001) return ONPLANE;
      else return ONSIDE;
    } else {
      double z = (a*(v.x)+b*(v.y)+d)/-c;
      if(z<v.z-0.001) return FRONT;
      if(z>v.z+0.001) return BEHIND;
      return ONPLANE;
    }
  }
  public
  // Examines how given edge is positioned in comparison to
  // the polygon plane. Returns one of FRONT, ONPLANE, BEHIND, ONSIDE,
  // or a combination of some of the values.
  int Position(Edge e)
  {
    return Position(e.v1)|Position(e.v2);
  }
  public
  // Examines how polygon is positioned in comparison to
  // the plane of this polygon. Returns one of FRONT, ONPLANE, BEHIND, ONSIDE,
  // or a combination of some or all of the values.
  int Position(PolygonSurface pS)
  {
    int r=0;
    Edge edge;
    for(pS.edges.PositionHead();
	(edge=(Edge)pS.edges.Current()) != null;
	pS.edges.PositionNext())
      r|=Position(edge);
    return r;
  }
  public
  void Split(PolygonSurface plane,
	     SinglyLinkedList fPolys, SinglyLinkedList bPolys)
  {
    //System.out.println("Splitting the polygon");
    //Debug();
    PolygonSurface fPoly = new PolygonSurface(color);
    PolygonSurface bPoly = new PolygonSurface(color);
    Edge fEdge = new Edge(false);
    Edge bEdge = new Edge(false);
    Edge edge;
    for(edges.PositionHead();
	(edge=(Edge)edges.Current()) != null;
	edges.PositionNext())
    {
      int pos1 = plane.Position(edge.v1);
      int pos2 = plane.Position(edge.v2);
      if(pos1==FRONT && pos2==FRONT) {
	fPoly.AddEdge(edge);
      } else if(pos1==BEHIND && pos2==BEHIND) {
	bPoly.AddEdge(edge);
      } else if(pos1==ONPLANE && pos2==FRONT) {
	fEdge.v2 = edge.v1;
	bEdge.v1 = edge.v1;
	fPoly.AddEdge(edge);
	//bPoly.AddEdge(bEdge); // only need do this once
      } else if(pos1==FRONT && pos2==ONPLANE) {
	fEdge.v1 = edge.v2;
	bEdge.v2 = edge.v2;
	fPoly.AddEdge(edge);
	fPoly.AddEdge(fEdge); // only need do this once
      } else if(pos1==ONPLANE && pos2==BEHIND) {
	fEdge.v1 = edge.v1;
	bEdge.v2 = edge.v1;
	//fPoly.AddEdge(fEdge); // only need do this once
	bPoly.AddEdge(edge);
      } else if(pos1==BEHIND && pos2==ONPLANE) {
	fEdge.v2 = edge.v2;
	bEdge.v1 = edge.v2;
	bPoly.AddEdge(edge);
	bPoly.AddEdge(bEdge); // only need do this once
      } else if(pos1==ONPLANE && pos2==ONPLANE) {
	System.err.println("ONPLANE-ONPLANE unexpected!"); // should not happen
      } else if(pos1==FRONT && pos2==BEHIND) {
	Vertex newV = edge.PointOnPlane(plane);
	// Part 1 of old edge.
	fPoly.AddEdge(new Edge(edge.v1,newV));
	// Polygon splitting edge.
	fEdge.v1 = newV;
	bEdge.v2 = newV;
	fPoly.AddEdge(fEdge);
	// Part 2 of old edge.
	bPoly.AddEdge(new Edge(newV,edge.v2));
      } else /* pos1==BEHIND && pos2==FRONT */ {
	Vertex newV = edge.PointOnPlane(plane);
	// Part 1 of old edge.
	bPoly.AddEdge(new Edge(edge.v1,newV));
	// Polygon splitting edge.
	fEdge.v2 = newV;
	bEdge.v1 = newV;
	bPoly.AddEdge(bEdge);
	// Part 2 of old edge.
	fPoly.AddEdge(new Edge(newV,edge.v2));
      }
    } 
    fPolys.Append(fPoly);
    bPolys.Append(bPoly);
    //System.out.println("resulting polygons are (front)");
    //fPoly.Debug();
    //System.out.println("and (back)");
    //bPoly.Debug();
  }
  public
  void Debug()
  {
    Edge edge;
    for(edges.PositionHead();
	(edge=(Edge)edges.Current()) != null;
	edges.PositionNext())
    {
      edge.Debug();
    }
  }
  public
  boolean Update()
  {
    return CalculatePlaneEquation(); // && UpdateLimits();
  }
  /* Property.
   */
  public
  final int FRONT=1;
  public
  final int ONPLANE=2;
  public
  final int BEHIND=4;
  public
  final int ONSIDE=8;
  public
  Color color = Color.gray;
  SinglyLinkedList edges;
  public
  double a, b, c, d;                    // a*x + b*y + c*z + d = 0
  double minX, maxX, minY, maxY;        // extreme values of vertex positions
}

class Edge
{
  public
  Edge()
  {
    v1=null;
    v2=null;
    visible=true;
  }
  public
  Edge(boolean vis)
  {
    v1=null;
    v2=null;
    visible=vis;
  }
  public
  Edge(Vertex vertex1, Vertex vertex2)
  {
    v1=vertex1;
    v2=vertex2;
    visible=true;
  }
  public
  void Draw(Graphics g)
  {
    if(visible)
      g.drawLine((int)v1.x,(int)v1.y,(int)v2.x,(int)v2.y);
  }
  public
  Edge LeftMult(Matrix4x4 l)
  {
    Vertex vertex1 = v1.LeftMult(l);
    Vertex vertex2 = v2.LeftMult(l);
    return new Edge(vertex1,vertex2);
  }
  // Returns true iff self is parallel to e.
  public
  boolean Parallel(Edge e)
  {
    Point3 a = new Point3(v2.x-v1.x,v2.y-v1.y,v2.z-v1.z);
    Point3 b = new Point3(e.v2.x-e.v1.x,e.v2.y-e.v1.y,e.v2.z-e.v1.z);
    return a.y*b.z-a.z*b.y==0 && a.z*b.x-a.x*b.z==0 && a.x*b.y-a.y*b.x==0;
  }
  public
  // Assumes other vertex is outside of the given plane, and that
  // the other one is inside of it. Calculates the point on the plane,
  // and returns that point as a vertex.
  Vertex PointOnPlane(PolygonSurface plane)
  {
    boolean failure = false;
    double q = 0, x, y, z;
    Vertex ret = null;
    if((plane.a*(v1.x-v2.x)+plane.b*(v1.y-v2.y)+plane.c*(v1.z-v2.z))==0) {
      System.err.println("PointOnPlane divisor 0");
      failure = true;
    } else {
      q = (plane.a*v1.x+plane.b*v1.y+plane.c*v1.z+plane.d)/
	(plane.a*(v1.x-v2.x)+plane.b*(v1.y-v2.y)+plane.c*(v1.z-v2.z));
      x = v1.x+q*(v2.x-v1.x);
      y = v1.y+q*(v2.y-v1.y);
      z = v1.z+q*(v2.z-v1.z);
      ret = new Vertex(x,y,z);
    }
    if(failure || !CheckPointOnEdge(ret) || !plane.CheckPointOnPlane(ret))
    {
      /*
      if(!failure)
	System.out.println("PointOnPlane() failure! (1): "+
			   "("+v1.x+","+v1.y+","+v1.z+") - "+
			   "("+ret.x+","+ret.y+","+ret.z+") - "+
			   "("+v2.x+","+v2.y+","+v2.z+") "+
			   "(a="+plane.a+",b="+plane.b+",c="+
			   plane.c+",d="+plane.d+") q="+q);
			   */
      // We assumedly got an error because of a roundoff error.
      // Let's find a solution by iterating.
      ret = Iterate(v1,plane.a*v1.x+plane.b*v1.y+plane.c*v1.z+plane.d,
		    v2,plane.a*v2.x+plane.b*v2.y+plane.c*v2.z+plane.d,
		    plane);
      //System.out.println("Iterated ("+ret.x+","+ret.y+","+ret.z+")");
    }
    return ret;
  }
  private
  // This method could be further optimized!!
  Vertex Iterate(Vertex vs, double err1, Vertex ve, double err2,
		 PolygonSurface plane)
  {
    double x = (vs.x+ve.x)/2;
    double y = (vs.y+ve.y)/2;
    double z = (vs.z+ve.z)/2;
    double err = plane.a*x+plane.b*y+plane.c*z+plane.d;
    Vertex vm = new Vertex(x,y,z);
    if(Math.abs(err)<0.0001) return vm; 
    if(err>0) {
      if(err1<0)
	return Iterate(vs,err1,vm,err,plane);
      else
	return Iterate(ve,err2,vm,err,plane);
    } else /* err<0 */ {
      if(err1>0)
	return Iterate(vm,err,vs,err1,plane);
      else
	return Iterate(vm,err,ve,err2,plane);
    }
  }
  private
  // Returns true iff vertex 'vx' is on this edge.
  boolean CheckPointOnEdge(Vertex vx)
  {
    Vertex v1v2 = v2.Minus(v1);
    Vertex v1vx = vx.Minus(v1);
    boolean parallel = (v1v2.CrossProduct(v1vx)).Zero();
    double minx = Math.min(v1.x,v2.x);
    double maxx = Math.max(v1.x,v2.x);
    double miny = Math.min(v1.y,v2.y);
    double maxy = Math.max(v1.y,v2.y);
    double minz = Math.min(v1.z,v2.z);
    double maxz = Math.max(v1.z,v2.z);
    boolean range =
      vx.x>=minx && vx.x<=maxx &&
      vx.y>=miny && vx.y<=maxy &&
      vx.z>=minz && vx.z<=maxz;
    //if(!parallel) System.out.println("parallel");
    //if(!range) System.out.println("range");
    return parallel && range;
  }
  public
  // Prints out information which may be useful when locating
  // errors from the code.
  void Debug()
  {
    if(v1==null || v2==null)
    {
      if(v1==null) System.out.println("v1 null!");
      if(v2==null) System.out.println("v2 null!");
      return;
    }
    System.out.println("edge: v1: x="+v1.x+" y="+v1.y+" z="+v1.z+
		       " v2: x="+v2.x+" y="+v2.y+" z="+v2.z);
  }
  /* Property.
   */
  Vertex v1;
  Vertex v2;
  boolean visible;
}

// Vertices which define the endpoints of an Edge, are instances of this class.
class Vertex extends Point3
{
  public
  Vertex()
  {
    //super(); does nothing
  }
  public
  Vertex(double xv, double yv, double zv)
  {
    super(xv,yv,zv);
  }
  public
  // The string should be of the format "double,double,double".
  Vertex(String s)
       throws SyntaxException
  {
    // Note that we do not call super/3 for efficiency reasons!

    int i, j;

    j = s.indexOf(',');
    if(j==-1) throw new SyntaxException();
    String ss = s.substring(0,j);
    Double dbl = Double.valueOf(ss);
    x = dbl.doubleValue();

    i = s.indexOf(',',j+1);
    if(i==-1) throw new SyntaxException();
    ss = s.substring(j+1,i);
    dbl = Double.valueOf(ss);
    y = dbl.doubleValue();

    j = s.length();
    ss = s.substring(i+1,j);
    dbl = Double.valueOf(ss);
    z = dbl.doubleValue();
  }
  public
  // Returns true iff elements of this vertex and that of 'vx' are
  // exactly equal.
  boolean Equal(Vertex vx)
  {
    return vx.x==x && vx.y==y && vx.z==z;
  }
  public
  // Returns true if all of the elements of this vector are close to zero.
  boolean Zero()
  {
    if(Math.abs(x)<1e-10 && Math.abs(y)<1e-10 && Math.abs(z)<1e-10)
      return true;
    return false;
  }
  public
  // Deducts vertex 'vx' from this one, and returns the resulting vertex.
  Vertex Minus(Vertex vx)
  {
    return new Vertex(x-vx.x,y-vx.y,z-vx.z);
  }
  public
  // Calculates the cross product of this vertex and vertex 'vx',
  // returning the resulting vertex.
  Vertex CrossProduct(Vertex vx)
  {
    //             |   ux    uy  uz |
    // this X vx = |    x    y    z |
    //             | vx.x vx.y vx.z |
    return new Vertex(y*vx.z-z*vx.y,z*vx.x-x*vx.z,x*vx.y-y*vx.x);
  }
  public
  // Multiplies this vertex with matrix 'l' from the left side,
  // and returns the resulting vertex.
  Vertex LeftMult(Matrix4x4 l)
  {
    return new Vertex(l.m[0][0]*x+l.m[0][1]*y+l.m[0][2]*z+l.m[0][3],
		      l.m[1][0]*x+l.m[1][1]*y+l.m[1][2]*z+l.m[1][3],
		      l.m[2][0]*x+l.m[2][1]*y+l.m[2][2]*z+l.m[2][3]);
  }
}

// A 4 by 4 matrix.
class Matrix4x4
{
  public
  Matrix4x4()
  {
    m = new double[4][4];
  }
  public
  Matrix4x4(double x11, double x12, double x13, double x14,
	    double x21, double x22, double x23, double x24,
	    double x31, double x32, double x33, double x34,
	    double x41, double x42, double x43, double x44)
  {
    m = new double[4][4];
    m[0][0] = x11; m[0][1] = x12; m[0][2] = x13; m[0][3] = x14;
    m[1][0] = x21; m[1][1] = x22; m[1][2] = x23; m[1][3] = x24;
    m[2][0] = x31; m[2][1] = x32; m[2][2] = x33; m[2][3] = x34;
    m[3][0] = x41; m[3][1] = x42; m[3][2] = x43; m[3][3] = x44;
  }
  public
  // Multiplies this matrix with matrix 'l' from the left side,
  // and returns the resulting matrix.
  Matrix4x4 LeftMult(Matrix4x4 l)
  {
    Matrix4x4 a = new Matrix4x4();
    int i, j;
    for(i=0; i<4; i++)
      for(j=0; j<4; j++)
	a.m[i][j] = l.m[i][0]*m[0][j] + l.m[i][1]*m[1][j]
	  + l.m[i][2]*m[2][j] + l.m[i][3]*m[3][j];
    return a;
  }
  public
  // Transposes this matrix and returns the resulting matrix.
  Matrix4x4 Transpose()
  {
    Matrix4x4 a = new Matrix4x4();
    int i, j;
    for(i=0; i<4; i++)
      for(j=0; j<4; j++)
	a.m[i][j] = m[j][i];
    return a;
  }
  public
  // Sets the elements of this matrix so that this matrix becomes
  // a unit matrix.
  void UnitMatrix()
  {
    int i, j;
    for(i=0; i<4; i++)
      for(j=0; j<4; j++)
      {
	if(j==i) m[i][j]=1.0;
	else m[i][j]=0.0;
      }
  }
  public
  double Determinant()
  {
    /*
    // A determinant of a 4x4 matrix is calculated as follows,
    // where Determinant(j,k) returns a determinant of the
    // 3x3 matrix formed from this matrix by removing row j
    // and column k.
    double sum = 0;
    int k = 0; // selected column
    for(int j=0; j<4; j++)
      sum += ((((j+k)%2)==0)?1:-1)*m[j][k]*Determinant(j,k);
    return sum;
    */
    return m[0][0]*(m[1][1]*(m[2][2]*m[3][3]-m[3][2]*m[2][3])-
                    m[1][2]*(m[2][1]*m[3][3]-m[3][1]*m[2][3])+
                    m[1][3]*(m[2][1]*m[3][2]-m[3][1]*m[2][2]))-
           m[0][1]*(m[1][0]*(m[2][2]*m[3][3]-m[3][2]*m[2][3])-
                    m[1][2]*(m[2][0]*m[3][3]-m[3][0]*m[2][3])+
                    m[1][3]*(m[2][0]*m[3][2]-m[3][0]*m[2][2]))+
           m[0][2]*(m[1][0]*(m[2][1]*m[3][3]-m[3][1]*m[2][3])-
                    m[1][1]*(m[2][0]*m[3][3]-m[3][0]*m[2][3])+
                    m[1][3]*(m[2][0]*m[3][1]-m[3][0]*m[2][1]))-
           m[0][3]*(m[1][0]*(m[2][1]*m[3][2]-m[3][1]*m[2][2])-
                    m[1][1]*(m[2][0]*m[3][2]-m[3][0]*m[2][2])+
                    m[1][2]*(m[2][0]*m[3][1]-m[3][0]*m[2][1]));
  }
  public
  void Print()
  {
    System.out.println("[["+m[0][0]+", "+m[0][1]+", "+m[0][2]+", "
		       +m[0][3]+"]");
    System.out.println(" ["+m[1][0]+", "+m[1][1]+", "+m[1][2]+", "
		       +m[1][3]+"]");
    System.out.println(" ["+m[2][0]+", "+m[2][1]+", "+m[2][2]+", "
		       +m[2][3]+"]");
    System.out.println(" ["+m[3][0]+", "+m[3][1]+", "+m[3][2]+", "
		       +m[3][3]+"]]");
  }
  /* Property.
   */
  public
  double[][] m;              // elements of this matrix
}

// Point4 is a vector with 4 elements.
class Point4
{
  public
  Point4()
  {
    v = new double[4];
  }
  public
  Point4(double xv, double yv, double zv, double wv)
  {
    v = new double[4];
    v[0]=xv; v[1]=yv; v[2]=zv; v[3]=wv;
  }
  public
  // Returns the vector resulting when this vector is multiplied my
  // matrix 'l' from the left side.
  Point4 LeftMult(Matrix4x4 l)
  {
    Point4 point = new Point4();
    int i, j;
    for(i=0; i<4; i++)
      point.v[i]=l.m[i][0]*v[0]+l.m[i][1]*v[1]+l.m[i][2]*v[2]+l.m[i][3]*v[3];
    return point;
  }
  /* Property.
   */
  double[] v; // elements of this vector
}

// Point3 is a vector with 3 elements.
class Point3
{
  public
  Point3()
  {
  }

  public
  Point3(double xv, double yv, double zv)
  {
    x=xv; y=yv; z=zv;
  }

  public
  // Calculates and returns the dot product of this vector and vector 'vec'.
  double DotProduct(Point3 vec)
  {
    return x*vec.x+y*vec.y+z*vec.z;
  }

  public
  // Projects a this three dimensional point into a two dimensional plane,
  // returning the resulting point.
  Point2 Projection()
  {
    double xp, yp;
    xp=x-z/3;
    yp=y-z/3;
    return new Point2(xp,yp);
  }

  public
  double x=0, y=0, z=0;
}

// Point2 is a vector with two elements.
// (Apparently there is a class named Point in java.awt, but
// I don't have any documentation about it.)
class Point2
{
  public
  Point2() {}

  public
  Point2(double xv, double yv) {x=xv; y=yv;}

  public
  double x=0, y=0;
}
