import java.awt.Color; import robotrace.Vector; import static java.lang.Math.*; import static javax.media.opengl.GL2.*; /** * Implementation of a race track that is made from Bezier segments. */ class RaceTrack extends BetterBase { /** * Half-width of the ellipse. */ protected static final double ELLIPSE_A = 10; /** * Half-height of the ellipse. */ protected static final double ELLIPSE_B = 14; /** * Number of segments for the race track. */ private final double SEGMENTS = 180; /** * Array with control points for the O-track. */ private final Vector[] controlPointsOTrack; /** * Array with control points for the L-track. */ private Vector[] controlPointsLTrack; /** * Array with control points for the C-track. */ private Vector[] controlPointsCTrack; /** * Array with control points for the custom track. */ private Vector[] controlPointsCustomTrack; private Vector[] selectedControlPoints; private final RobotRace race; /** * Debug option: set to true to show control points. */ private static final boolean drawControlPoints = true; /** * Constructs the race track, sets up display lists. */ public RaceTrack(RobotRace race) { this.race = race; // points are chosen such that the boundaries of a quarter lay // on a straight line (to get second-order continuity). // top #---#|--# this is the first control point of top-left, // left ^- - - - - - and the last of top-right // # top # // | right | (^ then continue anti-clockwise) // - | // # # <- - - begin here (top-right) // | - // | bottom | // # left right # // #--|#---# controlPointsOTrack = new Vector[] { // top-right new Vector( 15, 0, 1), new Vector( 15, 8, 1), new Vector( 8, 15, 1), // top-left new Vector( 0, 15, 1), new Vector( -8, 15, 1), new Vector(-15, 8, 1), // bottom-left new Vector(-15, 0, 1), new Vector(-15, -8, 1), new Vector( -8, -15, 1), // bottom-right new Vector( 0, -15, 1), new Vector( 8, -15, 1), new Vector( 15, -8, 1), }; } /** * Draws this track, based on the selected track number. */ public void draw(int trackNr) { // The test track is selected if (0 == trackNr) { drawTestTrack(); } else if (1 == trackNr) { // The O-track is selected drawTrack(controlPointsOTrack); } else if (2 == trackNr) { // The L-track is selected drawTrack(controlPointsLTrack); } else if (3 == trackNr) { // The C-track is selected drawTrack(controlPointsCTrack); } else if (4 == trackNr) { // The custom track is selected drawTrack(controlPointsOTrack); } } /** * Returns the position of the curve at 0 <= {@code t} <= 1. */ public Vector getPoint(double t) { return new Vector(ELLIPSE_A * cos(2 * PI * t), ELLIPSE_B * sin(2 * PI * t), 1); } /** * Returns the position of the curve at 0 <= {@code t} <= 1 and * the center of a lane at lane 1 <= laneNo <= (number of robots). */ public Vector getPointForLane(double t, double laneNo) { Vector p = getPoint(t); Vector lanes_len = new Vector(p.x(), p.y(), 0).normalized().scale(laneNo + .5); return p.add(lanes_len); } /** * Returns the tangent of the curve at 0 <= {@code t} <= 1. */ public Vector getTangent(double t) { /* * Given a vector (-Y/B^2, X/A^2, 0) where X and Y are the coordinates * of the point p on the ellipse. A is the HALFWIDTH of the ellipse and * B is the HALFHEIGHT of the ellipse. * * Vector (X/A^2, Y/B^2, 0) is the normal vector through point p * and the center of the ellipse. Because the X and Y coordinates * are divided by the width and height of the ellipse, everything is * "normalized" to a circle. Hence a line through the origin and a point * p describes a normal vector for a point p on the ellipse. * * Since the dot product of the latter vector and the first (tangent) * vector results in zero, we can say the normal vector is perpendicular * to the tangent vector. And because of that, the first vector * describes the tangent vector of point p. */ Vector p = getPoint(t); return new Vector(-p.y() / (ELLIPSE_A * ELLIPSE_A), p.x() / (ELLIPSE_B * ELLIPSE_B), 0).normalized(); } private void drawTestTrack() { /* A track segment looks like: * B----------------------------D "outside top" * / : /| * / G- - - - - - - - - - - - -/--H "outside bottom" * / / * A----------------------------C "inside top" * | | * E----------------------------F "inside bottom" * ^-- t = t0 ^-- t = t0 + 1 * Assume point A the inner point of the race track. Draw quads from * EF (starting point) to AC, BD, GH. */ // previous points Vector point_A = null, point_B = null, point_E = null, point_G = null; for (double i = 0; i <= SEGMENTS; ++i) { double t = i / SEGMENTS; Vector point_C = getPoint(t); // the outer side is located on the number of lanes (4) shifted from // the center to the side (minus 0.5). Vector point_D = getPointForLane(t, 3.5); // Z=1 to Z=-1 Vector point_F = point_C.subtract(new Vector(0, 0, 2)); Vector point_H = point_D.subtract(new Vector(0, 0, 2)); // initially, there are no "previous" vectors to use as start. if (i > 0) { Vector norm_outside = new Vector(point_E.x(), point_E.y(), 0).normalized(); Vector norm_inside = norm_outside.scale(-1).normalized(); Vector norm_up = Vector.Z; // Set brick texture if (race.enableTextures) { race.getBrickTexture().bind(gl); } // Draw track walls gl.glBegin(GL_QUADS); setColor(Color.RED); // inside bottom glNormal(norm_inside); gl.glTexCoord2f(0, 0); glVertex(point_E); gl.glTexCoord2f(1, 0); glVertex(point_F); setColor(Colors.PALE_TURQOISE); // inside top glNormal(norm_up.add(norm_inside).normalized()); gl.glTexCoord2f(1, 1); glVertex(point_C); gl.glTexCoord2f(0, 1); glVertex(point_A); // outside bottom glNormal(norm_outside); gl.glTexCoord2f(0, 0); glVertex(point_G); gl.glTexCoord2f(1, 0); glVertex(point_H); // outside top glNormal(norm_up.add(norm_outside).normalized()); gl.glTexCoord2f(1, 1); glVertex(point_D); gl.glTexCoord2f(0, 1); glVertex(point_B); gl.glEnd(); if (race.enableTextures) { race.getTrackTexture().bind(gl); } // Draw track itself // Every 20 segments a distance line is drawn, // and at the start, a start line is drawn. gl.glBegin(GL_QUADS); glNormal(Vector.Z); gl.glTexCoord2f(i == 1 ? 0 : 0.2f, 0); glVertex(point_A); gl.glTexCoord2f(i % 20 == 0 && i != SEGMENTS ? 1f : 0.8f, 0); glVertex(point_C); gl.glTexCoord2f(i % 20 == 0 && i != SEGMENTS ? 1f : 0.8f, 1f); glVertex(point_D); gl.glTexCoord2f(i == 1 ? 0 : 0.2f, 1f); glVertex(point_B); gl.glEnd(); } unbindTextures(); // save points for next draw round point_E = point_F; point_A = point_C; point_B = point_D; point_G = point_H; } } /** * Draw a closed race track. * * @param pts Control points. */ private void drawTrack(Vector[] pts) { if (pts == null) { System.err.println("not implemented points"); return; } assert pts.length % 3 == 0 : "Multiple of three control points required"; int number_of_segments = pts.length / 3; // number of "u" units per segment double segment_size = 1.0 / number_of_segments; gl.glLineWidth(5); gl.glBegin(GL_LINE_STRIP); for (double i = 0; i <= SEGMENTS; ++i) { double u = i / SEGMENTS; int segment_number = (int) ((i / (SEGMENTS + 1)) / segment_size); int start = 3 * segment_number; double segment_u = u - segment_number * segment_size; // scale the part to 0.0 to 0.1 segment_u *= number_of_segments; assert segment_u >= 0.0 && segment_u <= 1.0 : "Segment out of bounds"; Vector bezierPt = getCubicBezierPnt(segment_u, pts, start); gl.glColor4d(1.0, 0.0, segment_number, .8); glVertex(bezierPt); } gl.glEnd(); if (drawControlPoints) { // draw control points gl.glPointSize(10); gl.glBegin(GL_POINTS); for (int i = 0; i < pts.length; i++) { double color = ((double) i / 3.0); gl.glColor3d(0.0, color, 1.0); glVertex(pts[i]); } gl.glEnd(); } } /** * Obtains a cubic Bézier segment from points P0, P1, P2 and P3 for * parameter value t. */ public static Vector getCubicBezierPnt(double t, Vector P0, Vector P1, Vector P2, Vector P3) { // the factorials for the Bézier blending functions (Bernstein // polynomials) with n=3 are pre-calculated. // P(u) = (1 - u)^3 . P0 + // 3u (1 - u)^2 . P1 + // 3u^2 (1 - u) . P2 + // u^3 . P3 // Implementation note: Vector is instantiated 7 times! return P0.scale( pow(1 - t, 3)) // k = 0 .add(P1.scale(3 * t * pow(1 - t, 2))) // k = 1 .add(P2.scale(3 * pow(t, 2) * (1 - t))) // k = 2 .add(P3.scale( pow(t, 3))); // k = 3 } /** * Obtains a point on the Bézier curve from points P, starting at index i. */ public static Vector getCubicBezierPnt(double t, Vector[] P, int i) { Vector P4 = P[(i + 3) % P.length]; return getCubicBezierPnt(t, P[i], P[i + 1], P[i + 2], P4); } /** * Evaluate the tangent of a cubic Bézier segment from points P0, P2, P2 and * P3 for parameter value t. */ public static Vector getCubicBezierTng(double t, Vector P0, Vector P1, Vector P2, Vector P3) { // The tangent is the derivative of the Bézier curve P(t). // dP(u) / du = 3 (1 - u)^2 . P0 + // (3 (1 - u)^2 + 6u (1 - u)) . P1 + // (6u (1 - u) - 3u^2) . P2 + // 3u^2 . P3 // = 3 (1 - u)^2 . P0 + // (3 - 3u^2) . P1 + // (6u - 9u^2) . P2 + // 3u^2 . P3 return P0.scale(3 * pow(1 - t, 2)) .add(P1.scale(3 - 3 * t * t)) .add(P2.scale(6 * t + 9 * t * t)) .add(P3.scale(3 * t * t)); } }