import java.awt.event.*; import java.awt.*; import java.applet.*; import java.io.*; import java.awt.Toolkit; import java.util.Vector; import javax.vecmath.*; import java.util.Enumeration; // for Timer interface MyComparator { public int compare (Object o1, Object o2); } class MyArrays { public static Object[] toArray (Vector v) { Object[] result = new Object[v.size()]; for (int i = 0; i < v.size(); ++i) result[i] = v.elementAt(i); return result; } public static void sort (Object[] array, MyComparator comparator) { for (int i = 0; i < (array.length - 1); ++i) { for (int j = i+1; j < array.length; ++j) { if (comparator.compare (array[i], array[j]) > 0) { // swap to put the smaller in front Object temp = array[i]; array[i] = array[j]; array[j] = temp; } } } } } class Surface { // _points is a Vector of Point3d objects. Vector _points = new Vector(); public Vector points() {return _points;} public Point3d point(int index) {return (Point3d)_points.elementAt (index);} double _maxDiameter; public double maxDiameter() {return _maxDiameter;} double _minX, _maxX, _minY, _maxY, _minZ, _maxZ; boolean _gotFirstPoint = false; // This is a cache to be used by the Scene rendering routine. // It is the rotated (not translated) version of _points. Vector _scenePoints = new Vector(); public Vector scenePoints() {return _scenePoints;} public Point3d scenePoint(int index) {return (Point3d)_scenePoints.elementAt (index);} // scene origin is used by the Scene rendering engine. // It is the surface origin rotated and translated to scene coordinates Point3d _sceneOrigin = new Point3d(); public Point3d sceneOrigin() {return _sceneOrigin;} // This is the origin in world coordinates independent of the camera. Point3d _origin = new Point3d(); public Point3d origin() {return _origin;} // This is the rotation in world coordinates, independent of the camera Matrix3d _rotation = new Matrix3d (1., 0., 0., 0., 1., 0., 0., 0., 1.); Matrix3d rotation() {return _rotation;} // default color to gray public double _red = .8, _green = .8, _blue = .8; double _frictionAngle = Math.PI/9; public double frictionAngle() {return _frictionAngle;} // _triangles is a Vector of int[3] where the integers index into _points. // The cross product of the vector pointing from the first to second point, // and the vector pointing from the second to third point, points // outward from the surface. Vector _triangles = new Vector(); public Vector triangles() {return _triangles;} public int[] triangle(int index) {return (int [])_triangles.elementAt (index);} // Return the rotated point of the specified triangle index. // pointIndex is 0, 1 or 2. public Point3d rotatedTrianglePoint (int triangleIndex, int pointIndex) { return (Point3d)_scenePoints.elementAt (((int [])_triangles.elementAt (triangleIndex))[pointIndex]); } // Set the rotation to first to r, then a rotation of pitch around Y then // yaw around Z. void setRotation (Matrix3d r, double pitch, double yaw) { Matrix3d ry = new Matrix3d(); Matrix3d rz = new Matrix3d(); Matrix3d temp = new Matrix3d(); ry.rotY (pitch); rz.rotZ (yaw); temp.mul (ry, r); _rotation.mul (rz, temp); } // Adds copies of point to _points and _scenePoints. // Also updates maxDiameter. void addPoint (Point3d point) { _points.addElement (new Point3d (point)); _scenePoints.addElement (new Point3d (point)); if (_gotFirstPoint) { _minX = point.x; _maxX = point.x; _minY = point.y; _maxY = point.y; _minZ = point.z; _maxZ = point.z; _gotFirstPoint = true; } else { _minX = Math.min (point.x, _minX); _maxX = Math.max (point.x, _maxX); _minY = Math.min (point.y, _minY); _maxY = Math.max (point.y, _maxY); _minZ = Math.min (point.z, _minZ); _maxZ = Math.max (point.z, _maxZ); } double temp1 = Math.max (Math.abs(_maxX - _minX), Math.abs(_maxY - _minY)); double temp2 = Math.max (Math.abs(_maxZ - _minZ), temp1); _maxDiameter = Math.max (_maxDiameter, temp2); } // Adds the triangle given by points a, b, c to the surface. // The normal points out from the surface as defined by (b-a) cross (c-b) // This adds a copy of each point to _points and _scenePoints if it is not // already there // Also updates maxDiameter. public void addTriangle (Point3d a, Point3d b, Point3d c) { int aIndex = _points.indexOf (a); if (aIndex == -1) { addPoint (a); aIndex = _points.size() - 1; } int bIndex = _points.indexOf (b); if (bIndex == -1) { addPoint (b); bIndex = _points.size() - 1; } int cIndex = _points.indexOf (c); if (cIndex == -1) { addPoint (c); cIndex = _points.size() - 1; } int triangle[] = {aIndex, bIndex, cIndex}; _triangles.addElement (triangle); } } class CubeSurface extends Surface { CubeSurface () { // define the 8 vertices Point3d v1 = new Point3d (-.050, -.050, .050); Point3d v2 = new Point3d (-.050, .050, .050); Point3d v3 = new Point3d ( .050, .050, .050); Point3d v4 = new Point3d ( .050, -.050, .050); Point3d v5 = new Point3d (-.050, -.050, -.050); Point3d v6 = new Point3d (-.050, .050, -.050); Point3d v7 = new Point3d ( .050, .050, -.050); Point3d v8 = new Point3d ( .050, -.050, -.050); // add the six surfaces, two triangles per surface // front addTriangle (v1, v4, v3); addTriangle (v1, v3, v2); // back addTriangle (v5, v6, v7); addTriangle (v5, v7, v8); // left addTriangle (v5, v1, v2); addTriangle (v5, v2, v6); // right addTriangle (v4, v8, v7); addTriangle (v4, v7, v3); // top addTriangle (v2, v3, v7); addTriangle (v2, v7, v6); // bottom addTriangle (v1, v5, v8); addTriangle (v1, v8, v4); _red = 0.; _green = 1.; _blue = 0.; } // override for debug public void addTriangle (Point3d a, Point3d b, Point3d c) { super.addTriangle (a, b, c); { Point3d aa = new Point3d (a); Point3d bb = new Point3d (b); Point3d cc = new Point3d (c); Point3d offset = new Point3d (.071, -0.048, 0.); aa.scale (.2); aa.add (offset); bb.scale (.2); bb.add (offset); cc.scale (.2); cc.add (offset); // super.addTriangle (aa, bb, cc); } { Point3d aa = new Point3d (a); Point3d bb = new Point3d (b); Point3d cc = new Point3d (c); Point3d offset = new Point3d (.12, -0.008, 0.); aa.scale (.2); aa.add (offset); bb.scale (.2); bb.add (offset); cc.scale (.2); cc.add (offset); // super.addTriangle (aa, bb, cc); } { Point3d aa = new Point3d (a); Point3d bb = new Point3d (b); Point3d cc = new Point3d (c); Point3d offset = new Point3d (-.15, 0.01, 0.05); aa.scale (.6); aa.add (offset); bb.scale (.6); bb.add (offset); cc.scale (.6); cc.add (offset); // super.addTriangle (aa, bb, cc); } } } class STLSurface extends Surface { // If inlineFile is true, the file is actually a String with the data, // otherwise it is the name of a file to read. // If can't file filename, print an error and leave points().size() as zero. STLSurface (String file, boolean inlineFile) { try { BufferedReader reader; if (inlineFile) reader = new BufferedReader (new StringReader (file)); else reader = new BufferedReader (new FileReader (new File (file))); StreamTokenizer tokenizer = new StreamTokenizer (reader); tokenizer.resetSyntax(); tokenizer.whitespaceChars (0, 0x20); tokenizer.wordChars (0x21, 0xff); read (tokenizer); reader.close (); // Find x, y and z range and recenter double minX, maxX, minY, maxY, minZ, maxZ; minX = point (0).x; minY = point (0).y; minZ = point (0).z; maxX = minX; maxY = minY; maxZ = minZ; for (int i = 1; i < _points.size(); ++i) { Point3d point = point (i); if (point.x < minX) minX = point.x; if (point.x > maxX) maxX = point.x; if (point.y < minY) minY = point.y; if (point.y > maxY) maxY = point.y; if (point.z < minZ) minZ = point.z; if (point.z > maxZ) maxZ = point.z; } // Find the average of the supplied surface Point3d average = new Point3d ((maxX - minX) / 2., (maxY - minY) / 2., (maxZ - minZ) / 2.); // Adjust all points around center for (int i = 0; i < _points.size(); ++i) { point (i).sub (average); } _red = 0.; _green = 1.; _blue = 0.; } catch (FileNotFoundException e) { System.out.println ("ERROR: Can't find file " + file); return; } catch (IOException e) { return; } } void read (StreamTokenizer tokenizer) throws IOException { try { Point3d vertex1, vertex2, vertex3, temp; Vector3d normal, side1 = new Vector3d(), side2 = new Vector3d(), surfaceNormal = new Vector3d(); while (true) { if ((temp = readPoint (tokenizer, "normal")) == null) return; // We actually wanted a vector normal = new Vector3d (temp); if ((vertex1 = readPoint (tokenizer, "vertex")) == null) return; if ((vertex2 = readPoint (tokenizer, "vertex")) == null) return; if ((vertex3 = readPoint (tokenizer, "vertex")) == null) return; // Check that the normal is in the correct direction side1.set (vertex2); side1.sub (vertex1); side2.set (vertex3); side2.sub (vertex2); surfaceNormal.cross (side1, side2); if (normal.dot (surfaceNormal) < 0.) { // vertices were specified in the wrong order, so reverse two of them temp = vertex2; vertex2 = vertex3; vertex3 = temp; } addTriangle (vertex1, vertex2, vertex3); } } catch (IOException e) { throw e; } } // Find the given label in the tokenizer stream and then return the next // three numbers as a point. // Return null if end of stream Point3d readPoint (StreamTokenizer tokenizer, String label) throws IOException { while (true) { if (tokenizer.nextToken() == StreamTokenizer.TT_EOF) return null; if (tokenizer.sval.equals (label)) break; } if (tokenizer.nextToken() == StreamTokenizer.TT_EOF) return null; double x = Double.valueOf (tokenizer.sval).doubleValue(); if (tokenizer.nextToken() == StreamTokenizer.TT_EOF) return null; double y = Double.valueOf (tokenizer.sval).doubleValue(); if (tokenizer.nextToken() == StreamTokenizer.TT_EOF) return null; double z = Double.valueOf (tokenizer.sval).doubleValue(); return new Point3d (x, y, z); } } class ProbeSurface extends Surface { ProbeSurface () { // define the 8 vertices Point3d v1 = new Point3d (0., -.002, .002); Point3d v2 = new Point3d (0., .002, .002); Point3d v3 = new Point3d (1., .003, .003); Point3d v4 = new Point3d (1., -.003, .003); Point3d v5 = new Point3d (0., -.002, -.002); Point3d v6 = new Point3d (0., .002, -.002); Point3d v7 = new Point3d (1., .003, -.003); Point3d v8 = new Point3d (1., -.003, -.003); // add the six surfaces, two triangles per surface // front addTriangle (v1, v4, v3); addTriangle (v1, v3, v2); // back addTriangle (v5, v6, v7); addTriangle (v5, v7, v8); // left addTriangle (v5, v1, v2); addTriangle (v5, v2, v6); // right addTriangle (v4, v8, v7); addTriangle (v4, v7, v3); // top addTriangle (v2, v3, v7); addTriangle (v2, v7, v6); // bottom addTriangle (v1, v5, v8); addTriangle (v1, v8, v4); _red = 0.; _green = 0.; _blue = 1.; } } class SurfaceZComparator implements MyComparator { public int compare (Object o1, Object o2) { double z1 = ((Surface)o1).sceneOrigin().z; double z2 = ((Surface)o2).sceneOrigin().z; if (z1 < z2) return -1; else if (z1 > z2) return 1; else return 0; } } class TriangleMaxZComparator implements MyComparator { Surface _surface; TriangleMaxZComparator (Surface surface) { _surface = surface; } double maxTriangleZ (int[] triangle) { double result = _surface.scenePoint(triangle[0]).z; if (_surface.scenePoint(triangle[1]).z > result) result = _surface.scenePoint(triangle[1]).z; if (_surface.scenePoint(triangle[2]).z > result) result = _surface.scenePoint(triangle[2]).z; return result; } public int compare (Object o1, Object o2) { double z1 = maxTriangleZ ((int[])o1); double z2 = maxTriangleZ ((int[])o2); if (z1 < z2) return -1; else if (z1 > z2) return 1; else return 0; } } // View the world coordinates have Z toward the camera, X right, Y up // You can set cameraRotation to something else to get Z pointing up. class Scene { Vector3d _lightDirection = new Vector3d (0., 0., 1.); Matrix3d _cameraRotation = new Matrix3d (1., 0., 0., 0., 1., 0., 0., 0., 1.); public Matrix3d cameraRotation() {return _cameraRotation;} double _ambient = .1; double _zoom = 1000.; Vector _surfaces = new Vector(); public Vector surfaces() {return _surfaces;}; public Surface surface (int index) { return ((Surface)_surfaces.elementAt (index)); } // Set x = r * y public static void rotateTuple3d (Tuple3d x, Matrix3d r, Tuple3d y) { x.set (r.m00 * y.x + r.m01 * y.y + r.m02 * y.z, r.m10 * y.x + r.m11 * y.y + r.m12 * y.z, r.m20 * y.x + r.m21 * y.y + r.m22 * y.z); } // Set all _scenePoints in surface to its _points rotated by its rotation as // well as _cameraRotation. // Set _sceneOrigin to surface's origin rotated by _cameraRotation void rotateSurface (Surface surface) { Matrix3d r = new Matrix3d(); r.mul (_cameraRotation, surface.rotation()); for (int i = 0; i < surface.points().size(); ++i) { rotateTuple3d (surface.scenePoint(i), r, surface.point (i)); } rotateTuple3d (surface.sceneOrigin(), _cameraRotation, surface.origin()); } // Set screen x and y int[3] vectors to the x and y coordinates of the // triangle using _scenePoints of the surface. // This inverts the screen y values and multiplies x and y by _zoom void getPolygon (Surface surface, int[] triangle, int[] x, int[] y) { for (int i = 0; i < 3; ++i) { Point3d point = surface.scenePoint (triangle[i]); x[i] = (int)(_zoom * (point.x + surface.sceneOrigin().x)); y[i] = -(int)(_zoom * (point.y + surface.sceneOrigin().y)); } } // Return the surface normal (normalized) of the surface triangle at // index, using the surface _scenePoints. Vector3d surfaceNormal (int[] triangle, Surface surface) { Vector3d side1 = new Vector3d (surface.scenePoint (triangle[1])); Vector3d side2 = new Vector3d (surface.scenePoint (triangle[2])); // Use side1 which we haven't finished computing but still holds point 1 side2.sub (side1); side1.sub (surface.scenePoint (triangle[0])); Vector3d normal = new Vector3d(); normal.cross (side1, side2); normal.normalize(); return normal; } // The calling program has already called g.translate() to put the axis in // the center of the window, and cleared the buffer if necessary. // This does not first clear the buffer. void drawSurfaces (Graphics g) { int[] polyX = new int[3]; int[] polyY = new int[3]; // First rotate the points and origins for (int s = 0; s < _surfaces.size(); ++s) { rotateSurface (surface (s)); } // Sort the objects from back to front, where positive Z is // closer to the camera Object[] surfaceArray = MyArrays.toArray (_surfaces); MyArrays.sort (surfaceArray, new SurfaceZComparator()); for (int s = 0; s < _surfaces.size(); ++s) { Surface surface = (Surface)surfaceArray[s]; // Sort triangles from front to back Object[] triangleArray = MyArrays.toArray (surface.triangles()); MyArrays.sort (triangleArray, new TriangleMaxZComparator(surface)); for (int i = 0; i < triangleArray.length; ++i) { Vector3d normal = surfaceNormal ((int[])triangleArray[i], surface); // Only bother if the normal points toward the camera if (normal.z > 0.) { double cosLightingAngle = normal.dot (_lightDirection); double intensity = ((1. - _ambient) * cosLightingAngle) + _ambient; // make it blue g.setColor (new Color ((float)(intensity * surface._red), (float)(intensity * surface._green), (float)(intensity * surface._blue))); getPolygon (surface, (int[])triangleArray[i], polyX, polyY); g.fillPolygon (polyX, polyY, 3); } } } } void drawLine (Graphics g, Point2d a, Point2d b) { g.drawLine ((int)(a.x * _zoom), -(int)(a.y * _zoom), (int)(b.x * _zoom), -(int)(b.y * _zoom)); } // // The calling program has already called g.translate() to put the axis in // // the center of the window, and cleared the buffer if necessary. // // This does not first clear the buffer. // void drawEdges (Graphics g, Slice slice, Surface surface) { // Point3d rotatedA = new Point3d(); // Point3d rotatedB = new Point3d(); // Matrix3d r = new Matrix3d (); //// r.rotZ (-slice.theta()); //// r.mul (surface.rotation()); // r.mul (_cameraRotation, surface.rotation()); // // // main edges // g.setColor (Color.black); // for (int e = 0; e < slice.edges().size(); ++e) { // Edge edge = slice.edge (e); // Point2d a = edge.endA(), b = edge.endB(); // rotatedA.set (a.x, a.y, slice.z()); // rotatedB.set (b.x, b.y, slice.z()); // Slice.rotateTuple3d (rotatedA, r, rotatedA); // Slice.rotateTuple3d (rotatedB, r, rotatedB); // // drawLine (g, a, b); // //// g.drawLine ((int)((rotatedA.x + surface.sceneOrigin().x) * _zoom), //// -(int)((rotatedA.y + surface.sceneOrigin().y) * _zoom), //// (int)((rotatedB.x + surface.sceneOrigin().x) * _zoom), //// -(int)((rotatedB.y + surface.sceneOrigin().y) * _zoom)); // } // // // Do probeX then probeY // for (int probe = 0; probe <= 1; ++probe) { // Vector edgeSegments; // if (probe == 0) // edgeSegments = slice.probeXEdgeSegments(); // else // edgeSegments = slice.probeYEdgeSegments(); // // for (int e = 0; e < edgeSegments.size(); ++e) { // EdgeSegment edgeSegment = (EdgeSegment)edgeSegments.elementAt (e); // // g.setColor (probe == 0 ? Color.red : Color.green); // drawLine (g, edgeSegment.pointA(), edgeSegment.pointB()); //// Point2d temp2d = new Point2d(); //// if (probe == Slice.PROBE_X) { //// temp2d.set (0., .1); //// temp2d.add (edgeSegment.pointA()); //// drawLine (g, edgeSegment.pointA(), temp2d); //// } //// else { //// temp2d.set (.1, 0.); //// temp2d.add (edgeSegment.pointA()); //// drawLine (g, edgeSegment.pointA(), temp2d); //// } // // if (0 == 0) continue; // debug // // for (int direction = 0; direction <= 1; ++direction) { // Point2d otherContact = new Point2d(); //// if (probe == Slice.PROBE_X) { //// otherContact.set (.1, 0.03); //// otherContact.add (edgeSegment.pointA()); //// } //// else { //// otherContact.set (-.03, .1); //// otherContact.add (edgeSegment.pointA()); //// } // otherContact.set (0.034408597126073165, -0.03449369224355482); // double theta = edgeSegment.maxVertexDeltaTheta // (probe, direction, otherContact, surface.frictionAngle()); // theta = -theta; // We will draw the rotated probe, not workpiece // // if (probe == Slice.PROBE_X) // theta += Math.PI/2; // Point2d temp = new Point2d (.2*Math.cos (theta), .2*Math.sin(theta)); // temp.add (edgeSegment.pointA()); // // g.setColor ((direction == Slice.DECREASING) ? Color.magenta : Color.blue); // drawLine (g, edgeSegment.pointA(), temp); // } // } // } // } } class EdgeEnd extends Point2d { Edge _edge1, _edge2; public Edge edge1() {return _edge1;} public Edge edge2() {return _edge2;} public Edge otherEdge (Edge edge){return (edge == _edge1) ? _edge2 : _edge1;} // Surface edge always points in positive Z Vector3d _surfaceEdge; public Vector3d surfaceEdge () {return _surfaceEdge;} // Use trianglePoint1 and trianglePoint2 to compute the surfaceEdge vector. // This is a vector along the original surface edge from which the // slice edge end came. EdgeEnd (Tuple2d point, Point3d trianglePoint1, Point3d trianglePoint2) { super (point); _surfaceEdge = new Vector3d (trianglePoint1); _surfaceEdge.sub (trianglePoint2); if (_surfaceEdge.z < 0.) _surfaceEdge.scale (-1.); } void setEdge (Edge edge) { if (_edge1 == null) _edge1 = edge; else if (_edge2 == null) _edge2 = edge; else System.out.println ("ERROR: setEdge: more that one edge for this edgeEnd"); } void replaceEdge (Edge edgeToReplace, Edge edge) { if (_edge1 == edgeToReplace) _edge1 = edge; else if (_edge2 == edgeToReplace) _edge2 = edge; } } class Edge { EdgeEnd _endA, _endB; public EdgeEnd endA () {return _endA;} public EdgeEnd endB () {return _endB;} // NOTE: lowerX() and higherX() are always different, even if same X public EdgeEnd lowerX() {return ((_endA.x < _endB.x) ? _endA : _endB);} public EdgeEnd higherX() {return ((_endA.x < _endB.x) ? _endB : _endA);} public EdgeEnd lowerY() {return ((_endA.y < _endB.y) ? _endA : _endB);} public EdgeEnd higherY() {return ((_endA.y < _endB.y) ? _endB : _endA);} public EdgeEnd otherEnd (EdgeEnd edgeEnd) {return (edgeEnd == _endA) ? _endB : _endA;} Vector3d _surfaceNormal; public Vector3d surfaceNormal() {return _surfaceNormal;} // These is computed in the constructor // The normal to the edge in the slice plane Vector2d _edgeNormal; public Vector2d edgeNormal() {return _edgeNormal;} // Do not bother making a copy of any of these arguments Edge (EdgeEnd endA, EdgeEnd endB, Vector3d surfaceNormal) { _endA = endA; _endB = endB; _surfaceNormal = surfaceNormal; _edgeNormal = new Vector2d (_surfaceNormal.x, _surfaceNormal.y); _edgeNormal.normalize(); } // return the Y value on the edge that has value x. // If both endpoints have the same x, return the y of _endA. public double yAtX (double x) { if (_endA.x == _endB.x) return _endA.y; double factor = (x - lowerX().x) / (higherX().x - lowerX().x); return (lowerX().y + (higherX().y - lowerX().y) * factor); } // return the X value on the edge that has value y. // If both endpoints have the same y, return the X of _endA. public double xAtY (double y) { if (_endA.y == _endB.y) return _endA.x; double factor = (y - lowerY().y) / (higherY().y - lowerY().y); return (lowerY().x + (higherY().x - lowerY().x) * factor); } public String toString() { return ("[" + _endA + _endB + "]"); } } class EdgeSegment { // An EdgeSegment can either be a segment along an edge or a vertex. boolean _isVertex; public boolean isVertex() {return _isVertex;} // pointA to pointB moves around the peremiter in increasing theta // Probe contact at pointA is best for DECREASING theta // Probe contact at pointB is best for INCREASING theta Point2d _pointA, _pointB; public Point2d pointA() {return _pointA;} public Point2d pointB() {return _pointB;} // only set if not vertex. Edge _parentEdge; // only set if vertex; EdgeEnd _parentEdgeEnd; // probe is Slice.PROBE_X or Slice.PROBE_Y, the intended contacting probe int _probe; public int probe() {return _probe;} Vector3d _surfaceNormal; public Vector3d surfaceNormal() {return _surfaceNormal;} Vector2d _edgeNormal; public Vector2d edgeNormal() {return _edgeNormal;} // Normalized vector pointing along edge in increasing theta // (from pointA to pointB) Vector2d _edgeTangent; public Vector2d edgeTangent() {return _edgeTangent;} // This constructor is used when it is a real segment of an edge EdgeSegment (Point2d pointA, Point2d pointB, Edge parentEdge, int probe) { _pointA = pointA; _pointB = pointB; _parentEdge = parentEdge; _probe = probe; // Just point to parent's, assuming it won't change _surfaceNormal = _parentEdge.surfaceNormal(); _edgeNormal = _parentEdge.edgeNormal(); _edgeTangent = new Vector2d (_pointB); _edgeTangent.sub (_pointA); _edgeTangent.normalize(); } // This constructor is used when it is a vertex. // probe is Slice.PROBE_X or Slice.PROBE_Y and is used to compute the // effective surface normal. // Assume the vertex is convex. EdgeSegment (EdgeEnd parentEdgeEnd, int probe) { // Make both points the same. _pointA = new Point2d (parentEdgeEnd); _pointB = _pointA; _parentEdgeEnd = parentEdgeEnd; _probe = probe; _isVertex = true; _surfaceNormal = effectiveSurfaceNormal (_parentEdgeEnd.surfaceEdge(), probe); _edgeNormal = new Vector2d (_surfaceNormal.x, _surfaceNormal.y); _edgeNormal.normalize(); // For an edgeTangent, just rotate the edgeNormal. Since pointA and // pointB are the same, it really doesn't matter. _edgeTangent = new Vector2d (-_edgeNormal.y, _edgeNormal.x); } // probe is Slice.PROBE_X or Slice.PROBE_Y and is used to compute the // effective surface normal. // surfacEdge always points in positive Z public static Vector3d effectiveSurfaceNormal (Vector3d surfaceEdge, int probe) { Vector3d probeV; if (probe == Slice.PROBE_X) probeV = new Vector3d (0., -1., 0.); else // PROBE_Y probeV = new Vector3d (1., 0., 0.); Vector3d surfaceNormal = new Vector3d(); surfaceNormal.cross (probeV, surfaceEdge); surfaceNormal.normalize(); return (surfaceNormal); } // direction is Slice.INCREASING or Slice.DECREASING. // based on the position of the otherProbeContact, compute the // best position along this edge segment of this probe based on // the parent edge's surface normal and the given frictionAngle. // If this edge is a vertex, return null. (Shouldn't happen). // First check if the preferred position is within the friction // cone of the grip and if not try moving along the edge segment. // If there is no satisfactory contact, return null. Point2d bestContact (int direction, EdgeSegment otherEdge, Point2d otherProbeContact, double frictionAngle) { if (_isVertex) return null; if (otherEdge.isVertex()) direction = (direction == Slice.INCREASING) ? Slice.DECREASING : Slice.INCREASING; // First check if the preferred contact is within the // friction cone. Point2d result; if (direction == Slice.INCREASING) result = new Point2d (_pointB); else result = new Point2d (_pointA); boolean myFrictionOK = frictionOK (result, otherProbeContact, frictionAngle); if (myFrictionOK && otherEdge.frictionOK (otherProbeContact, result, frictionAngle)) // The preferred contact is within the friction cone of this edge and // the other contact is also within its friction cone, so return. return (result); double gripN, gripT; Vector2d grip; if (!myFrictionOK) { // Try to move the contact point where the friction is OK for this edge. // To find grip as a normalized vector in the possible grip axis, // solve: grip dot surfaceNormal = cos (frictionAngle). // If we represent these relative to the edge normal in the plane: // gripN*surfaceN + gripT*surfaceT = cos (frictionAngle) // Where gripT is the grip tangent to the edge, etc. // We know surfaceT is zero, so: // gripN = cos (frictionAngle) / surfaceN. gripN = Math.cos (frictionAngle) / Slice.xyDot (_edgeNormal, _surfaceNormal); // We want to solve sqrt (gripN^2 + gripT^2) = 1 if (gripN > 1.) // The surface normal points away from the plane too much return null; gripT = Math.sqrt (1. - gripN * gripN); // Now we can define the normalized grip axis depending on which way // we are moving the contact along the edge. // The grip points from the otherProbeContact to this edge segment. grip = new Vector2d (edgeNormal()); grip.scale (gripN); if (direction == Slice.INCREASING) grip.scaleAdd (gripT, _edgeTangent, grip); else // DECREASING grip.scaleAdd (-gripT, _edgeTangent, grip); // grip should already be normalized but just make sure grip.normalize(); // Now find where the grip vector going from otherProbeContact to the // edge would intersect the edge. result = Slice.rayLineIntersection2d (otherProbeContact, grip, _pointA, _edgeTangent); // If we still aren't within the edge, then moving the point // further isn't going to help if (result == null || !containsPoint (result, _pointA, _pointB)) return null; // If we are within the other contact's friction cone the we are done if (otherEdge.frictionOK (otherProbeContact, result, frictionAngle)) return result; } // Either the preferred contact for this edge or a modified // contact to bring a grip just within the friction cone for // this edge is not a valid grip for the other contact. // Try to move farther along the edge to bring the grip // within the friction cone of the other contact. // As above, get gripN and gripT for the other edge. gripN = Math.cos (frictionAngle) / Slice.xyDot (otherEdge.edgeNormal(), otherEdge.surfaceNormal()); if (gripN > 1.) // The surface normal points away from the plane too much return null; gripT = Math.sqrt (1. - gripN * gripN); // Now we can define the normalized grip axis depending on which way // we are moving the contact along the edge. // The grip points from the otherProbeContact to this edge segment. grip = new Vector2d (otherEdge.edgeNormal()); // Use negative to point it from the other edge toward this edge grip.scale (-gripN); // Remember that because the edgeTangent is defined going around the // perimeter, it will point in the opposite direction on the opposite // edge if (direction == Slice.INCREASING) grip.scaleAdd (-gripT, otherEdge.edgeTangent(), grip); else // DECREASING grip.scaleAdd (gripT, otherEdge.edgeTangent(), grip); // grip should already be normalized but just make sure grip.normalize(); // Now find where this grip vector going from otherProbeContact to the // edge would intersect the edge. result = Slice.rayLineIntersection2d (otherProbeContact, grip, _pointA, _edgeTangent); // This point must be on the edge segment and also in its friction cone. if (result == null || !containsPoint (result, _pointA, _pointB)) return null; if (frictionOK (result, otherProbeContact, frictionAngle)) // We are still within the friction cone for the contact on this edge. return result; return null; } // Return true if the point is between pointA and pointB by making // sure the point points to pointA and pointB in different directions. static boolean containsPoint (Point2d point, Point2d pointA, Point2d pointB){ Vector2d toA = new Vector2d (pointA); toA.sub (point); Vector2d toB = new Vector2d (pointB); toB.sub (point); if (toA.dot(toB) > 0.) return false; else return true; } // Just check myContact against a grip with otherProbeContact boolean frictionOK (Point2d myContact, Point2d otherProbeContact, double frictionAngle) { if (Math.abs (myContact.x - otherProbeContact.x) < 1e-6 && Math.abs (myContact.y - otherProbeContact.y) < 1e-6) // These are the same points return false; Vector2d grip = new Vector2d (myContact); grip.sub (otherProbeContact); grip.normalize(); double k = Slice.xyDot (grip, _surfaceNormal); // Make sure the other contact is on the correct side of the edge. if (k <= 0.) return false; if (k > (Math.cos (frictionAngle) - 1e-6)) return true; else return false; } // probe must be PROBE_X or PROBE_Y // direction must be DECREASING or INCREASING. // return how much theta can change in the given direction for // a probe (PROBE_X or PROBE_Y) touching the vertex, based only // on the maximum rotation to keep the grip with otherProbeContact // within the friction cone. Also prevent the tip of the // probe from passing parallel to the further edge coming off the vertex, // but don't concern with other parts hitting the probe. // The result will be positive if direction is INCREASING, // or negative if DECREASING (or zero). double maxVertexDeltaTheta (int probe, int direction, Point2d otherProbeContact, double frictionAngle) { if (!_isVertex) // Shouldn't really happen return 0.; // Find the angles of the grip and the two edges coming off the vertex // relative to the probe normal. Vector2d gripV = new Vector2d (otherProbeContact); // _pointA and _pointB are the same gripV.sub (_pointA); Vector2d edge1V = new Vector2d (_parentEdgeEnd.edge1().otherEnd (_parentEdgeEnd)); edge1V.sub (_pointA); Vector2d edge2V = new Vector2d (_parentEdgeEnd.edge2().otherEnd (_parentEdgeEnd)); edge2V.sub (_pointA); double gripAngle, edge1Angle, edge2Angle; if (probe == Slice.PROBE_X) { // Probe comes down from positive Y gripAngle = Math.atan2 (gripV.y, gripV.x); edge1Angle = Math.atan2 (edge1V.y, edge1V.x); edge2Angle = Math.atan2 (edge2V.y, edge2V.x); } else { // PROBE_Y // Probe comes in from positive X gripAngle = Math.atan2 (-gripV.x, gripV.y); edge1Angle = Math.atan2 (-edge1V.x, edge1V.y); edge2Angle = Math.atan2 (-edge2V.x, edge2V.y); } if (! (gripAngle > -Math.PI/2 && gripAngle < Math.PI/2 && edge1Angle > -Math.PI/2 && edge1Angle < Math.PI/2 && edge2Angle > -Math.PI/2 && edge2Angle < Math.PI/2)) // grip or edges don't point away from the probe contact in // a valid direction. return 0.; // Find the maximum we can rotate due to limiting the probe tip // from hitting an edge double maxDeltaThetaTip; if (probe == Slice.PROBE_X) { if (direction == Slice.INCREASING) // We are rotating away from the probe tip, so set to max maxDeltaThetaTip = 2*Math.PI; else { double closestEdgeAngle = (edge1Angle < edge2Angle) ? edge1Angle : edge2Angle; maxDeltaThetaTip = (-Math.PI/2) - closestEdgeAngle; } } else { // PROBE_Y if (direction == Slice.DECREASING) // We are rotating away from the probe tip, so set to max maxDeltaThetaTip = -2*Math.PI; else { double closestEdgeAngle = (edge1Angle > edge2Angle) ? edge1Angle : edge2Angle; maxDeltaThetaTip = Math.PI/2 - closestEdgeAngle; } } // Now compute the angle allowed by the friction cone // To find grip as a normalized vector in the possible grip axis, // solve: grip dot surfaceNormal = cos (frictionAngle). // If we represent these relative to the edge normal in the plane: // gripN*surfaceN + gripT*surfaceT = cos (frictionAngle) // Where gripT is the grip tangent to the edge, etc. // We know surfaceT is zero, so: // gripN = cos (frictionAngle) / surfaceN. double gripN = Math.cos (frictionAngle) / Slice.xyDot (_edgeNormal, _surfaceNormal); if (gripN > 1.) // The surface normal points away from the plane too much return 0.; // So, sliceFrictionAngle is the friction angle in this slice double sliceFrictionAngle = Math.acos (gripN); double maxDeltaThetaFriction; if (direction == Slice.INCREASING) { maxDeltaThetaFriction = sliceFrictionAngle - gripAngle; if (maxDeltaThetaFriction < 0.) maxDeltaThetaFriction = 0.; // Now return the worse of this and maxDeltaThetaTip if (maxDeltaThetaFriction < maxDeltaThetaTip) return maxDeltaThetaFriction; else return maxDeltaThetaTip; } else { // DECREASING maxDeltaThetaFriction = (-sliceFrictionAngle) - gripAngle; if (maxDeltaThetaFriction > 0.) maxDeltaThetaFriction = 0.; // Now return the worse of this and maxDeltaThetaTip if (maxDeltaThetaFriction > maxDeltaThetaTip) return maxDeltaThetaFriction; else return maxDeltaThetaTip; } } public String toString() { return ("[" + _pointA + _pointB + "]"); } } class EdgePair { EdgeSegment _probeXEdgeSegment; public EdgeSegment probeXEdgeSegment() {return _probeXEdgeSegment;} EdgeSegment _probeYEdgeSegment; public EdgeSegment probeYEdgeSegment() {return _probeYEdgeSegment;} Point2d _probeXContact; public Point2d probeXContact() {return _probeXContact;} Point2d _probeYContact; public Point2d probeYContact() {return _probeYContact;} // This is > 0 for INCREASING theta double _deltaTheta; double deltaTheta() {return _deltaTheta;} // Do not bother making a copy of the edges. Do copy the contact points // The contact points and edges are within the frame of the owner slice.theta EdgePair (EdgeSegment probeXEdgeSegment, EdgeSegment probeYEdgeSegment, Point2d probeXContact, Point2d probeYContact, double deltaTheta) { _probeXEdgeSegment = probeXEdgeSegment; _probeYEdgeSegment = probeYEdgeSegment; _probeXContact = new Point2d (probeXContact); _probeYContact = new Point2d (probeYContact); _deltaTheta = deltaTheta; } } class Slice { // deltaTheta will be > 0 if direction is Slice.INCREASING // otherwise < 0. This is the value from bestEdgePair. // If deltaTheta is 0., bestEdgePair may be null. double _deltaTheta; public double deltaTheta() {return _deltaTheta;} // This can be null if there is no valid grip or deltaTheta would be 0. EdgePair _bestEdgePair; public EdgePair bestEdgePair() {return _bestEdgePair;} Surface _surface; public Surface surface() {return _surface;} // remember rotation Matrix3d _rotation; public Matrix3d rotation() {return _rotation;} // Initial theta double _theta; public double theta() {return _theta;} double _z; public double z() {return _z;} // Vector of EdgeEnd. These are from the original slice boundary Vector _edgeEnds = new Vector(); public Vector edgeEnds() {return _edgeEnds;} public EdgeEnd edgeEnd(int index) {return (EdgeEnd)_edgeEnds.elementAt (index);} // Vector of Edge Vector _edges = new Vector(); public Vector edges() {return _edges;} public Edge edge(int index) {return (Edge)_edges.elementAt(index);} // Vector of EdgeSegment for edges used by probe that moves in X Vector _probeXEdgeSegments = new Vector(); public Vector probeXEdgeSegments() {return _probeXEdgeSegments;} public EdgeSegment probeXEdgeSegment(int index) {return (EdgeSegment)_probeXEdgeSegments.elementAt(index);} // Vector of EdgeSegment for edges used by probe that moves in Y Vector _probeYEdgeSegments = new Vector(); public Vector probeYEdgeSegments() {return _probeYEdgeSegments;} public EdgeSegment probeYEdgeSegment(int index) {return (EdgeSegment)_probeYEdgeSegments.elementAt(index);} // amount around edge points where the probe can't contact double _epsilon; public static final int PROBE_X = 0; public static final int PROBE_Y = 1; public static final int DECREASING = 0; public static final int INCREASING = 1; // rotate surface first by rotation then by theta around the Z axis. // direction is INCREASING or DECREASING. Set the best deltaTheta. // If deltaTheta zero, bestEdgePair may be null, otherwise it is // the edge pair for deltaTheta. Slice (Surface surface, Matrix3d rotation, double theta, double z, int direction) { _surface = surface; // Keep a copy of rotation _rotation = new Matrix3d (rotation); _theta = theta; _z = z; // Choose an epsilon based on the size of the part _epsilon = .05 * surface.maxDiameter(); setEdges (); _probeXEdgeSegments.removeAllElements(); _probeYEdgeSegments.removeAllElements(); addEdgeSegments(PROBE_X); addEdgeSegments(PROBE_Y); addEdgeSegmentsVertex(PROBE_X); addEdgeSegmentsVertex(PROBE_Y); setBestEdgePair(direction); if (_bestEdgePair != null) _deltaTheta = _bestEdgePair.deltaTheta(); } // Return dot product of (a.x, a.y, 0) with b static double xyDot (Tuple2d a, Tuple3d b) { return (a.x * b.x + a.y * b.y); } // a and b are points along two edges of a surface triangle. // commonTrianglePoint is the common point of the two triangle edges // and trianglePointA is the other along the edge that a came from. // (Same for trianglePointB.) These are send to EdgeEnd constructor // to compute the surfaceEdge vector. // If a and b are the same or if the edge is already added, do not add. void addEdge (Point2d a, Point2d b, Vector3d surfaceNormal, Point3d commonTrianglePoint, Point3d trianglePointA, Point3d trianglePointB) { if (a.equals (b)) { // shouldn't happen System.out.println ("ERROR: Slice.addEdge(): both edge ends are the same"); return; } // Check if already added (should not really happen) for (int i = 0; i < _edges.size(); ++i) { Edge edge = edge (i); if ((a.equals (edge.endA()) && b.equals (edge.endB())) || (a.equals (edge.endB()) && b.equals (edge.endA()))) { System.out.println ("ERROR: Slice.addEdge(): found equal edge"); return; } } int aIndex = _edgeEnds.indexOf (a); if (aIndex == -1) { _edgeEnds.addElement (new EdgeEnd (a, commonTrianglePoint, trianglePointA)); aIndex = _edgeEnds.size() - 1; } int bIndex = _edgeEnds.indexOf (b); if (bIndex == -1) { _edgeEnds.addElement (new EdgeEnd (b, commonTrianglePoint, trianglePointB)); bIndex = _edgeEnds.size() - 1; } EdgeEnd endA = edgeEnd (aIndex); EdgeEnd endB = edgeEnd (bIndex); Edge edge = new Edge (endA, endB, new Vector3d (surfaceNormal)); _edges.addElement (edge); endA.setEdge (edge); endB.setEdge (edge); } // If the 3d line between points a and b intersects the plane at _z, // return the 2d point on the xy plane, otherwise null. Point2d sliceIntersection (Point3d a, Point3d b) { if ((a.z == _z) && (a.z == b.z)) // Should have already checked for this return null; // Make a.z lower than b.z if (a.z > b.z) { Point3d temp = a; a = b; b = temp; } if (! (_z >= a.z && _z <= b.z)) // not in range return null; double factor = (_z - a.z) / (b.z - a.z); return new Point2d (a.x + (b.x - a.x) * factor, a.y + (b.y - a.y) * factor); } // Set x = r * y public static void rotateTuple3d (Tuple3d x, Matrix3d r, Tuple3d y) { x.set (r.m00 * y.x + r.m01 * y.y + r.m02 * y.z, r.m10 * y.x + r.m11 * y.y + r.m12 * y.z, r.m20 * y.x + r.m21 * y.y + r.m22 * y.z); } // set _edges to the slice at level _z // rotate first by _rotation then by _theta around the Z axis. void setEdges () { Matrix3d rz = new Matrix3d (); rz.rotZ (_theta); Matrix3d r = new Matrix3d (); r.mul (rz, _rotation); Point3d[] rotatedPoints = new Point3d[_surface.points().size()]; for (int i = 0; i < rotatedPoints.length; ++i) { rotatedPoints[i] = new Point3d(); rotateTuple3d (rotatedPoints[i], r, _surface.point (i)); } Vector3d side1 = new Vector3d(), side2 = new Vector3d(), surfaceNormal = new Vector3d(); _edges.removeAllElements(); for (int i = 0; i < _surface.triangles().size(); ++i) { int[] triangle = _surface.triangle (i); Point3d point0 = rotatedPoints[triangle[0]]; Point3d point1 = rotatedPoints[triangle[1]]; Point3d point2 = rotatedPoints[triangle[2]]; // First check for equal z. This should not happen because we // are supposed to pick z values between vertices. if (point0.z == _z || point1.z == _z || point2.z == _z) { System.out.println ("ERROR: Slice.setEdges(): z value same as vertex " + _z); continue; } // Now check for intersections Point2d intersection1 = sliceIntersection (point0, point1); Point2d intersection2 = sliceIntersection (point1, point2); Point2d intersection3 = sliceIntersection (point0, point2); // Compute the normal side1.set (point1); side1.sub (point0); side2.set (point2); side2.sub (point1); surfaceNormal.cross (side1, side2); surfaceNormal.normalize(); if (intersection1 != null && intersection2 != null) addEdge (intersection1, intersection2, surfaceNormal, point1, point0, point2); else if (intersection2 != null && intersection3 != null) addEdge (intersection2, intersection3, surfaceNormal, point2, point1, point0); else if (intersection1 != null && intersection3 != null) addEdge (intersection1, intersection3, surfaceNormal, point0, point1, point2); } // Integrity test: make sure each _edgeEnds connects two edges for (int e = 0; e < _edgeEnds.size(); ++e) { // if edge2 is not null then neither is edge1 if (edgeEnd (e).edge2() == null) System.out.println ("ERROR: setEdges: edge end does not connect two edges."); } // Now, if an both edges connected to an edgeEnd have the same surface // normal then the lines are continuous, so merge them. // Make i go backwards so when we remove it the indexing is still good boolean didMerge; do { didMerge = false; for (int i = _edgeEnds.size() - 1; i >= 0; --i) { EdgeEnd edgeEnd = edgeEnd (i); if (edgeEnd.edge1().surfaceNormal().equals (edgeEnd.edge2().surfaceNormal())) { didMerge = true; EdgeEnd mergedEndA = edgeEnd.edge1().otherEnd (edgeEnd); EdgeEnd mergedEndB = edgeEnd.edge2().otherEnd (edgeEnd); Edge mergedEdge = new Edge (mergedEndA, mergedEndB,edgeEnd.edge1().surfaceNormal()); _edges.addElement (mergedEdge); mergedEndA.replaceEdge (edgeEnd.edge1(), mergedEdge); mergedEndB.replaceEdge (edgeEnd.edge2(), mergedEdge); // All fixed. Now we can delete the edge end and two small edges _edges.removeElement (edgeEnd.edge1()); _edges.removeElement (edgeEnd.edge2()); _edgeEnds.removeElement (edgeEnd); } } } while (didMerge); } // rayV is a normalized vector for a ray starting from point rayP // lineV is a normalized vector along a line starting from lineP // Return the point on the line where the ray intersects the line // or null if no intersection in the direction of the ray. public static Point2d rayLineIntersection2d (Point2d rayP, Vector2d rayV, Point2d lineP, Vector2d lineV) { // We want to solve rayP + rayT*rayV = lineP + lineT*lineV, or // rayV*rayT - lineV*lineT = lineP - rayP. We split into two // equations for x and y and identify elements with the system: // ax - by = c // dx - ey = f where x is rayT and y is lineT. // This gives x = (ec - bf)/(ae - bd) and y = (cd - fa)/(ae - bd) double a = rayV.x, b = lineV.x, c = lineP.x - rayP.x; double d = rayV.y, e = lineV.y, f = lineP.y - rayP.y; double divisor = (a*e - b*d); if (divisor == 0.) // lines are parallel return null; double rayT = (e*c - b*f) / divisor; double lineT = (c*d - f*a) / divisor; if (rayT < -1e-6) // intersection not in direction of ray return null; Point2d result = new Point2d(); result.scaleAdd (lineT, lineV, lineP); return result; } // probe is PROBE_X or PROBE_Y // This adds non vertex EdgeSegments void addEdgeSegments (int probe) { Object[] edgeEndsArray = MyArrays.toArray (_edgeEnds); if (probe == PROBE_X) // Sort _vertices by X MyArrays.sort (edgeEndsArray, new Tuple2dXComparator()); else // Sort _vertices by Y MyArrays.sort (edgeEndsArray, new Tuple2dYComparator()); for (int e = 0; e < _edges.size(); ++e) { Edge edge = edge (e); // Before anything else, make sure that the surface normal is // within the friction cone // Get the normal in the slice plane Vector2d edgeNormal = new Vector2d (edge.surfaceNormal().x, edge.surfaceNormal().y); edgeNormal.normalize(); double cosFrictionAngle = Math.cos (_surface.frictionAngle()); if (Slice.xyDot (edgeNormal, edge.surfaceNormal()) < (cosFrictionAngle - 1e-6)) // Even our best grip is not within the friction cone continue; Point2d lower, higher; if (probe == PROBE_X) { if (edge.surfaceNormal().y < -1e-6) // edge faces away from the probe continue; lower = new Point2d (edge.lowerX()); higher = new Point2d (edge.higherX()); } else { if (edge.surfaceNormal().x < -1e-6) // edge faces away from the probe continue; lower = new Point2d (edge.lowerY()); higher = new Point2d (edge.higherY()); } // Compute a vector of epsilon length along edge Vector2d edgeEpsilon = new Vector2d (higher); edgeEpsilon.sub (lower); edgeEpsilon.normalize(); edgeEpsilon.scale (_epsilon); // Move lower and higher in by _epsilon lower.add (edgeEpsilon); higher.sub (edgeEpsilon); Vector2d edgeCheck = new Vector2d (higher); edgeCheck.sub (lower); if (edgeCheck.dot (edgeEpsilon) < 0.) // the new lower and higher crossed over each other so the edge // is too small. continue; if (((probe == PROBE_X) && (Math.abs (lower.x - higher.x) < 1e-6)) || ((probe == PROBE_Y) && (Math.abs (lower.y - higher.y) < 1e-6))) { // Special case. Just check for accessibility and add boolean collision = false; for (int j = 0; j < _edges.size(); ++j) { if (j == e) // Do not check this edge continue; Edge edgeToCheck = edge (j); if (probe == PROBE_X) { if ((lower.x > edgeToCheck.lowerX().x) && (lower.x < edgeToCheck.higherX().x)) { if (edgeToCheck.yAtX (lower.x) > edge.higherY().y) { collision = true; break; } } } else { if ((lower.y > edgeToCheck.lowerY().y) && (lower.y < edgeToCheck.higherY().y) && edgeToCheck.xAtY (lower.y) > edge.higherX().x) { collision = true; break; } } } if (!collision) { // Add the edge points in order consitent with usual case if (probe == PROBE_X) { if (edge.surfaceNormal().x < 0) _probeXEdgeSegments.addElement (new EdgeSegment (new Point2d (lower.x, edge.higherY().y - _epsilon), new Point2d (lower.x, edge.lowerY().y + _epsilon), edge, probe)); else _probeXEdgeSegments.addElement (new EdgeSegment (new Point2d (lower.x, edge.lowerY().y + _epsilon), new Point2d (lower.x, edge.higherY().y - _epsilon), edge, probe)); } else { // PROBE_Y if (edge.surfaceNormal().y < 0) _probeYEdgeSegments.addElement (new EdgeSegment (new Point2d (edge.lowerX().x + _epsilon, lower.y), new Point2d (edge.higherX().x - _epsilon, lower.y), edge, probe)); else _probeYEdgeSegments.addElement (new EdgeSegment (new Point2d (edge.higherX().x - _epsilon, lower.y), new Point2d (edge.lowerX().x + _epsilon, lower.y), edge, probe)); } } continue; } // These are for inserting lower and higher into the sorted search boolean didLower = false, didHigher = false; // Go through sorted edgeEndsArray, starting at 1 so we // can look at the previous // Because of epsilon, we know the very first cannot be used. // This chops up the edge into visible segments. Point2d previousVertex = (Point2d)edgeEndsArray[0]; int i = 1; while (i < edgeEndsArray.length) { Point2d vertex = (Point2d)edgeEndsArray[i]; // Insert lower or higher into the process Point2d substituteVertex = null; if (!didLower) { if (((probe == PROBE_X) && (lower.x >= previousVertex.x && lower.x <= vertex.x)) || ((probe == PROBE_Y) && (lower.y >= previousVertex.y && lower.y <= vertex.y))) { substituteVertex = lower; didLower = true; } } else if (!didHigher) { if (((probe == PROBE_X) && (higher.x >= previousVertex.x && higher.x <= vertex.x)) || ((probe == PROBE_Y) && (higher.y >= previousVertex.y && higher.y <= vertex.y))) { substituteVertex = higher; didHigher = true; } } // If we substituted, keep i the same so we look at the real vertex if (substituteVertex != null) vertex = substituteVertex; else // OK to increment i here because we don't use it again. ++i; // only interested in vertices on the accessible side if (probe == PROBE_X) { if (! (vertex.y >= (edge.yAtX (vertex.x) - 1e-6) || vertex.equals(lower) || vertex.equals(higher))) continue; } else { if (! (vertex.x >= (edge.xAtY (vertex.y) - 1e-6) || vertex.equals(lower) || vertex.equals(lower))) continue; } if (probe == PROBE_X) { if (vertex.x == previousVertex.x) // skip duplicate X values continue; } else { if (vertex.y == previousVertex.y) // skip duplicate Y values continue; } if (((probe == PROBE_X) && (previousVertex.x >= lower.x && vertex.x <= higher.x)) || ((probe == PROBE_Y) && (previousVertex.y >= lower.y && vertex.y <= higher.y))) { // we are in the edge so check for open along positive axis double midpointX = 0, midpointY = 0; double yAtMidpoint = 0, xAtMidpoint = 0; if (probe == PROBE_X) { midpointX = (previousVertex.x + vertex.x) / 2.; yAtMidpoint = edge.yAtX (midpointX); } else { midpointY = (previousVertex.y + vertex.y) / 2.; xAtMidpoint = edge.xAtY (midpointY); } boolean collision = false; for (int j = 0; j < _edges.size(); ++j) { if (j == e) // Do not check this edge continue; Edge edgeToCheck = edge (j); if (probe == PROBE_X) { if ((midpointX > edgeToCheck.lowerX().x) && (midpointX < edgeToCheck.higherX().x) && edgeToCheck.yAtX (midpointX) > yAtMidpoint) { collision = true; break; } } else { if ((midpointY > edgeToCheck.lowerY().y) && (midpointY < edgeToCheck.higherY().y) && edgeToCheck.xAtY (midpointY) > xAtMidpoint) { collision = true; break; } } } if (!collision) { // We create the segment so pointA to pointB moves around // the peremiter in increasing theta. previousVertex // has the lower value. if (probe == PROBE_X) _probeXEdgeSegments.addElement (new EdgeSegment (new Point2d (vertex.x, edge.yAtX (vertex.x)), new Point2d (previousVertex.x, edge.yAtX (previousVertex.x)), edge, probe)); else _probeYEdgeSegments.addElement (new EdgeSegment (new Point2d (edge.xAtY (previousVertex.y), previousVertex.y), new Point2d (edge.xAtY (vertex.y), vertex.y), edge, probe)); } } previousVertex = vertex; } } } // probe is PROBE_X or PROBE_Y // This adds vertex EdgeSegments void addEdgeSegmentsVertex (int probe) { for (int v = 0; v < _edgeEnds.size(); ++v) { EdgeEnd edgeEnd = edgeEnd (v); Vector2d edge1V = new Vector2d (edgeEnd.edge1().otherEnd (edgeEnd)); edge1V.sub (edgeEnd); Vector2d edge2V = new Vector2d (edgeEnd.edge2().otherEnd (edgeEnd)); edge2V.sub (edgeEnd); // Make sure both edges face away from the probe if (probe == PROBE_X) { if (edge1V.x < -1e-6 || edge2V.x < -1e-6) continue; } else { // PROBE_Y if (edge1V.y < -1e-6 || edge2V.y < -1e-6) continue; } // Before anything else, make sure that the surface normal is // within the friction cone // Get the normal in the slice plane Vector3d surfaceNormal = EdgeSegment.effectiveSurfaceNormal (edgeEnd.surfaceEdge(), probe); Vector2d edgeNormal = new Vector2d (surfaceNormal.x, surfaceNormal.y); edgeNormal.normalize(); double cosFrictionAngle = Math.cos (_surface.frictionAngle()); if (Slice.xyDot (edgeNormal, surfaceNormal) < (cosFrictionAngle - 1e-6)) // Even our best grip is not within the friction cone continue; // Check for accessibility boolean collision = false; for (int j = 0; j < _edges.size(); ++j) { Edge edgeToCheck = edge (j); if (edgeToCheck.endA() == edgeEnd || edgeToCheck.endB() == edgeEnd) // Do not check this edge with this edgeEnd continue; if (probe == PROBE_X) { if ((edgeEnd.x > edgeToCheck.lowerX().x) && (edgeEnd.x < edgeToCheck.higherX().x)) { if (edgeToCheck.yAtX (edgeEnd.x) > edgeEnd.y) { collision = true; break; } } } else { if ((edgeEnd.y > edgeToCheck.lowerY().y) && (edgeEnd.y < edgeToCheck.higherY().y) && edgeToCheck.xAtY (edgeEnd.y) > edgeEnd.x) { collision = true; break; } } } if (!collision) { // Add vertex as an edgeSegment if (probe == PROBE_X) _probeXEdgeSegments.addElement (new EdgeSegment (edgeEnd, probe)); else _probeYEdgeSegments.addElement (new EdgeSegment (edgeEnd, probe)); } } } // probe must be PROBE_X or PROBE_Y // direction must be DECREASING or INCREASING. // return how much theta can change in the given direction for // a probe (PROBE_X or PROBE_Y) touching the contact point on the // edgeSegment. // If edgeSegment.isVertex, then use otherProbeContact to make sure // the grip doesn't go outside the frictionAngle. // The result will be positive if direction is INCREASING, // or negative if DECREASING (or zero). double maxProbeDeltaTheta (int probe, int direction, Point2d contact, EdgeSegment edgeSegment, Point2d otherProbeContact) { double angle, bestAngle; if (edgeSegment.isVertex()) { bestAngle = edgeSegment.maxVertexDeltaTheta (probe, direction, otherProbeContact, _surface.frictionAngle()); if (Math.abs (bestAngle) < 1e-6) return 0.; } else { // The best angle is actually the least rotation, so start with the // maximum possible value if (direction == INCREASING) bestAngle = 2*Math.PI; else bestAngle = -2*Math.PI; } for (int e = 0; e < _edgeEnds.size(); ++e) { EdgeEnd edgeEnd = edgeEnd (e); if (Math.abs (edgeEnd.x - contact.x) < 1e-6 && Math.abs (edgeEnd.y - contact.y) < 1e-6) // don't check against itself continue; // Measure the angle relative to the probe if (probe == PROBE_X) // Positive rotation moves x in the negative direction angle = Math.atan2 (-(edgeEnd.x - contact.x), edgeEnd.y - contact.y); else // PROBE_Y // Positive rotation moves y in the positive direction angle = Math.atan2 (edgeEnd.y - contact.y, edgeEnd.x - contact.x); // That actually measured the probe moving to the workpiece but // we want the workpiece moving to the probe. angle = -angle; if (Math.abs (angle) < 1e-6) { // If the angle is not zero then assume the probe is moving // within the accessibility range already calculated for the // edge segment. // If it is zero, the probe may be resting against an edge end // which would not block the probe. Check that both edges // of the edgeEnd are pointing away from the direction that // the probe needs to move. If so, don't restrict bestAngle. if (probe == PROBE_X) { // Since angle is zero, contact.x == edgeEnd.x double deltaX1 = edgeEnd.edge1().otherEnd(edgeEnd).x - edgeEnd.x; double deltaX2 = edgeEnd.edge2().otherEnd(edgeEnd).x - edgeEnd.x; if (direction == INCREASING) { if (deltaX1 <= 1e-6 && deltaX2 <= 1e-6) continue; } if (direction == DECREASING) { if (deltaX1 >= -1e-6 && deltaX2 >= -1e-6) continue; } } else { // PROBE_Y // Since angle is zero, contact.y == edgeEnd.y double deltaY1 = edgeEnd.edge1().otherEnd(edgeEnd).y - edgeEnd.y; double deltaY2 = edgeEnd.edge2().otherEnd(edgeEnd).y - edgeEnd.y; if (direction == INCREASING) { if (deltaY1 >= -1e-6 && deltaY2 >= -1e-6) continue; } if (direction == DECREASING) { if (deltaY1 <= 1e-6 && deltaY2 <= 1e-6) continue; } } } // Angle is not zero, so it is the value between -PI and PI returned // by atan2. We want to allow a full rotation to (almost) 2*PI // so readjust the angle value in the range 0 to positive or // negative 2*PI depending on the direction. // The max angle is actually the least rotation if (direction == INCREASING) { if (angle < 0.) angle += 2*Math.PI; if (angle < bestAngle) bestAngle = angle; } else { // DECREASING if (angle > 0.) angle -= 2*Math.PI; if (angle > bestAngle) bestAngle = angle; } } return (bestAngle); } // direction is INCREASING or DECREASING void setBestEdgePair (int direction) { _bestEdgePair = null; for (int ix = 0; ix < _probeXEdgeSegments.size(); ++ix) { EdgeSegment edgeSegmentX = probeXEdgeSegment (ix); for (int iy = 0; iy < _probeYEdgeSegments.size(); ++iy) { EdgeSegment edgeSegmentY = probeYEdgeSegment (iy); if (edgeSegmentX.isVertex() && edgeSegmentY.isVertex()) // To simplify analysis, don't do vertex-vertex contacts continue; // First try the best contact for probe X Point2d probeXContactFavorX = new Point2d(), probeYContactFavorX = new Point2d(); double deltaThetaFavorX = maxDeltaThetaFavorProbe (edgeSegmentX, edgeSegmentY, probeXContactFavorX, probeYContactFavorX, PROBE_X, direction); // Now try the best contact for probe Y Point2d probeXContactFavorY = new Point2d(), probeYContactFavorY = new Point2d(); double deltaThetaFavorY = maxDeltaThetaFavorProbe (edgeSegmentX, edgeSegmentY, probeXContactFavorY, probeYContactFavorY, PROBE_Y, direction); if (deltaThetaFavorX == 0. && deltaThetaFavorY == 0.) // don't add an edge pair if can't turn continue; // Add the edge pair with the better deltaTheta based // on the direction we want to turn. EdgePair edgePair; if (direction == INCREASING) { if (deltaThetaFavorX > deltaThetaFavorY) edgePair = new EdgePair (edgeSegmentX, edgeSegmentY, probeXContactFavorX, probeYContactFavorX, deltaThetaFavorX); else edgePair = new EdgePair (edgeSegmentX, edgeSegmentY, probeXContactFavorY, probeYContactFavorY, deltaThetaFavorY); if (_bestEdgePair == null || edgePair.deltaTheta() > _bestEdgePair.deltaTheta()) _bestEdgePair = edgePair; } else { // DECREASING if (deltaThetaFavorX < deltaThetaFavorY) edgePair = new EdgePair (edgeSegmentX, edgeSegmentY, probeXContactFavorX, probeYContactFavorX, deltaThetaFavorX); else edgePair = new EdgePair (edgeSegmentX, edgeSegmentY, probeXContactFavorY, probeYContactFavorY, deltaThetaFavorY); if (_bestEdgePair == null || edgePair.deltaTheta() < _bestEdgePair.deltaTheta()) _bestEdgePair = edgePair; } } } } // favorProbe is PROBE_X or PROBE_Y. // direction is INCREASING or DECREASING // For the two edge segments, choose which probe to // place in the preferred position based on favorProbe. // If there is no valid positioning, return 0. Otherwise, // return the deltaTheta possible and set probeXContact // and probeYContact to the contact positions. double maxDeltaThetaFavorProbe (EdgeSegment edgeSegmentX, EdgeSegment edgeSegmentY, Point2d probeXContact, Point2d probeYContact, int favorProbe, int direction) { if (favorProbe == PROBE_X) { if (direction == INCREASING) probeXContact.set (edgeSegmentX.pointB()); else probeXContact.set (edgeSegmentX.pointA()); Point2d tempY = edgeSegmentY.bestContact (direction, edgeSegmentX, probeXContact, _surface.frictionAngle()); if (tempY == null) // no contact at all so can't add a pair return (0.); probeYContact.set (tempY); } else { // PROBE_Y if (direction == INCREASING) probeYContact.set (edgeSegmentY.pointB()); else probeYContact.set (edgeSegmentY.pointA()); Point2d tempX = edgeSegmentX.bestContact (direction, edgeSegmentY, probeYContact, _surface.frictionAngle()); if (tempX == null) // no contact at all so can't add a pair return (0.); probeXContact.set (tempX); } // We have positions for probeX and probeY based on // favoring one or the other. Now determine which probe // can rotate the most double deltaThetaProbeX = maxProbeDeltaTheta (PROBE_X, direction, probeXContact, edgeSegmentX, probeYContact); double deltaThetaProbeY = maxProbeDeltaTheta (PROBE_Y, direction, probeYContact, edgeSegmentY, probeXContact); // The maximum turn is the worst of these if (direction == INCREASING) return ((deltaThetaProbeX < deltaThetaProbeY) ? deltaThetaProbeX : deltaThetaProbeY); else return ((deltaThetaProbeX > deltaThetaProbeY) ? deltaThetaProbeX : deltaThetaProbeY); } } class GripSequence { Surface _surface; public Surface surface() {return _surface;} // remember rotation Matrix3d _rotation; public Matrix3d rotation() {return _rotation;} // direction is Slice.INCREASING or Slice.DECREASING int _direction; public int direction() {return _direction;} Vector _slices = new Vector(); public Vector slices() {return _slices;} public Slice slice (int i) {return (Slice)_slices.elementAt (i);} // maxDeltaTheta will be > 0 if direction is Slice.INCREASING // otherwise < 0 double _maxDeltaTheta; public double maxDeltaTheta() {return _maxDeltaTheta;} // array with the Z for each slice level double[] _sliceZ; // minimum difference between vertex Z's to consider it a slice double _minZDifference; // Start with surface in its present rotation // Compute the grips in the given direction which is Slice.INCREASING // or Slice.DECREASING, adding them in order to slices. // Slices are added until a maximum rotation is found or > 2*PI GripSequence (Surface surface, int direction) { _surface = surface; // Save the rotation of the workpiece _rotation = new Matrix3d (surface.rotation()); _direction = direction; // Choose an epsilon based on the size of the part _minZDifference = .05 * surface.maxDiameter(); ///////////////////// // First rotate the workpiece and find the ordered list of Z values Vector rotatedPoints = new Vector(); for (int i = 0; i < _surface.points().size(); ++i) { Point3d rotatedPoint = new Point3d(); Slice.rotateTuple3d (rotatedPoint, _rotation, _surface.point (i)); rotatedPoints.addElement (rotatedPoint); } Object[] pointsArray = MyArrays.toArray (rotatedPoints); MyArrays.sort (pointsArray, new Tuple3dZComparator ()); // First count how many slices there will be so we can // declare an array int sliceZCount = 0; double startZ = ((Point3d)pointsArray[0]).z; for (int i = 1; i < pointsArray.length; ++i) { double endZ = ((Point3d)pointsArray[i]).z; if ((endZ - startZ) > _minZDifference) { ++sliceZCount; } // get ready for next segment startZ = endZ; } // Now actually compute the slice Z's _sliceZ = new double[sliceZCount]; int zIndex = 0; startZ = ((Point3d)pointsArray[0]).z; for (int i = 1; i < pointsArray.length; ++i) { double endZ = ((Point3d)pointsArray[i]).z; if ((endZ - startZ) > _minZDifference) { // add a slice at the midpoint between Z levels _sliceZ[zIndex++] = (startZ + endZ) / 2.; } // get ready for next segment startZ = endZ; } ////////////////// // Now we know the Z levels. Go around in the _direction // adding the Slice which has the best delta theta. double theta = 0.; while (true) { Slice bestSlice = bestSlice (theta); if (bestSlice == null) // We know if the deltaTheta would be zero, this is null break; _slices.addElement (bestSlice); theta += bestSlice.deltaTheta(); if (_direction == Slice.INCREASING) { if (theta >= 2*Math.PI) break; } else { if (theta <= -2.*Math.PI) break; } } _maxDeltaTheta = theta; } // Return the best slice given the starting theta // or null if there is none. Slice bestSlice (double theta) { Slice bestSlice = null; for (int zIndex = 0; zIndex < _sliceZ.length; ++zIndex) { Slice slice = new Slice (_surface, _rotation, theta, _sliceZ[zIndex], _direction); if (Math.abs (slice.deltaTheta()) < 1e-6) // too small to consider continue; if (_direction == Slice.INCREASING) { if (bestSlice == null || slice.deltaTheta() > bestSlice.deltaTheta()) bestSlice = slice; } else { //DECREASING if (bestSlice == null || slice.deltaTheta() < bestSlice.deltaTheta()) bestSlice = slice; } } return bestSlice; } } class Tuple2dXComparator implements MyComparator { public int compare (Object o1, Object o2) { double x1 = ((Tuple2d)o1).x; double x2 = ((Tuple2d)o2).x; if (x1 < x2) return -1; else if (x1 > x2) return 1; else return 0; } } class Tuple2dYComparator implements MyComparator { public int compare (Object o1, Object o2) { double y1 = ((Tuple2d)o1).y; double y2 = ((Tuple2d)o2).y; if (y1 < y2) return -1; else if (y1 > y2) return 1; else return 0; } } class Tuple3dZComparator implements MyComparator { public int compare (Object o1, Object o2) { double z1 = ((Tuple3d)o1).z; double z2 = ((Tuple3d)o2).z; if (z1 < z2) return -1; else if (z1 > z2) return 1; else return 0; } } class Script { Surface _workpiece, _probeX, _probeY; GripSequence _gripSequence; int _step = 0; double _pitch; double _maxPitch = Math.PI; double _thetaIncrement = .2; double _pitchIncrement = .2; double _linearIncrement; Point3d _home; int _sliceIndex; Slice _slice; EdgePair _edgePair; Point3d _workpieceOrigin = new Point3d(); Point3d _workpieceGoal = new Point3d(); Vector3d _workpieceGoalV = new Vector3d(); // probesOrigin is a virtual point containing the X of probeX // and the Y of probeY. Point3d _probesOrigin = new Point3d(); Point3d _probesGoal = new Point3d(); Point3d _probesSubgoal = new Point3d(); Vector3d _probesGoalV = new Vector3d(); // Used for when the probe is touching a vertex Point3d _probeXVertexOffset; Point3d _probeYVertexOffset; double _theta, _thetaGoal, _thetaV; int _endWaitCount; Script (Surface workpiece, Surface probeX, Surface probeY, GripSequence gripSequence) { _workpiece = workpiece; _probeX = probeX; _probeY = probeY; _gripSequence = gripSequence; _linearIncrement = _workpiece.maxDiameter() / 5.; _home = new Point3d (-_workpiece.maxDiameter(), -_workpiece.maxDiameter(), 0.); _probeXVertexOffset = new Point3d (0., -.05 * _workpiece.maxDiameter(), 0.); _probeYVertexOffset = new Point3d (-.05 * _workpiece.maxDiameter(), 0., 0.); } // Same as Vector.normalize but if v is zero, sets it // arbitrarily to (1, 0, 0) static void normalize (Vector3d v) { if (v.length() == 0.) v.set (1., 0., 0.); else v.normalize(); } void update () { while (true) { switch (_step) { case 0: { // initialize _probesOrigin.set (_home); _probeX.origin().set (_probesOrigin.x, 0., 0.); _probeY.origin().set (0., _probesOrigin.y, 0.); _workpieceOrigin.set (_home); _workpiece.origin().set (_workpieceOrigin); _theta = 0.; _workpiece.setRotation (_gripSequence.rotation(), 0., 0.); if (_gripSequence.slices().size() == 0) // No grips were found return; _sliceIndex = 0; ++_step; break; } case 1: { // We check the bounds on _sliceIndex at the end of the loop // Prepare objects used in all cases for this sliceIndex // probesGoal is a virtual point. It has the x of probeX // and the Y of probeY. _slice = _gripSequence.slice(_sliceIndex); _edgePair = _slice.bestEdgePair(); _probesGoal.set (_edgePair.probeXContact().x - _edgePair.probeYContact().x, _edgePair.probeYContact().y - _edgePair.probeXContact().y, 0.); // Each probe is going to start out at _home, so preposition // the workpiece where it will catch the probes. _workpieceGoal.set (_probesGoal.x, 0., 0.); // Include the location in z of the probe contact Point3d temp3d = new Point3d (_edgePair.probeXContact().x, _edgePair.probeXContact().y, _slice.z()); _workpieceGoal.sub (temp3d); // _workpieceGoal is now where it will be when finally positioned. // We want it to be _workpieceGoal - (_probesGoal - _home) _workpieceGoal.sub (_probesGoal); _workpieceGoal.add (_home); // Get a normalized vector in the direction we want to go _workpieceGoalV.set (_workpieceGoal); _workpieceGoalV.sub (_workpieceOrigin); normalize (_workpieceGoalV); ++_step; break; } case 2: { // Positioning workpiece origin Vector3d originDifference = new Vector3d (_workpieceGoal); originDifference.sub (_workpieceOrigin); if (originDifference.dot (_workpieceGoalV) > 0.) { // We have not passed the goal yet _workpiece.origin().set (_workpieceOrigin); _workpieceOrigin.scaleAdd (_linearIncrement, _workpieceGoalV, _workpieceOrigin); return; } // We have passed the goal point so set to the goal point _workpieceOrigin.set (_workpieceGoal); _workpiece.origin().set (_workpieceOrigin); // We next move the probes in two steps, first X then Y. _probesSubgoal.set (_probesGoal.x, _probesOrigin.y, 0.); _probesGoalV.set (_probesSubgoal); _probesGoalV.sub (_probesOrigin); normalize (_probesGoalV); // We will be incrementally moving the workpiece, so find out // where it should eventually be double deltaX = _probesSubgoal.x - _probesOrigin.x; _workpieceGoal.set (_workpieceOrigin.x + deltaX, _workpieceOrigin.y, _workpieceOrigin.z); ++_step; break; } case 3: { // Positioning probes origin X if (!prepositionHelper ()) // not done; return; _probesSubgoal.set (_probesGoal); _probesGoalV.set (_probesSubgoal); _probesGoalV.sub (_probesOrigin); normalize (_probesGoalV); // We will be incrementally moving the workpiece, so find out // where it should eventually be double deltaY = _probesSubgoal.y - _probesOrigin.y; _workpieceGoal.set (_workpieceOrigin.x, _workpieceOrigin.y + deltaY, _workpieceOrigin.z); ++ _step; break; } case 4: { // Positioning probes origin Y if (!prepositionHelper ()) // not done; return; // We have passed the goal point so set to the goal point _probesOrigin.set (_probesGoal); _probeX.origin().set (_probesOrigin.x, 0., 0.); _probeY.origin().set (0., _probesOrigin.y, 0.); _thetaGoal = _slice.theta() + _slice.deltaTheta(); // This is a normalized value in the direction we want to go _thetaV = (_gripSequence.direction() == Slice.INCREASING) ? 1. : -1.; ++ _step; break; } case 5: { // Rotating theta double thetaDifference = _thetaGoal - _theta; if (thetaDifference * _thetaV > 0.) { // We have not passed the goal yet positionProbes (_theta, _slice); _theta += (_thetaIncrement * _thetaV); return; } // We have passed the goal point so set to the goal point positionProbes (_thetaGoal, _slice); // We next release the probes in two steps, first X then Y. _probesGoal.set (_home); _probesSubgoal.set (_probesGoal.x, _probesOrigin.y, 0.); _probesGoalV.set (_probesSubgoal); _probesGoalV.sub (_probesOrigin); normalize (_probesGoalV); // We will be incrementally moving the workpiece, so find out // where it should eventually be double deltaX = _probesSubgoal.x - _probesOrigin.x; _workpieceGoal.set (_workpieceOrigin.x + deltaX, _workpieceOrigin.y, _workpieceOrigin.z); ++ _step; break; } case 6: { // Releasing probes origin X if (!prepositionHelper ()) // not done; return; _probesSubgoal.set (_probesGoal); _probesGoalV.set (_probesSubgoal); _probesGoalV.sub (_probesOrigin); normalize (_probesGoalV); // We will be incrementally moving the workpiece, so find out // where it should eventually be double deltaY = _probesSubgoal.y - _probesOrigin.y; _workpieceGoal.set (_workpieceOrigin.x, _workpieceOrigin.y + deltaY, _workpieceOrigin.z); ++ _step; break; } case 7: { // Releasing probes origin Y if (!prepositionHelper ()) // not done; return; // We have passed the goal point so set to the goal point _probesOrigin.set (_probesGoal); _probeX.origin().set (_probesOrigin.x, 0., 0.); _probeY.origin().set (0., _probesOrigin.y, 0.); // Go to the next slice ++ _sliceIndex; if (_sliceIndex < _gripSequence.slices().size()) { // loop back _step = 1; break; } // Did all slices, so go to waiting ++_step; _endWaitCount = 0; break; } case 8: // waiting at the end ++_endWaitCount; // assume about 1/10 second for each update if (_endWaitCount <= 10) // keep waiting return; _step = 0; break; case 9: { // // Rotating pitch // if (_pitch <= _maxPitch) { // _workpiece.setRotation (_pitch, _maxYaw); // _pitch += _pitchIncrement; // break; // } // _yaw = _minYaw; // _step = 0; // break; } default: _step = 0; return; } } } // Return true if we are done and should move on to next step // Help moving the probes and the workpiece along with it boolean prepositionHelper () { Vector3d originDifference = new Vector3d (_probesSubgoal); originDifference.sub (_probesOrigin); if (originDifference.dot (_probesGoalV) > 0.) { // We have not passed the goal yet _probeX.origin().set (_probesOrigin.x, 0., 0.); _probeY.origin().set (0., _probesOrigin.y, 0.); if (_edgePair.probeXEdgeSegment().isVertex()) _probeX.origin().add (_probeXVertexOffset); if (_edgePair.probeYEdgeSegment().isVertex()) _probeY.origin().add (_probeYVertexOffset); _workpiece.origin().set (_workpieceOrigin); _probesOrigin.scaleAdd (_linearIncrement, _probesGoalV, _probesOrigin); _workpieceOrigin.scaleAdd (_linearIncrement, _probesGoalV, _workpieceOrigin); return false; } // We have passed the goal point so set to the goal point _probeX.origin().set (_probesSubgoal.x, 0., 0.); _probeY.origin().set (0., _probesSubgoal.y, 0.); if (_edgePair.probeXEdgeSegment().isVertex()) _probeX.origin().add (_probeXVertexOffset); if (_edgePair.probeYEdgeSegment().isVertex()) _probeY.origin().add (_probeYVertexOffset); // _workpieceOrigin.set (_workpieceGoal); // _workpiece.origin().set (_workpieceOrigin); return true; } // This rotates _workpiece by the gripSequence.rotation, then // by theta, then looks at the slice and positions the probes // and the workpiece. // This first sets _workpieceOrigin, then _workpiece.origin(). // This first sets _probesOrigin, then the origins of the probes. void positionProbes (double theta, Slice slice) { _workpiece.setRotation (_gripSequence.rotation(), 0., theta); // Get modified probe contacts based in the difference theta has // moved from where the contacts were originally defined. EdgePair edgePair = slice.bestEdgePair(); // Make the contacts include the correct Z value Point3d probeXContact = new Point3d (edgePair.probeXContact().x, edgePair.probeXContact().y, slice.z()); Point3d probeYContact = new Point3d (edgePair.probeYContact().x, edgePair.probeYContact().y, slice.z()); Matrix3d rDelta = new Matrix3d(); rDelta.rotZ (_theta - slice.theta()); Slice.rotateTuple3d (probeXContact, rDelta, probeXContact); Slice.rotateTuple3d (probeYContact, rDelta, probeYContact); _probesOrigin.set (probeXContact.x - probeYContact.x, probeYContact.y - probeXContact.y, 0.); _probeX.origin().set (_probesOrigin.x, 0., 0.); _probeY.origin().set (0., _probesOrigin.y, 0.); _workpieceOrigin.set (_probeX.origin()); _workpieceOrigin.sub (probeXContact); _workpiece.origin().set (_workpieceOrigin); if (_edgePair.probeXEdgeSegment().isVertex()) _probeX.origin().add (_probeXVertexOffset); if (_edgePair.probeYEdgeSegment().isVertex()) _probeY.origin().add (_probeYVertexOffset); } } class ProbesDrawPanel extends Panel implements MouseListener, MouseMotionListener { Scene _scene = new Scene(); Surface _workpiece; // These are the point contacts in the workpiece coordinates Point3d _workpieceContactX = new Point3d(); Point3d _workpieceContactY = new Point3d(); // Move in the X direction ProbeSurface _probeX = new ProbeSurface (); // Move in the Y direction ProbeSurface _probeY = new ProbeSurface (); Timer _timer; // Initially, _imageSize is zero so we have to allocate Dimension _imageSize = new Dimension (0, 0); Image _bufferImage; Graphics _bufferGraphics; static int _mouseX, _mouseY; GripSequence _gripSequence; Script _script; // If stlFilename is null, default workpiece is used. public ProbesDrawPanel (String stlFilename) { setBackground(Color.white); addMouseMotionListener(this); addMouseListener(this); // Set initial camera rotation Matrix3d rx = new Matrix3d(); Matrix3d rz = new Matrix3d(); rx.rotX (-.11); rz.rotZ (-.02); _scene.cameraRotation().mul (rx, rz); // For now, ignore stlFilename setWorkpiece (STLFiles.cube, Slice.INCREASING); _timer = new Timer (); _timer.setDelay (150L); // This creates an anonymous inner class which extends TimerListener _timer.addTimerListener (new TimerListener () { public void onTime (java.awt.event.ActionEvent evt) { timerOnTime (evt); } } ); } // direction is Slice.INCREASING or Slice.DECREASING public void setWorkpiece (String file, int direction) { _scene.surfaces().removeAllElements(); _workpiece = new STLSurface (file, true); _scene.surfaces().addElement (_workpiece); _scene._zoom = 350. / (_workpiece.maxDiameter() * 3.); // _probeY already extends in the positive X direction _probeY.origin().set (-_workpiece.maxDiameter(), 0., 0.); _scene.surfaces().addElement (_probeY); // Make _probeX extend in the positive Y direction _probeX.rotation().rotZ (Math.PI/2.); _probeX.origin().set (0, -_workpiece.maxDiameter(), 0.); _scene.surfaces().addElement (_probeX); _gripSequence = new GripSequence (_workpiece, direction); _script = new Script (_workpiece, _probeX, _probeY, _gripSequence); _script.update(); repaint(); } void timerOnTime (ActionEvent event) { if (_script != null) { _script.update(); repaint(); } } public void mouseDragged(MouseEvent event) { event.consume(); _mouseX = event.getX(); _mouseY = event.getY(); // getWidth() / 2 is the center of the window // Limit the range of X rotation to no look from underneath Matrix3d rx = new Matrix3d(); // Use getSize instead of getHeight and getWidth because they are since 1.2 Dimension size = getSize(); rx.rotX (event.getY() * (Math.PI/2) / size.height - Math.PI/2); Matrix3d rz = new Matrix3d(); rz.rotZ ((event.getX() - (size.width / 2)) / 100.); _scene.cameraRotation().mul (rx, rz); repaint(); } public void mouseMoved(MouseEvent event) { } public void mousePressed(MouseEvent event) { } public void mouseReleased(MouseEvent event) { } public void mouseEntered(MouseEvent event) { } public void mouseExited(MouseEvent event) { } public void mouseClicked(MouseEvent event) { } // This is called by repaint public void update (Graphics g) { paint (g); } public void paint(Graphics g) { Dimension size = getSize(); // Initially, _imageSize is zero so this will be called the first time if (!size.equals(_imageSize)) { // Replace the old image with a new one of the new size. _bufferImage = createImage (size.width, size.height); _bufferGraphics = _bufferImage.getGraphics(); // keep a copy _imageSize = new Dimension (size); } _bufferGraphics.setColor (this.getBackground()); _bufferGraphics.fillRect (0, 0, size.width, size.height); int originX = size.width / 2; int originY = size.height / 2; // translate to the origin _bufferGraphics.translate (originX, originY); _scene.drawSurfaces (_bufferGraphics); // double theta = _mouseX / 100.; // Slice slice = new Slice // (_workpiece, _workpiece.rotation(), 0., 0., Slice.INCREASING); // _scene.drawEdges (_bufferGraphics, slice, _workpiece); // translate back _bufferGraphics.translate (-originX, -originY); // Copy the buffer to window g.drawImage (_bufferImage, 0, 0, this); } } class ProbesControls extends Panel implements ItemListener { ProbesDrawPanel _target; Choice _files; Choice _directions; public ProbesControls(ProbesDrawPanel target) { _target = target; setLayout(new FlowLayout()); setBackground(Color.lightGray); _files = new Choice(); _files.addItemListener(this); _files.addItem("cube"); _files.addItem("hexagon"); _files.addItem("trapezoid"); _files.setBackground(Color.lightGray); add(_files); _directions = new Choice(); _directions.addItemListener(this); _directions.addItem("increasing"); _directions.addItem("decreasing"); _directions.setBackground(Color.lightGray); add(_directions); } public void itemStateChanged(ItemEvent e) { if (e.getSource() == _files || e.getSource() == _directions) { String surface = _files.getSelectedItem(); String file; if (surface.equals ("hexagon")) file = STLFiles.hexagon; else if (surface.equals ("trapezoid")) file = STLFiles.trapezoid; else file = STLFiles.cube; String directionString = _directions.getSelectedItem(); int direction = (directionString.equals("increasing")) ? Slice.INCREASING : Slice.DECREASING; _target.setWorkpiece (file, direction); } } } class ProbesApplication extends Frame implements WindowListener{ public ProbesApplication () { super ("Probes"); addWindowListener (this); } public void windowClosing (WindowEvent event) { System.exit(0); } // other WindowListener methods public void windowActivated (WindowEvent event) {} public void windowClosed (WindowEvent event) {} public void windowDeactivated (WindowEvent event) {} public void windowDeiconified (WindowEvent event) {} public void windowIconified (WindowEvent event) {} public void windowOpened (WindowEvent event) {} } public class Probes extends Applet{ ProbesDrawPanel _panel; ProbesControls _controls; public String _stlFilename; /* This is called if invoked from the command line */ public static void main(String args[]) { ProbesApplication f = new ProbesApplication(); Probes probes = new Probes(); if (args.length >= 1) probes._stlFilename = args[0]; probes.init(); probes.start(); f.add("Center", probes); f.setSize(400, 400); f.show(); } /* This is called if invoked from an applet */ public void init() { setLayout(new BorderLayout()); _panel = new ProbesDrawPanel(_stlFilename); _controls = new ProbesControls (_panel); add("Center", _panel); add("South", _controls); } public void destroy() { remove(_panel); remove(_controls); } public String getAppletInfo() { return "Probes."; } } class STLFiles { public static final String cube = "solid Part1 " + " facet normal -1.000000e+000 0.000000e+000 0.000000e+000 " + " outer loop " + " vertex 5.000002e-004 1.005000e-001 3.000005e-004 " + " vertex 5.000002e-004 5.000002e-004 3.000005e-004 " + " vertex 5.000002e-004 1.005000e-001 6.030000e-002 " + " endloop " + " endfacet " + " facet normal -1.000000e+000 0.000000e+000 0.000000e+000 " + " outer loop " + " vertex 5.000002e-004 1.005000e-001 6.030000e-002 " + " vertex 5.000002e-004 5.000002e-004 3.000005e-004 " + " vertex 5.000002e-004 5.000002e-004 6.030000e-002 " + " endloop " + " endfacet " + " facet normal 0.000000e+000 -1.000000e+000 0.000000e+000 " + " outer loop " + " vertex 5.000002e-004 5.000002e-004 3.000005e-004 " + " vertex 1.005000e-001 5.000002e-004 3.000005e-004 " + " vertex 5.000002e-004 5.000002e-004 6.030000e-002 " + " endloop " + " endfacet " + " facet normal 0.000000e+000 -1.000000e+000 0.000000e+000 " + " outer loop " + " vertex 5.000002e-004 5.000002e-004 6.030000e-002 " + " vertex 1.005000e-001 5.000002e-004 3.000005e-004 " + " vertex 1.005000e-001 5.000002e-004 6.030000e-002 " + " endloop " + " endfacet " + " facet normal 1.000000e+000 0.000000e+000 0.000000e+000 " + " outer loop " + " vertex 1.005000e-001 5.000002e-004 3.000005e-004 " + " vertex 1.005000e-001 1.005000e-001 3.000005e-004 " + " vertex 1.005000e-001 5.000002e-004 6.030000e-002 " + " endloop " + " endfacet " + " facet normal 1.000000e+000 0.000000e+000 0.000000e+000 " + " outer loop " + " vertex 1.005000e-001 5.000002e-004 6.030000e-002 " + " vertex 1.005000e-001 1.005000e-001 3.000005e-004 " + " vertex 1.005000e-001 1.005000e-001 6.030000e-002 " + " endloop " + " endfacet " + " facet normal 0.000000e+000 1.000000e+000 0.000000e+000 " + " outer loop " + " vertex 1.005000e-001 1.005000e-001 3.000005e-004 " + " vertex 5.000002e-004 1.005000e-001 3.000005e-004 " + " vertex 1.005000e-001 1.005000e-001 6.030000e-002 " + " endloop " + " endfacet " + " facet normal 0.000000e+000 1.000000e+000 0.000000e+000 " + " outer loop " + " vertex 1.005000e-001 1.005000e-001 6.030000e-002 " + " vertex 5.000002e-004 1.005000e-001 3.000005e-004 " + " vertex 5.000002e-004 1.005000e-001 6.030000e-002 " + " endloop " + " endfacet " + " facet normal 0.000000e+000 0.000000e+000 1.000000e+000 " + " outer loop " + " vertex 5.000002e-004 5.000002e-004 6.030000e-002 " + " vertex 1.005000e-001 5.000002e-004 6.030000e-002 " + " vertex 5.000002e-004 1.005000e-001 6.030000e-002 " + " endloop " + " endfacet " + " facet normal 0.000000e+000 0.000000e+000 1.000000e+000 " + " outer loop " + " vertex 5.000002e-004 1.005000e-001 6.030000e-002 " + " vertex 1.005000e-001 5.000002e-004 6.030000e-002 " + " vertex 1.005000e-001 1.005000e-001 6.030000e-002 " + " endloop " + " endfacet " + " facet normal 0.000000e+000 0.000000e+000 -1.000000e+000 " + " outer loop " + " vertex 1.005000e-001 5.000002e-004 3.000005e-004 " + " vertex 5.000002e-004 5.000002e-004 3.000005e-004 " + " vertex 1.005000e-001 1.005000e-001 3.000005e-004 " + " endloop " + " endfacet " + " facet normal 0.000000e+000 0.000000e+000 -1.000000e+000 " + " outer loop " + " vertex 1.005000e-001 1.005000e-001 3.000005e-004 " + " vertex 5.000002e-004 5.000002e-004 3.000005e-004 " + " vertex 5.000002e-004 1.005000e-001 3.000005e-004 " + " endloop " + " endfacet " + "endsolid "; public static final String hexagon = "solid hourglass " + " facet normal -5.144957e-001 -8.574929e-001 0.000000e+000 " + " outer loop " + " vertex 5.000002e-004 3.060000e-002 1.030000e-002 " + " vertex 5.050000e-002 6.000010e-004 1.030000e-002 " + " vertex 5.000002e-004 3.060000e-002 5.030000e-002 " + " endloop " + " endfacet " + " facet normal -5.144957e-001 -8.574929e-001 0.000000e+000 " + " outer loop " + " vertex 5.000002e-004 3.060000e-002 5.030000e-002 " + " vertex 5.050000e-002 6.000010e-004 1.030000e-002 " + " vertex 5.050000e-002 6.000010e-004 5.030000e-002 " + " endloop " + " endfacet " + " facet normal 5.144957e-001 -8.574929e-001 0.000000e+000 " + " outer loop " + " vertex 5.050000e-002 6.000010e-004 1.030000e-002 " + " vertex 1.005000e-001 3.060000e-002 1.030000e-002 " + " vertex 5.050000e-002 6.000010e-004 5.030000e-002 " + " endloop " + " endfacet " + " facet normal 5.144957e-001 -8.574929e-001 0.000000e+000 " + " outer loop " + " vertex 5.050000e-002 6.000010e-004 5.030000e-002 " + " vertex 1.005000e-001 3.060000e-002 1.030000e-002 " + " vertex 1.005000e-001 3.060000e-002 5.030000e-002 " + " endloop " + " endfacet " + " facet normal 1.000000e+000 0.000000e+000 0.000000e+000 " + " outer loop " + " vertex 1.005000e-001 3.060000e-002 1.030000e-002 " + " vertex 1.005000e-001 9.060000e-002 1.030000e-002 " + " vertex 1.005000e-001 3.060000e-002 5.030000e-002 " + " endloop " + " endfacet " + " facet normal 1.000000e+000 0.000000e+000 0.000000e+000 " + " outer loop " + " vertex 1.005000e-001 3.060000e-002 5.030000e-002 " + " vertex 1.005000e-001 9.060000e-002 1.030000e-002 " + " vertex 1.005000e-001 9.060000e-002 5.030000e-002 " + " endloop " + " endfacet " + " facet normal 5.144957e-001 8.574929e-001 0.000000e+000 " + " outer loop " + " vertex 1.005000e-001 9.060000e-002 1.030000e-002 " + " vertex 5.050000e-002 1.206000e-001 1.030000e-002 " + " vertex 1.005000e-001 9.060000e-002 5.030000e-002 " + " endloop " + " endfacet " + " facet normal 5.144957e-001 8.574929e-001 0.000000e+000 " + " outer loop " + " vertex 1.005000e-001 9.060000e-002 5.030000e-002 " + " vertex 5.050000e-002 1.206000e-001 1.030000e-002 " + " vertex 5.050000e-002 1.206000e-001 5.030000e-002 " + " endloop " + " endfacet " + " facet normal -5.144957e-001 8.574929e-001 0.000000e+000 " + " outer loop " + " vertex 5.050000e-002 1.206000e-001 1.030000e-002 " + " vertex 5.000002e-004 9.060000e-002 1.030000e-002 " + " vertex 5.050000e-002 1.206000e-001 5.030000e-002 " + " endloop " + " endfacet " + " facet normal -5.144957e-001 8.574929e-001 0.000000e+000 " + " outer loop " + " vertex 5.050000e-002 1.206000e-001 5.030000e-002 " + " vertex 5.000002e-004 9.060000e-002 1.030000e-002 " + " vertex 5.000002e-004 9.060000e-002 5.030000e-002 " + " endloop " + " endfacet " + " facet normal -1.000000e+000 0.000000e+000 0.000000e+000 " + " outer loop " + " vertex 5.000002e-004 9.060000e-002 1.030000e-002 " + " vertex 5.000002e-004 3.060000e-002 1.030000e-002 " + " vertex 5.000002e-004 9.060000e-002 5.030000e-002 " + " endloop " + " endfacet " + " facet normal -1.000000e+000 0.000000e+000 0.000000e+000 " + " outer loop " + " vertex 5.000002e-004 9.060000e-002 5.030000e-002 " + " vertex 5.000002e-004 3.060000e-002 1.030000e-002 " + " vertex 5.000002e-004 3.060000e-002 5.030000e-002 " + " endloop " + " endfacet " + " facet normal 0.000000e+000 0.000000e+000 1.000000e+000 " + " outer loop " + " vertex 5.050000e-002 1.089381e-001 6.030000e-002 " + " vertex 1.050000e-002 8.493809e-002 6.030000e-002 " + " vertex 9.050000e-002 8.493809e-002 6.030000e-002 " + " endloop " + " endfacet " + " facet normal 0.000000e+000 0.000000e+000 1.000000e+000 " + " outer loop " + " vertex 9.050000e-002 8.493809e-002 6.030000e-002 " + " vertex 1.050000e-002 8.493809e-002 6.030000e-002 " + " vertex 1.050000e-002 3.626190e-002 6.030000e-002 " + " endloop " + " endfacet " + " facet normal 0.000000e+000 0.000000e+000 1.000000e+000 " + " outer loop " + " vertex 9.050000e-002 8.493809e-002 6.030000e-002 " + " vertex 1.050000e-002 3.626190e-002 6.030000e-002 " + " vertex 9.050000e-002 3.626190e-002 6.030000e-002 " + " endloop " + " endfacet " + " facet normal 0.000000e+000 0.000000e+000 1.000000e+000 " + " outer loop " + " vertex 9.050000e-002 3.626190e-002 6.030000e-002 " + " vertex 1.050000e-002 3.626190e-002 6.030000e-002 " + " vertex 5.050000e-002 1.226190e-002 6.030000e-002 " + " endloop " + " endfacet " + " facet normal 0.000000e+000 0.000000e+000 -1.000000e+000 " + " outer loop " + " vertex 9.050000e-002 3.626190e-002 3.000005e-004 " + " vertex 5.050000e-002 1.226190e-002 3.000005e-004 " + " vertex 9.050000e-002 8.493809e-002 3.000005e-004 " + " endloop " + " endfacet " + " facet normal 0.000000e+000 0.000000e+000 -1.000000e+000 " + " outer loop " + " vertex 9.050000e-002 8.493809e-002 3.000005e-004 " + " vertex 5.050000e-002 1.226190e-002 3.000005e-004 " + " vertex 1.050000e-002 3.626190e-002 3.000005e-004 " + " endloop " + " endfacet " + " facet normal 0.000000e+000 0.000000e+000 -1.000000e+000 " + " outer loop " + " vertex 9.050000e-002 8.493809e-002 3.000005e-004 " + " vertex 1.050000e-002 3.626190e-002 3.000005e-004 " + " vertex 5.050000e-002 1.089381e-001 3.000005e-004 " + " endloop " + " endfacet " + " facet normal 0.000000e+000 0.000000e+000 -1.000000e+000 " + " outer loop " + " vertex 5.050000e-002 1.089381e-001 3.000005e-004 " + " vertex 1.050000e-002 3.626190e-002 3.000005e-004 " + " vertex 1.050000e-002 8.493809e-002 3.000005e-004 " + " endloop " + " endfacet " + " facet normal 7.071068e-001 0.000000e+000 7.071068e-001 " + " outer loop " + " vertex 9.050000e-002 3.626190e-002 6.030000e-002 " + " vertex 1.005000e-001 3.060000e-002 5.030000e-002 " + " vertex 9.050000e-002 8.493809e-002 6.030000e-002 " + " endloop " + " endfacet " + " facet normal 7.071068e-001 0.000000e+000 7.071068e-001 " + " outer loop " + " vertex 9.050000e-002 8.493809e-002 6.030000e-002 " + " vertex 1.005000e-001 3.060000e-002 5.030000e-002 " + " vertex 1.005000e-001 9.060000e-002 5.030000e-002 " + " endloop " + " endfacet " + " facet normal 3.638034e-001 6.063390e-001 7.071068e-001 " + " outer loop " + " vertex 9.050000e-002 8.493809e-002 6.030000e-002 " + " vertex 1.005000e-001 9.060000e-002 5.030000e-002 " + " vertex 5.050000e-002 1.089381e-001 6.030000e-002 " + " endloop " + " endfacet " + " facet normal 3.638034e-001 6.063390e-001 7.071068e-001 " + " outer loop " + " vertex 5.050000e-002 1.089381e-001 6.030000e-002 " + " vertex 1.005000e-001 9.060000e-002 5.030000e-002 " + " vertex 5.050000e-002 1.206000e-001 5.030000e-002 " + " endloop " + " endfacet " + " facet normal 3.638034e-001 -6.063390e-001 7.071068e-001 " + " outer loop " + " vertex 5.050000e-002 6.000010e-004 5.030000e-002 " + " vertex 1.005000e-001 3.060000e-002 5.030000e-002 " + " vertex 5.050000e-002 1.226190e-002 6.030000e-002 " + " endloop " + " endfacet " + " facet normal 3.638034e-001 -6.063390e-001 7.071068e-001 " + " outer loop " + " vertex 5.050000e-002 1.226190e-002 6.030000e-002 " + " vertex 1.005000e-001 3.060000e-002 5.030000e-002 " + " vertex 9.050000e-002 3.626190e-002 6.030000e-002 " + " endloop " + " endfacet " + " facet normal -3.638034e-001 6.063390e-001 7.071068e-001 " + " outer loop " + " vertex 5.050000e-002 1.206000e-001 5.030000e-002 " + " vertex 5.000002e-004 9.060000e-002 5.030000e-002 " + " vertex 5.050000e-002 1.089381e-001 6.030000e-002 " + " endloop " + " endfacet " + " facet normal -3.638034e-001 6.063390e-001 7.071068e-001 " + " outer loop " + " vertex 5.050000e-002 1.089381e-001 6.030000e-002 " + " vertex 5.000002e-004 9.060000e-002 5.030000e-002 " + " vertex 1.050000e-002 8.493809e-002 6.030000e-002 " + " endloop " + " endfacet " + " facet normal -3.638034e-001 -6.063390e-001 7.071068e-001 " + " outer loop " + " vertex 1.050000e-002 3.626190e-002 6.030000e-002 " + " vertex 5.000002e-004 3.060000e-002 5.030000e-002 " + " vertex 5.050000e-002 1.226190e-002 6.030000e-002 " + " endloop " + " endfacet " + " facet normal -3.638034e-001 -6.063390e-001 7.071068e-001 " + " outer loop " + " vertex 5.050000e-002 1.226190e-002 6.030000e-002 " + " vertex 5.000002e-004 3.060000e-002 5.030000e-002 " + " vertex 5.050000e-002 6.000010e-004 5.030000e-002 " + " endloop " + " endfacet " + " facet normal -7.071068e-001 0.000000e+000 7.071068e-001 " + " outer loop " + " vertex 5.000002e-004 9.060000e-002 5.030000e-002 " + " vertex 5.000002e-004 3.060000e-002 5.030000e-002 " + " vertex 1.050000e-002 8.493809e-002 6.030000e-002 " + " endloop " + " endfacet " + " facet normal -7.071068e-001 0.000000e+000 7.071068e-001 " + " outer loop " + " vertex 1.050000e-002 8.493809e-002 6.030000e-002 " + " vertex 5.000002e-004 3.060000e-002 5.030000e-002 " + " vertex 1.050000e-002 3.626190e-002 6.030000e-002 " + " endloop " + " endfacet " + " facet normal -7.071068e-001 0.000000e+000 -7.071068e-001 " + " outer loop " + " vertex 1.050000e-002 3.626190e-002 3.000005e-004 " + " vertex 5.000002e-004 3.060000e-002 1.030000e-002 " + " vertex 1.050000e-002 8.493809e-002 3.000005e-004 " + " endloop " + " endfacet " + " facet normal -7.071068e-001 0.000000e+000 -7.071068e-001 " + " outer loop " + " vertex 1.050000e-002 8.493809e-002 3.000005e-004 " + " vertex 5.000002e-004 3.060000e-002 1.030000e-002 " + " vertex 5.000002e-004 9.060000e-002 1.030000e-002 " + " endloop " + " endfacet " + " facet normal -3.638034e-001 6.063390e-001 -7.071068e-001 " + " outer loop " + " vertex 1.050000e-002 8.493809e-002 3.000005e-004 " + " vertex 5.000002e-004 9.060000e-002 1.030000e-002 " + " vertex 5.050000e-002 1.089381e-001 3.000005e-004 " + " endloop " + " endfacet " + " facet normal -3.638034e-001 6.063390e-001 -7.071068e-001 " + " outer loop " + " vertex 5.050000e-002 1.089381e-001 3.000005e-004 " + " vertex 5.000002e-004 9.060000e-002 1.030000e-002 " + " vertex 5.050000e-002 1.206000e-001 1.030000e-002 " + " endloop " + " endfacet " + " facet normal -3.638034e-001 -6.063390e-001 -7.071068e-001 " + " outer loop " + " vertex 5.050000e-002 6.000010e-004 1.030000e-002 " + " vertex 5.000002e-004 3.060000e-002 1.030000e-002 " + " vertex 5.050000e-002 1.226190e-002 3.000005e-004 " + " endloop " + " endfacet " + " facet normal -3.638034e-001 -6.063390e-001 -7.071068e-001 " + " outer loop " + " vertex 5.050000e-002 1.226190e-002 3.000005e-004 " + " vertex 5.000002e-004 3.060000e-002 1.030000e-002 " + " vertex 1.050000e-002 3.626190e-002 3.000005e-004 " + " endloop " + " endfacet " + " facet normal 3.638034e-001 6.063390e-001 -7.071068e-001 " + " outer loop " + " vertex 5.050000e-002 1.206000e-001 1.030000e-002 " + " vertex 1.005000e-001 9.060000e-002 1.030000e-002 " + " vertex 5.050000e-002 1.089381e-001 3.000005e-004 " + " endloop " + " endfacet " + " facet normal 3.638034e-001 6.063390e-001 -7.071068e-001 " + " outer loop " + " vertex 5.050000e-002 1.089381e-001 3.000005e-004 " + " vertex 1.005000e-001 9.060000e-002 1.030000e-002 " + " vertex 9.050000e-002 8.493809e-002 3.000005e-004 " + " endloop " + " endfacet " + " facet normal 3.638034e-001 -6.063390e-001 -7.071068e-001 " + " outer loop " + " vertex 9.050000e-002 3.626190e-002 3.000005e-004 " + " vertex 1.005000e-001 3.060000e-002 1.030000e-002 " + " vertex 5.050000e-002 1.226190e-002 3.000005e-004 " + " endloop " + " endfacet " + " facet normal 3.638034e-001 -6.063390e-001 -7.071068e-001 " + " outer loop " + " vertex 5.050000e-002 1.226190e-002 3.000005e-004 " + " vertex 1.005000e-001 3.060000e-002 1.030000e-002 " + " vertex 5.050000e-002 6.000010e-004 1.030000e-002 " + " endloop " + " endfacet " + " facet normal 7.071068e-001 0.000000e+000 -7.071068e-001 " + " outer loop " + " vertex 1.005000e-001 9.060000e-002 1.030000e-002 " + " vertex 1.005000e-001 3.060000e-002 1.030000e-002 " + " vertex 9.050000e-002 8.493809e-002 3.000005e-004 " + " endloop " + " endfacet " + " facet normal 7.071068e-001 0.000000e+000 -7.071068e-001 " + " outer loop " + " vertex 9.050000e-002 8.493809e-002 3.000005e-004 " + " vertex 1.005000e-001 3.060000e-002 1.030000e-002 " + " vertex 9.050000e-002 3.626190e-002 3.000005e-004 " + " endloop " + " endfacet " + "endsolid "; public static final String trapezoid = "solid trapezoid " + " facet normal -8.809944e-001 4.731266e-001 0.000000e+000 " + " outer loop " + " vertex 5.222210e-004 1.000000e-003 0.000000e+000 " + " vertex 5.222210e-004 1.000000e-003 9.999999e-002 " + " vertex 3.274444e-002 6.100000e-002 0.000000e+000 " + " endloop " + " endfacet " + " facet normal -8.809944e-001 4.731266e-001 0.000000e+000 " + " outer loop " + " vertex 3.274444e-002 6.100000e-002 0.000000e+000 " + " vertex 5.222210e-004 1.000000e-003 9.999999e-002 " + " vertex 3.274444e-002 6.100000e-002 9.999999e-002 " + " endloop " + " endfacet " + " facet normal 0.000000e+000 -1.000000e+000 0.000000e+000 " + " outer loop " + " vertex 1.094111e-001 1.000000e-003 0.000000e+000 " + " vertex 1.094111e-001 1.000000e-003 9.999999e-002 " + " vertex 5.222210e-004 1.000000e-003 0.000000e+000 " + " endloop " + " endfacet " + " facet normal 0.000000e+000 -1.000000e+000 0.000000e+000 " + " outer loop " + " vertex 5.222210e-004 1.000000e-003 0.000000e+000 " + " vertex 1.094111e-001 1.000000e-003 9.999999e-002 " + " vertex 5.222210e-004 1.000000e-003 9.999999e-002 " + " endloop " + " endfacet " + " facet normal 8.782682e-001 4.781683e-001 0.000000e+000 " + " outer loop " + " vertex 7.674444e-002 6.100000e-002 0.000000e+000 " + " vertex 7.674444e-002 6.100000e-002 9.999999e-002 " + " vertex 1.094111e-001 1.000000e-003 0.000000e+000 " + " endloop " + " endfacet " + " facet normal 8.782682e-001 4.781683e-001 0.000000e+000 " + " outer loop " + " vertex 1.094111e-001 1.000000e-003 0.000000e+000 " + " vertex 7.674444e-002 6.100000e-002 9.999999e-002 " + " vertex 1.094111e-001 1.000000e-003 9.999999e-002 " + " endloop " + " endfacet " + " facet normal 1.540060e-016 1.000000e+000 0.000000e+000 " + " outer loop " + " vertex 3.274444e-002 6.100000e-002 0.000000e+000 " + " vertex 3.274444e-002 6.100000e-002 9.999999e-002 " + " vertex 7.674444e-002 6.100000e-002 0.000000e+000 " + " endloop " + " endfacet " + " facet normal 1.540060e-016 1.000000e+000 0.000000e+000 " + " outer loop " + " vertex 7.674444e-002 6.100000e-002 0.000000e+000 " + " vertex 3.274444e-002 6.100000e-002 9.999999e-002 " + " vertex 7.674444e-002 6.100000e-002 9.999999e-002 " + " endloop " + " endfacet " + " facet normal 0.000000e+000 0.000000e+000 1.000000e+000 " + " outer loop " + " vertex 3.274444e-002 6.100000e-002 9.999999e-002 " + " vertex 5.222210e-004 1.000000e-003 9.999999e-002 " + " vertex 7.674444e-002 6.100000e-002 9.999999e-002 " + " endloop " + " endfacet " + " facet normal 0.000000e+000 0.000000e+000 1.000000e+000 " + " outer loop " + " vertex 7.674444e-002 6.100000e-002 9.999999e-002 " + " vertex 5.222210e-004 1.000000e-003 9.999999e-002 " + " vertex 1.094111e-001 1.000000e-003 9.999999e-002 " + " endloop " + " endfacet " + " facet normal 0.000000e+000 0.000000e+000 -1.000000e+000 " + " outer loop " + " vertex 1.094111e-001 1.000000e-003 0.000000e+000 " + " vertex 5.222210e-004 1.000000e-003 0.000000e+000 " + " vertex 7.674444e-002 6.100000e-002 0.000000e+000 " + " endloop " + " endfacet " + " facet normal 0.000000e+000 0.000000e+000 -1.000000e+000 " + " outer loop " + " vertex 7.674444e-002 6.100000e-002 0.000000e+000 " + " vertex 5.222210e-004 1.000000e-003 0.000000e+000 " + " vertex 3.274444e-002 6.100000e-002 0.000000e+000 " + " endloop " + " endfacet " + "endsolid "; } /** The Timer JavaBean is a nonvisual component that sends an ActionEvent * to the registered TimerListeners every "delay" property milliseconds. * It can either send that event only once, or it can cycle (according to * the "onceOnly" property). * * @version 1.01, Sep 02, 1998 */ class Timer extends Object { public static final String PROP_ONCE_ONLY = "onceOnly"; public static final String PROP_DELAY = "delay"; public static final long DEFAULT_DELAY = 1000; public static final boolean DEFAULT_ONLY_ONCE = false; /** Creates a new Timer */ public Timer () { delay = DEFAULT_DELAY; onceOnly = DEFAULT_ONLY_ONCE; start (); } public synchronized void start () { if (running) return; timerThread = new TimerThread (); running = true; timerThread.start (); } /** Getter method for the delay property. * @return Current delay value */ public long getDelay () { return delay; } /** Setter method for the delay property. * @param value New delay value */ public void setDelay (long value) { if (delay == value) return; long oldValue = delay; delay = value; } /** Getter method for the onceOnly property. * @return Current onceOnly value */ public boolean getOnceOnly () { return onceOnly; } /** Setter method for the onceOnly property. * @param value New onceOnly value */ public void setOnceOnly (boolean value) { if (onceOnly == value) return; onceOnly = value; } public void addTimerListener (TimerListener l) { if (listeners == null) listeners = new Vector(); listeners.addElement (l); } public void removeTimerListener (TimerListener l) { if (listeners == null) return; listeners.removeElement (l); } private void fireTimerEvent () { if (listeners == null) return; Vector l; synchronized (this) { l = (Vector)listeners.clone (); } for (Enumeration e = l.elements (); e.hasMoreElements ();) { TimerListener tl = (TimerListener) e.nextElement (); tl.onTime (new ActionEvent (this, ActionEvent.ACTION_PERFORMED, "onTime")); } } class TimerThread extends Thread { public void run() { while (true) { try { sleep(delay); } catch (InterruptedException e) {} fireTimerEvent(); if (onceOnly) break; } running = false; } } transient private TimerThread timerThread; /** The timer listeners */ transient private Vector listeners; /** The flag indicating whether the timer is running */ private boolean running; /** If true, the timer stops after firing the first onTime, if false * it keeps ticking until stopped */ private boolean onceOnly; /** Delay in milliseconds */ private long delay; } /** The TimerListener interface must be implemented by * a class that wants to be notified about time events. * * @version 1.00, Jul 20, 1998 */ interface TimerListener extends java.util.EventListener { /** Called when a new timer event occurs */ public void onTime (java.awt.event.ActionEvent event); }