/** * @file SolidObject.java * @author Robert S Laramee * @version 1.0 * Start Date Tuesday, 17 November 1998 * "Finish" Date Thursday, 03 December 1998 * * Description This class contains the methods and data necessary for * creating user defined solid objects */ import java.awt.*; // for Graphics class import java.util.*; // for Vector class import java.io.*; // for FileReader, StreamTokenizer, PrintWriter //import matrix.*; // for Matrix4, Point4 class import javax.swing.*; public class SolidObject { private Vector SOpoints; // to store solid object points private double height; // height of the new solid object private String name; // name of the solid object private boolean done; // TRUE if we are done defining the object private Face bottomFace; // the bottom face private Face topFace; // the top face private Vector faces; // to store all faces solid objects private Vector instances; // to store all instances of solid object // -used when parsing input file private Matrix4 _xform; // a transformation matrix private Point4 vertices[]; // vertices of the face private Color color; // color of solid object private double darkenRatio; // darken ration for coloring faces private SolidObjectList SOlist; /** * constructor * A new solid object is created when the user selects "New Solid Object" * from the Drawing Mode menu (from DrawCanvas.java). */ public SolidObject() { SOpoints = new Vector(); // a list of solid object points faces = new Vector(); // used to store object's faces done = false; darkenRatio = 1.0; // default no darkening color = Color.white; // default white instances = new Vector(); // a list of instances SOlist = null; } /** * another constructor that takes a name as a parameter * @param n -the name of the new solid object */ public SolidObject(String n) { this(); name = n; } /** * return the solid object's name, instances, faces */ public String getName() { return name; } public Vector getInstances() { return instances; } public Vector getFaces() { return faces; } /** * setName() calls check name to perform some tests on the name given. * @param newName -the name given by the user */ public void setName(String newName) { if (checkName(newName) == true) { name = newName; } Swing3d.getApp().addUserShape(name); } /** * checkName() returns TRUE if a valid name was given * @param newName -the user supplied name to check * @return TRUE if the name is valid */ private boolean checkName(String newName) { if (newName.trim().equals("")) { System.out.println("** Error: empty name, using default: " + name); return false; } if ( SOlist != null ) { if ((SOlist.checkName(newName)) == false) { System.out.println("** Error: name: " + newName + " already used, using default name: "+name); return false; } } return true; } /** * Whenever the user clicks the mouse in solid object mode, the * mouseClicked event calls this method to create a vertex out of the * coordinates of the mouse click. * IF the new point is too close to the first THEN create a new face * out of those points * ELSE add the point to the current definition * @param SOlist -a reference to the list of solid objects * @param x -user drawn x coordinate (in Java window coordinates) * @param y -user drawn y coordinate (in Java window coordinates) * @param z -zBack - zFront (height) */ public void collectDefinition(SolidObjectList sol, int x, int y, double z) { SolidObjectPoint SOpoint = new SolidObjectPoint(x, y); height = z; if (SOpoint.onTopOfFirst(SOpoints) == true) { translateAndScale(); createSolidObject(sol); done = true; (new JNameDialog(new JFrame("New Name Frame"), this)).show(); } else { SOpoints.addElement(SOpoint); } } /** * This method is for positioning and scaling the "standard" representation * of the new solid so it fills the unit cube as much as possible, without * distorting its shape. In other words, it translates Xmin, Ymin, Zmax * (remember z < 0) to (0,0,0) and the scales uniformly by * 1/max(Xmax-Xmin, Ymax-Ymin, Zmax-Zmin) * Note: in the 2D definition, the Z values are all zero, so we can leave * those out of our computations */ private void translateAndScale() { double Xmin = findMin("x"); double Xmax = findMax("x"); double Ymin = findMin("y"); double Ymax = findMax("y"); translate(Xmin, Ymin); scale(1/(Math.max( (Xmax - Xmin), (Ymax - Ymin) ))); update3Dpoints(); } /** * The translate method subtracts Xmin and Ymin from all the X and * Y coordinate values respectively * @param Xmin -the X value to subtract off of the X coordinates * @param Ymin -the Y value to subtract off of the Y coordinates */ private void translate(double Xmin, double Ymin) { NDCpoint newPoint; for (int i = 0; i < SOpoints.size(); i++) { newPoint = new NDCpoint( getSolidObjectPoint(i).getDefinition().x - Xmin, getSolidObjectPoint(i).getDefinition().y - Ymin); getSolidObjectPoint(i).getDefinition().assign(newPoint); } } /** * The scale method multiplies all X and Y coordinates by the * scale factor * @param scaleFactor -1/max(Xmax - Xmin, Ymax - Ymin) */ private void scale(double scaleFactor) { NDCpoint newPoint; for (int i = 0; i < SOpoints.size(); i++) { newPoint = new NDCpoint( getSolidObjectPoint(i).getDefinition().x * scaleFactor, getSolidObjectPoint(i).getDefinition().y * scaleFactor); getSolidObjectPoint(i).getDefinition().assign(newPoint); } } /** * This method updates each of the solid object's points in 3D after the * 2D points have been translated and scaled */ private void update3Dpoints() { SolidObjectPoint sop; for (int i = 0; i < SOpoints.size(); i++) { sop = getSolidObjectPoint(i); sop.setPointIn3D(sop.getDefinition()); } } /** * findMin returns the minimum value from a set of NDC coordinate points * @param axis -find the min value w.r.t. this axis * @return min */ private double findMin(String axis) { double min = 1.0; double temp = 1.0; for(int i = 0; i < SOpoints.size(); i++) { if (axis.equals("x")) temp = getSolidObjectPoint(i).getDefinition().x; if (axis.equals("y")) temp = getSolidObjectPoint(i).getDefinition().y; if (temp < min) { min = temp; } } return min; } /** * findMax returns the maximum value from a set of NDC coordinate points * @param axis -find the max value w.r.t. this axis * @return max */ private double findMax(String axis) { double max = 0.0; double temp = 0.0; for(int i = 0; i < SOpoints.size(); i++) { if (axis.equals("x")) temp = getSolidObjectPoint(i).getDefinition().x; if (axis.equals("y")) temp = getSolidObjectPoint(i).getDefinition().y; if (temp > max) { max = temp; } } return max; } /** * This method creates a new solid object out of the user defined vertices. * 1 create the name * 2 add the name to the shape menu * 3 create the faces * 4 add the new solid object to the solid object list * @param SOlist -a reference to the list of solid objects */ private void createSolidObject(SolidObjectList sol) { SOlist = sol; name = new String("SolidObject" + (SOlist.size() + 1) ); createFaces(); SOlist.add(this); } /** * This method creates new faces in 3D out of the list of solid object * points in 3D. It collects the 3D points and hands them off to the * face constructor. */ private void createFaces() { makeBottomFace(); makeTopFace(); makeMiddleFaces(); } /** * make bottom face */ public void makeBottomFace() { SolidObjectPoint sop; Vector points4 = new Vector(); for (int i = 0; i < SOpoints.size(); i++) { sop = getSolidObjectPoint(i); points4.addElement(sop.getPointIn3D()); } bottomFace = new Face(Color.white, setDarkenRatio(), points4); faces.addElement(bottomFace); } /** * make top face * Remember, for the top face, we draw the vertices in reverse order * of the bottom face */ public void makeTopFace() { SolidObjectPoint sop; Vector points4 = new Vector(); for (int i = 0; i < SOpoints.size(); i++) { sop = getSolidObjectPoint(SOpoints.size() - (i + 1)); points4.addElement(new Point4(sop.getPointIn3D().x, height, sop.getPointIn3D().z)); } topFace = new Face(Color.white, setDarkenRatio(), points4); faces.addElement(topFace); } /** * make middle faces * As far as i see, all the "middle" faces will have four vertices * We can just get those 4 points and call the Face constructor */ public void makeMiddleFaces() { int n = bottomFace.getNumVerts(); for(int i = 0; i < n-1; i++) { Vector points4 = new Vector(); points4.addElement(bottomFace.getVert(i)); points4.addElement(bottomFace.getVert(i+1)); points4.addElement(topFace.getVert(n-(i+2))); points4.addElement(topFace.getVert(n-(i+1))); faces.addElement( new Face(Color.white, setDarkenRatio(), points4)); } /* make the last face */ Vector points4 = new Vector(); points4.addElement(bottomFace.getVert(n-1)); points4.addElement(bottomFace.getVert(0)); points4.addElement(topFace.getVert(n-1)); points4.addElement(topFace.getVert(0)); faces.addElement( new Face(Color.white, setDarkenRatio(), points4)); } /** * returns a Solid Object Point off of the solid object points list * @param i -the index of solid object point to return * @return -the Solid Object */ public SolidObjectPoint getSolidObjectPoint(int i) { return (SolidObjectPoint)SOpoints.elementAt(i); } /** * The addToTree method is responsible for adding the faces of a solid * object to the BSP Tree. In theory, this should be just like adding * the faces of a cube or tetrahedron to the tree. * Also we're going to store the object instances by saving each * transformation matrix. * @param xform -the current transformation matrix * @param rgb -the current color * @param tree -a reference to the BSP Tree */ public void addToTree(Matrix4 xform, Color rgb, BSPTree tree) { addToTree(new Instance(name, xform, rgb), tree); } /** * This is another addToTree() method that takes an already created * instance of this object and adds it to the tree. It is called from * InSerialize.java * @param instance -an instance of this solid object * @param tree -a reference to the BSP Tree */ public void addToTree(Instance instance, BSPTree tree) { // save each instance instances.addElement(instance); addFaces(instance, tree); } /** * Add the faces of the user defined solid object to the BSP Tree. * The faces in this case are read in by the InSerialize object * @param tree -a reference to the BSP Tree */ private void addFaces(Instance instance, BSPTree tree) { Face face; _xform = new Matrix4(instance.getXform()); /* FOR EACH face */ for (int j = 0; j < faces.size(); j++) { face = (Face)faces.elementAt(j); vertices = new Point4[face.getNumVerts()]; /* FOR EACH of the face's vertices */ for (int k = 0; k < face.getNumVerts(); k++) { vertices[k] = face.getVert(k); vertices[k] = _xform.timesPoint(vertices[k] ); } tree.addFace(instance.getName(), new Face(vertices, instance.getColor(), setDarkenRatio())); } } /** * draw definition is responsible for drawing what the user is defining * @param g -a reference to the graphics object * @param canvas -a reference to the drawing canvas */ public void drawDefinition(Graphics g, DrawCanvas canvas) { SolidObjectPoint sop1, sop2; g.setColor(Color.white); /* tell each point to draw itself */ for (int i = 0; i < SOpoints.size(); i++) { sop1 = (SolidObjectPoint)SOpoints.elementAt(i); sop1.drawDefinition(g); } /* draw lines between the points */ for (int i = 0; i < SOpoints.size()-1; i++) { sop1 = (SolidObjectPoint)SOpoints.elementAt(i); sop2 = (SolidObjectPoint)SOpoints.elementAt(i + 1); sop1.drawLine(g, sop2); } /* IF done, the draw a red line between the last first and last pts */ if (done == true) { String s = "Finished definition of: " + name; g.drawString(s, 5, 15); g.setColor(Color.red); sop1 = (SolidObjectPoint)SOpoints.elementAt(SOpoints.size()-1); sop2 = (SolidObjectPoint)SOpoints.elementAt(0); sop1.drawLine(g, sop2); canvas.setEditMode(DrawCanvas.DRAWSOLID); } } /** * This method sets the darkeRatio. It was only written so that it * could be reset when the sides get to black. Yes, i know it could * be written in a more compact manner. * @return */ public double setDarkenRatio() { darkenRatio -= 0.1; if (darkenRatio < 0.2) { darkenRatio = 1.1; } return darkenRatio; } /** * printToFile(): prints the solid object definition to output file * @param output -a reference to an output file */ public void printToFile(PrintWriter output) { output.println("D " + name); /* FOR EACH face */ for (int i = 0; i < faces.size(); i++) { Face face = (Face)faces.elementAt(i); output.println("d " + face.getDarkenRatio()); output.println("f " + face.size() + " " + face); } } /** * print(): prints out the solid object definition to standard output */ public void print() { System.out.println("D " + name); /* FOR EACH face */ for (int i = 0; i < faces.size(); i++) { Face face = (Face)faces.elementAt(i); System.out.println("d " + face.getDarkenRatio()); System.out.println("f " + face.size() + " " + face); } } }