package grapher3D.view; import java.awt.Color; import java.util.Collection; import java.util.LinkedList; import parser.ExpressionNode; import parser.RecursiveDescentParser; import parser.Value; import primitives3D.LineSegment3D; import primitives3D.Object3D; import primitives3D.Polygon3D; import primitives3D.SolidRod3D; import primitives3D.Vector3D; import valueTypes.ColorValue; import valueTypes.DecimalValue; import valueTypes.ErrorValue; import variables.Variable; import colorMap.ColorMap; public class Graph3D { /** * The lines which connect the points in the U direction. */ LineSegment3D[][] linesU; /** * The lines which connect the points in the V direction. */ LineSegment3D[][] linesV; /** * The 3D polygons which represent the surface. */ Polygon3D[][] polygons; /** * The colors of each part of the surface. */ ColorValue[][] colors; /** * The two dimensional array of 3D points ([u][v]) which will be connected * together to form the surface. */ Vector3D[][] points = new Vector3D[0][0]; /** * The indexed values of U from 0 to 1. */ double[] valuesU = new double[0]; /** * The indexed values of V from 0 to 1. */ double[] valuesV; /** * The number of 3D points representing the surface in the U direction. */ protected int graphResolutionU = 30; /** * The number of 3D points representing the surface in the V direction. */ protected int graphResolutionV = 30; /** * The ColorValue which holds the default color of the surface. */ protected Color defaultColor = Color.green; /** * The function evaluation tree which will be graphed. This function should * assign x,y,and z based on u and v varying from 0 to 1. * */ ExpressionNode function = (new RecursiveDescentParser()) .parse("executeFunction({x=u*20-10;y=v*20-10;z=sin(x*y/10+t)})"); /** * The u variable */ Variable uVar = Variable.getVariable("u"); /** * The v variable */ Variable vVar = Variable.getVariable("v"); /** * The x variable */ Variable xVar = Variable.getVariable("x"); /** * The y variable */ Variable yVar = Variable.getVariable("y"); /** * The v variable */ Variable zVar = Variable.getVariable("z"); /** * The color variable */ Variable colorVar = Variable.getVariable("color"); /** * The color map used when the color variable is assigned values */ ColorMap colorMap = ColorMap.generateDefaultColorMap(); /** * The red variable */ Variable redVar = Variable.getVariable("red"); /** * The green variable */ Variable greenVar = Variable.getVariable("green"); /** * The blue variable */ Variable blueVar = Variable.getVariable("blue"); /** * The value which is used to set the value of the u variable. */ DecimalValue reusableUValue = new DecimalValue(0); /** * The value which is used to set the value of the v variable. */ DecimalValue reusableVValue = new DecimalValue(0); /** * The value which is used to set the value of the color variable. */ DecimalValue reusableColorValue = new DecimalValue(-1); /** * When true, the surface is represented using lines, when false, the * surface is represented as solid 3D polygons. */ protected boolean wireframe = false; /** * Sets up the grid with the current values of graphResolutionU and * graphResolutionV. */ protected void recreateGrid() { synchronized (points) { synchronized (valuesU) { // initialize the points and colors colors = new ColorValue[graphResolutionU + 1][graphResolutionV + 1]; points = new Vector3D[graphResolutionU + 1][graphResolutionV + 1]; // initialize and add the lines linesU = new SolidRod3D[graphResolutionU][graphResolutionV + 1]; linesV = new SolidRod3D[graphResolutionU + 1][graphResolutionV]; // initialize and add the polygons polygons = new Polygon3D[graphResolutionU + 1][graphResolutionV + 1]; for (int u = 0; u < graphResolutionU + 1; u++) for (int v = 0; v < graphResolutionV + 1; v++) { points[u][v] = new Vector3D(0, 0, 0); colors[u][v] = new ColorValue(defaultColor); } for (int u = 0; u < graphResolutionU + 1; u++) for (int v = 0; v < graphResolutionV + 1; v++) { // the lines if (u < graphResolutionU) { linesU[u][v] = new SolidRod3D(points[u][v], points[u + 1][v], 0.04, Color.BLUE); } if (v < graphResolutionV) { linesV[u][v] = new SolidRod3D(points[u][v], points[u][v + 1], 0.04, Color.BLUE); } // the polygons if (u < graphResolutionU && v < graphResolutionV) { Vector3D[] polygonPoints = { points[u][v], points[u + 1][v], points[u + 1][v + 1], points[u][v + 1] }; polygons[u][v] = new Polygon3D(polygonPoints, Color.green); } } // initialize the values valuesU = new double[graphResolutionU + 1]; for (int i = 0; i < graphResolutionU + 1; i++) valuesU[i] = (double) i / graphResolutionU; valuesV = new double[graphResolutionV + 1]; for (int i = 0; i < graphResolutionV + 1; i++) valuesV[i] = (double) i / graphResolutionV; } } } public Collection<Object3D> get3DObjects() { Collection<Object3D> objects = new LinkedList<Object3D>(); for (int u = 0; u < graphResolutionU + 1; u++) for (int v = 0; v < graphResolutionV + 1; v++) { // the lines if (wireframe) { if (u < graphResolutionU) objects.add(linesU[u][v]); if (v < graphResolutionV) objects.add(linesV[u][v]); } else // the polygons if (u < graphResolutionU && v < graphResolutionV) objects.add(polygons[u][v]); } return objects; } /** * Calculates and sets the values of the points based on the current * function evaluation tree. * */ public void calculateGrid() { // set colors to -1 to indicate to use the default color reusableColorValue.value = Double.MIN_VALUE; colorVar.set(reusableColorValue); redVar.set(reusableColorValue); greenVar.set(reusableColorValue); blueVar.set(reusableColorValue); // temporary variables for color, red, green, and blue double colorVarValue = 0, r = 0, g = 0, b = 0; synchronized (points) { synchronized (valuesU) { synchronized (valuesU) { for (int u = 0; u < valuesU.length; u++) for (int v = 0; v < valuesV.length; v++) { reusableUValue.value = valuesU[u]; reusableVValue.value = valuesV[v]; uVar.set(reusableUValue); vVar.set(reusableVValue); function.evaluate(); points[u][v].x = DecimalValue .extractDoubleValue(xVar.evaluate()); points[u][v].y = DecimalValue .extractDoubleValue(yVar.evaluate()); points[u][v].z = DecimalValue .extractDoubleValue(zVar.evaluate()); colorVarValue = DecimalValue .extractDoubleValue(colorVar.evaluate()); r = DecimalValue.extractDoubleValue(redVar .evaluate()); g = DecimalValue.extractDoubleValue(greenVar .evaluate()); b = DecimalValue.extractDoubleValue(blueVar .evaluate()); if (colorVarValue != Double.MIN_VALUE) colors[u][v].value = colorMap .getColorAtValue(colorVarValue); else if (r != Double.MIN_VALUE || g != Double.MIN_VALUE || b != Double.MIN_VALUE) colors[u][v].value = new Color( (float) (r < 0 ? 0 : r > 1 ? 1 : r), (float) (g < 0 ? 0 : g > 1 ? 1 : g), (float) (b < 0 ? 0 : b > 1 ? 1 : b)); else colors[u][v].value = defaultColor; if (wireframe) { if (u < valuesU.length - 1) linesU[u][v].color = colors[u][v].value; if (v < valuesV.length - 1) linesV[u][v].color = colors[u][v].value; } else { if (u < valuesU.length - 1 && v < valuesV.length - 1) polygons[u][v].color = colors[u][v].value; } } } } } } /** * Checks the specified function for errors. * * @param function * the function to test * @return null if no errors, or the error message string if there is an * error. */ public String checkForErrorsInFunction(ExpressionNode function) { // check for errors uVar.set(reusableUValue); vVar.set(reusableVValue); Value error = null; Value result = function.evaluate(); if (result instanceof ErrorValue) error = result; if (xVar.evaluate() instanceof ErrorValue) error = xVar.evaluate(); else if (yVar.evaluate() instanceof ErrorValue) error = yVar.evaluate(); else if (zVar.evaluate() instanceof ErrorValue) error = zVar.evaluate(); return error == null ? null : error.toString(); } /** * * @return resolution of the graph in the U direction. That is, the number * of "rectangles" along the U direction used to represent the surface. */ public int getUResolution() { return graphResolutionU; } /** * * @return resolution of the graph in the V direction. That is, the number * of "rectangles" along the V direction used to represent the surface. */ public int getVResolution() { return graphResolutionV; } /** * * Sets the resolution of the graph in the U direction. That is, the number * of "rectangles" along the U direction used to represent the surface. * * @param i * the new resolution. */ public void setUResolution(int i) { graphResolutionU = i; } /** * * Sets the resolution of the graph in the V direction. That is, the number * of "rectangles" along the V direction used to represent the surface. * * @param i * the new resolution. */ public void setVResolution(int i) { graphResolutionV = i; } }