/* This file is part of the source code for 3D-XplorMath-J, Version 1.0 (January 2008). * Copyright (c) 2008 The 3D-XplorMath Consortium (http://3d-xplormath.org). * This source code is released under a BSD License, which allows redistribution * in source and binary form, with or without modification, provided copyright * and license information are included, and with no warranty or guarantee of * any kind. For details, see http://3d-xplormath.org/j/source/BSDLicense.txt */ package vmm.core3D; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Polygon; import java.awt.RenderingHints; import java.awt.geom.GeneralPath; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Collections; /** * A Grid3D represents a surface in three-space as an array of vectors. The first index of the array represents * the "u" direction on the surface, while the second index represents the "v" directions. Methods are * provided for drawing the surface a View3D or View3DLit. The surface is considered to be made up of * "patches" which can be further divided into "subpatches". The surface can be drawn in wireframe form, by * drawing each sub-patch, or just by drawing the large patches. When drawn in patch form, the lighting of the surface * is derived from the View in which it is drawn. The intrinsic color of each individual patch can be set as * part of the surface data. Drawing of the Grid3D will normally be done by calling methods in View3D and View3DLit, * which will make sure that the data needed to complete the drawing correctly is done correctly. *
It is possible to construct a surface from several copies of a basic grid, where each copy is
* obtained by applying a TransformMatrix to the basic grid. These transforms are specified by
* the method {@link #addGridTransform(GridTransformMatrix)}. See also {@link #setUseIdentityTransform(boolean)}.
*/
public class Grid3D {
private int uPatchCount, vPatchCount; // The U and V "resolution"
private final int subPatchesPerPatch;
private int uCurveIncrement, vCurveIncrement;
private Color defaultPatchColor = Color.white;
private Color defaultBackColor;
private int uCount, vCount;
private Vector3D[][] pointsOnSurface;
private Color[][] patchColor; // used only if individual colors are specied for patches; the (i,j) entry is the color for the (i,j) subpatch.
private Vector3D[][] normal; // contains normal vectors set from outside this object
private Vector3D[][][] transformedData; // Transformed pointsOnSurface; first index specifies which copy of the basic grid.
private Vector3D[][][] transformedVCurves; // Same data as the above array, but with indices reversed
private boolean subPatchesValid;
private ArrayListgetUCount()-1 while subpatches are numbered from 0 to getUCount()-2
* in the U direction.
*/
public int getUCount() {
return uCount;
}
/**
* Gets the number of (major) patches in the U direction, as specfied in the contructor or in {@link #setPatchCount(int, int)}.
* The number of vertices in the U direction is this value multiplied by subPatchesPerPatch.
*/
public int getUPatchCount() {
return uPatchCount;
}
/**
* Gets the number of vertices along the V direction of the basic grid. This number is one plus the total number of
* subpatches in that direction, and is computed as 1 + vPatchCount*subPatchesPerPatch. Note that vertices
* are numbered from 0 to getVCount()-1 while subpatches are numbered from 0 to getVCount()-2
* in the U direction.
*/
public int getVCount() {
return vCount;
}
/**
* Gets the number of (major) patches in the V direction, as specfied in the contructor or in {@link #setPatchCount(int, int)}.
* The number of subpatches in the V direction is this value multiplied by subPatchesPerPatch.
*/
public int getVPatchCount() {
return vPatchCount;
}
/**
* Gets the total number of subpatches in the basic grid. This is equal to (getUCount()-1)*(getVCount()-1).
* It is also the same as getPatchCount()*subPatchesPerPatch*subPatchesPerPatch
*/
public int getSubPatchCountInGrid() {
return (uCount-1) * (vCount-1);
}
/**
* Gets the total number of (major) patches in the basic grid. This is equal to getUPatchCount()*getVPatchCount()
*/
public int getPatchCountInGrid() {
return uPatchCount*vPatchCount;
}
/**
* Gets the total number of subpatches in the surface, which might consist of several copies of the baic grid.
* This is equal to the number of copies of the gird times (getUCount()-1)*(getVCount()-1).
*/
public int getSubPatchCountInSurface() {
int copies = 1;
if (transforms != null) {
copies = transforms.size();
if (useIdentityTransform)
copies++;
}
return copies*(uCount-1) * (vCount-1);
}
/**
* Gets the total number of (major) patches in the surface, which might consist of several copies of the baic grid.
* This is equal to the number of copies of the grid times getUPatchCount()*getVPatchCount()
*/
public int getPatchCountInSurface() {
int copies = 1;
if (transforms != null) {
copies = transforms.size();
if (useIdentityTransform)
copies++;
}
return copies*uPatchCount*vPatchCount;
}
/**
* Gets the number of subpatches that lie along each edge of a (major) patch, as specified in the constructor.
*/
public int getSubpatchesPerPatch() {
return subPatchesPerPatch;
}
/**
* Gets the U increment value, which is used to decide which grid lines to draw.
* @see #setUCurveIncrement(int)
*/
public int getUCurveIncrement() {
return uCurveIncrement;
}
/**
* Sets the U increment value, which is used to decide which grid lines to draw. When drawing u grid lines, the
* n-th line is drawn only if n is a multiple of uIncrement (except that the final grid line is always drawn).
* A value of zero indicates that no u grid lines are to be drawn, except that when both uIncrement and vIncrement
* are zero and a wireframe rendering is being drawn, uIncrement and vIncrement are both forced equal to
* subPatchesPerPatch, since otherwise nothing would be drawn.
* @param curveIncrement the U increment value. Any value less than zero is changed to zero.
*/
public void setUCurveIncrement(int curveIncrement) {
if (curveIncrement < 0)
curveIncrement = 0;
uCurveIncrement = curveIncrement;
}
/**
* Gets the V increment value, which is used to decide which grid lines to draw.
* @see #setUCurveIncrement(int)
*/
public int getVCurveIncrement() {
return vCurveIncrement;
}
/**
* Sets the V increment value, which is used to decide which grid lines to draw. When drawing v grid lines, the
* n-th line is drawn only if n is a multiple of uIncrement (except that the final grid line is always drawn).
* A value of zero indicates that no u grid lines are to be drawn, except that when both uIncrement and vIncrement
* are zero and a wireframe rendering is being drawn, uIncrement and vIncrement are both forced equal to
* subPatchesPerPatch, since otherwise nothing would be drawn.
* @param curveIncrement the V increment value. Any value less than zero is changed to zero.
*/
public void setVCurveIncrement(int curveIncrement) {
if (curveIncrement < 0)
curveIncrement = 0;
vCurveIncrement = curveIncrement;
}
/**
* Sets the intrinsic color of a subpatch. If the specified color is null, then the default color is used.
* If no color is ever set for a patch, then the default color is used. The color set by this
* method is used for both the front and back color of a patch.
* @param uIndex The u index of the subpatch, in the range 0 to getUCount()-2
* @param vIndex The v index of the subpatch, in the range 0 to getVCount()-2
* @param c The new color of the patch, or zero to use the default color.
* @see #setDefaultPatchColor(Color)
*/
public void setPatchColor(int uIndex, int vIndex, Color c) {
if (patchColor == null) {
if (c == null)
return;
patchColor = new Color[uCount][vCount];
}
patchColor[uIndex][vIndex] = c;
}
/**
* Gets the intrinsic color of a subpatch.
* @param uIndex The u index of the subpatch, in the range 0 to getUCount()-2
* @param vIndex The v index of the subpatch, in the range 0 to getVCount()-2
* @param frontFace Tells whether this is a front or back face
* @see #setPatchColor(int, int, Color)
*/
public Color getPatchColor(int uIndex, int vIndex, boolean frontFace) {
if (patchColor == null) {
if (frontFace || defaultBackColor == null)
return defaultPatchColor;
else
return defaultBackColor;
}
Color c = patchColor[uIndex][vIndex];
return (c == null)? defaultPatchColor : c;
}
/**
* Resets all individual patch colors, as set by {@link #setPatchColor(int, int, Color)}, to
* the default value.
*/
public void clearPatchColors() {
patchColor = null;
}
/**
* Set a normal vector at one of the vertices in the basic grid. The vector is not meant to be
* a unit vector.
* @see #getNormal(int, int)
* @see #getUnitNormal(int, int)
* @param uIndex the u index of the vertex, in the range 0 to {@link #getUCount()}, inclusive
* @param vIndex the v index of the vertex, in the range 0 to {@link #getVCount()}, inclusive
* @param normalVector the vector at vertex at position (uIndex,vIndex). This can be null; in that
* case, an approximate normal vector is computed using vectors to neighboring points on the surface.
*/
public void setNormal(int uIndex, int vIndex, Vector3D normalVector) {
if (normal == null)
normal = new Vector3D[uCount+1][vCount+1];
normal[uIndex][vIndex] = normalVector;
}
/**
* Returns a normal vector (not assumed to be a unit vector) at a specified vertex of the basic grid.
* If a non-null normal vector for this vertex was specified by {@link #setNormal(int, int, Vector3D)},
* then that vector is returned. Otherwise, an approximate normal vector is computed vectors from
* the specified vertex to two neighboring vertices on the surface.
* @param uIndex the u index of the vertex, in the range 0 to {@link #getUCount()}, inclusive
* @param vIndex the v index of the vertex, in the range 0 to {@link #getVCount()}, inclusive
*/
public Vector3D getNormal(int uIndex, int vIndex){
if (normal != null && normal[uIndex][vIndex] != null)
return new Vector3D(normal[uIndex][vIndex]);
Vector3D v1 = pointsOnSurface[uIndex][vIndex];
Vector3D v2, v4;
if (uIndex < uCount && vIndex < vCount) {
v2 = pointsOnSurface[uIndex+1][vIndex];
v4 = pointsOnSurface[uIndex][vIndex+1];
}
else if (uIndex == uCount && vIndex < vCount) {
v2 = pointsOnSurface[uIndex][vIndex+1];
v4 = pointsOnSurface[uIndex-1][vIndex];
}
else if (uIndex < uCount && vIndex == vCount) {
v2 = pointsOnSurface[uIndex][vIndex-1];
v4 = pointsOnSurface[uIndex+1][vIndex];
}
else {
v2 = pointsOnSurface[uIndex-1][vIndex];
v4 = pointsOnSurface[uIndex][vIndex-1];
}
Vector3D a = v2.minus(v1);
Vector3D b = v4.minus(v1);
Vector3D normal = a.cross(b);
return normal;
}
/**
* Returns a unit normal vector at a specified vertex in the basic grid. The return value is
* obtained by normalizing the return value of {@link #getNormal(int, int)}.
* However, if this yields an undefined result, a default value of (1,0,0)
* is returned.
* @param uIndex the u index of the vertex, in the range 0 to {@link #getUCount()}, inclusive
* @param vIndex the v index of the vertex, in the range 0 to {@link #getVCount()}, inclusive
*/
public Vector3D getUnitNormal(int uIndex, int vIndex) {
Vector3D N = getNormal(uIndex,vIndex);
N.normalize();
if (Double.isInfinite(N.x) || Double.isNaN(N.x) ||Double.isInfinite(N.y) || Double.isNaN(N.y) ||
Double.isInfinite(N.z) || Double.isNaN(N.z))
N = new Vector3D(1,0,0);
return N;
}
/**
* Removes all normal vectors that were set using {@link #setNormal(int, int, Vector3D)}.
*/
public void clearNormals() {
normal = null;
}
/**
* Set the default color that is used as the intrinsic color of a patch for which no other color has been
* set. The default default patch color is white. The default color is used for any patch for which
* no color has been set with {@link #setPatchColor(int, int, Color)}.
* @param c the default patch color; if null, white is used as the default color
*/
public void setDefaultPatchColor(Color c) {
defaultPatchColor = (c == null)? Color.white : c;
}
/**
* Get the default patch color.
* @see #setDefaultPatchColor(Color)
*/
public Color getDefaultPatchColor() {
return defaultPatchColor;
}
/**
* Get the default color for the back face of a patch.
* @see #setDefaultBackColor(Color)
*/
public Color getDefaultBackColor() {
return defaultBackColor;
}
/**
* Set the default color for the back face of a patch. This color is used
* only if no specific color has been set for the patch using the
* {@link #setPatchColor(int, int, Color)} method.
* @param defaultBackColor The default backface color; if null, the back color is the same as the front color.
*/
public void setDefaultBackColor(Color defaultBackColor) {
this.defaultBackColor = defaultBackColor;
}
// ------------------------- methods for rendering the surface -------------------------------
/**
* Applies a transform the all vertices of the surface, to produce a set of transformed vertices.
* It must be called before the drawing routines in this class are called.
* This method, like the drawing routines in this class, is called by the surface drawing routines in
* the View3D and View3DLit classes -- most programmers will use those routines to draw the surface
* and will not have to call this routine directly.
* @param view3D used for clipping
*/
public void applyTransform(Transform3D transform, View3D view3D) {
int copies = 1;
if (transforms != null) {
copies = transforms.size();
if (useIdentityTransform)
copies++;
}
if (transformedData == null || transformedData.length != copies) {
transformedData = new Vector3D[copies][uCount][vCount];
transformedVCurves = new Vector3D[copies][vCount][uCount];
}
int offset = 0;
if (transforms == null || useIdentityTransform) {
for (int i = 0; i < uCount; i += 1) {
for (int j = 0; j < vCount; j += 1) {
if (pointsOnSurface[i][j] == null || view3D.clip(pointsOnSurface[i][j]))
transformedData[0][i][j] = null;
else {
if (transformedData[0][i][j] == null)
transformedData[0][i][j] = new Vector3D();
transform.objectToViewCoords(pointsOnSurface[i][j],transformedData[0][i][j]);
}
transformedVCurves[0][j][i] = transformedData[0][i][j];
}
offset = 1;
}
}
if (transforms != null) {
for (int copyNum = offset; copyNum < copies; copyNum++) {
GridTransformMatrix gridTransform = transforms.get(copyNum-offset);
for (int i = 0; i < uCount; i += 1) {
for (int j = 0; j < vCount; j += 1) {
if (pointsOnSurface[i][j] == null)
transformedData[copyNum][i][j] = null;
else {
Vector3D gridPt = gridTransform.apply(pointsOnSurface[i][j]);
if (view3D.clip(gridPt))
transformedData[copyNum][i][j] = null;
else {
if (transformedData[copyNum][i][j] == null)
transformedData[copyNum][i][j] = new Vector3D();
transform.objectToViewCoords(gridPt,transformedData[copyNum][i][j]);
}
}
transformedVCurves[copyNum][j][i] = transformedData[copyNum][i][j];
}
}
}
}
patchesValid = subPatchesValid = false;
}
/**
* Draws a wireframe rendering of the surface by drawing some or all of the grid lines in the U and V
* directions. The choice of which lines to draw is determined by the uCurveIncrement and vCurveIncrement
* properties. This method is called by the drawing routines in the View3D and View3DLit classes, and
* will not generally be called directly. This must be called after {@link #applyTransform(Transform3D, View3D)}
* has been called.
* @see #setUCurveIncrement(int)
* @see #setVCurveIncrement(int)
*/
public void drawCurves(View3D view, Graphics2D g) {
int uInc = uCurveIncrement;
int vInc = vCurveIncrement;
if (uInc == 0 && vInc == 0)
uInc = vInc = subPatchesPerPatch;
if (uInc > 0) {
for (int copyNum = 0; copyNum < transformedData.length; copyNum++) {
for (int i = 0; i < uCount-1; i += uInc)
drawCurve(transformedData[copyNum][i], view, g);
drawCurve(transformedData[copyNum][uCount-1], view, g);
}
}
if (vInc > 0) {
for (int copyNum = 0; copyNum < transformedVCurves.length; copyNum++) {
for (int i = 0; i < vCount-1; i += vInc)
drawCurve(transformedVCurves[copyNum][i], view, g);
drawCurve(transformedVCurves[copyNum][vCount-1], view, g);
}
}
}
/**
* Draws some or all of the subpatches that make up the surface as shaded, lighted color patches.
* This method is called by {@link View3DLit#drawSurface(Grid3D, double, double)} and will probably not
* be used directly. If it is, it is essential that {@link #applyTransform(Transform3D, View3D)} be called
* before this method is called. The startPercent and endPercent are in the range 0 to 1, and they
* specify the subset of patches that are to be drawn.
* Lighting settings from the View3DLit are applied to the patches that are drawn.
* This method must be called after {@link #applyTransform(Transform3D, View3D)}.
*/
public void drawSubPatches(View3DLit view, Graphics2D g, double startPercent, double endPercent) {
computeSubPatches();
drawPatches(subpatches, 1, view, view.getPhongShading(), startPercent, endPercent);
}
/**
* Draws a "rough" version of the surface by drawing the (major) patches rather than the subpatches.
* The drawing uses flat shading; other than that, lighting settings are taken from the View3DLit.
* This method is called by {@link View3DLit#drawSurface(Grid3D, double, double)} and will probably not
* be used directly. If it is, it is essential that {@link #applyTransform(Transform3D, View3D)} be called
* before this method is called.
*/
public void drawMajorPatches(View3DLit view, Graphics2D g) {
computeMajorPatches();
drawPatches(patches, subPatchesPerPatch, view, false, 0, 1);
}
private void computeSubPatches() {
if (subpatches == null || !subPatchesValid) {
if (subpatches == null) {
subpatches = new ArrayList