import java.awt.Color; import javax.media.opengl.GL; import static javax.media.opengl.GL2.*; import javax.media.opengl.fixedfunc.GLLightingFunc; import javax.media.opengl.glu.GLUquadric; import robotrace.Vector; /** * Represents a Robot, to be implemented according to the Assignments. */ class Robot extends BetterBase { private final Color boneColor = Colors.CHOCOLATE; /** * The robot race in which this robot it participating (used for texture * stuff for example). */ private final RobotRace race; /** The material from which this robot is built. */ private final Material material; /** Relative lengths, widths and heights of robot model. */ private final float torsoHeight; private final float torsoWidth; private final float shoulderRadius; private final float armLength; private final float legLength; private final float armWidth; private final float legWidth; private final float neckHeight; private final float headRadius; private final float depth; private final float footWidth; private final float footHeight; private final float footLength; /** Size of the bone for stick figures. */ private final float boneSize; /** * True if a skeleton should be drawn instead of a full body. */ private boolean asStickFigure; /** * Robot speed (on track) in meters per second. */ private double speed; /** * Location of the robot on the track (in meters). */ private double robot_pos_meters; /** * The lane number on which this robot is positioned. Must be a positive * number greater or equal to zero. */ private final int laneNo; /** * Length of the race track in meters. (76 is the approximate perimeter of * an ellipse with half-widths 10 and 14.) */ private final static double TRACK_LENGTH = 76.0; private final WalkAnimation walkAnim; /** * Constructs the robot with initial parameters. */ public Robot(RobotRace race, Material material, int laneNo) { this.race = race; /* Set all parameters of the robot */ this.material = material; this.torsoHeight = 0.6f; this.torsoWidth = 0.48f; this.shoulderRadius = 0.09f; this.armLength = 0.6f; this.armWidth = 0.06f; this.legLength = 0.78f; this.legWidth = 0.06f; this.neckHeight = 0.15f; this.headRadius = 0.12f; this.footWidth = legWidth; this.footHeight = legWidth; this.footLength = 2 * legWidth; this.boneSize = 0.02f; this.depth = 0.24f; this.laneNo = laneNo; this.walkAnim = new SmarterWalkAnimation(legLength / 2, legLength / 2 + footHeight); } /** * Draws this robot (as a {@code stickfigure} if specified). */ public void draw(boolean stickFigure) { // before drawing body parts, configure whether to draw a full body or // just a stick figure with joints and such. this.asStickFigure = stickFigure; // These materials control the reflected light gl.glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, material.diffuse, 0); gl.glMaterialfv(GL_FRONT, GL_SPECULAR, material.specular, 0); // calculate rotation angles and positions for movements. walkAnim.updatePosition(robot_pos_meters); // save positions so it can be restored easily later gl.glPushMatrix(); // position the center of the torso above the Z axis such that the foot // stands on the XY plane. gl.glTranslatef(0, 0, torsoHeight / 2); // the length of the legs and foot are included. gl.glTranslated(0, 0, walkAnim.getBottomOffset()); // Draw the robot, everything is relative to the center of torso. // Static parts (that do not animate): if (race.enableTextures) { race.getTorsoTexture().bind(gl); } drawTorso(); // Rotate the head so it looks forward (for texturing) gl.glRotatef(180, 0, 0, 1); if (race.enableTextures) { race.getHeadTexture().bind(gl); } drawHead(); // Rotate back so the other robot parts are facing the right direction gl.glRotatef(-180, 0, 0, 1); unbindTextures(); // only draw the shoulder parts in normal, full body mode if (!asStickFigure) { drawShoulderTop(); } // Parts that should be animated: // draw left and right legs drawLeg(false, walkAnim.getLegAngleLeft(), walkAnim.getKneeAngleLeft()); drawLeg(true, walkAnim.getLegAngleRight(), walkAnim.getKneeAngleRight()); // draw left and right arms drawArm(false, walkAnim.getArmAngleLeft()); drawArm(true, walkAnim.getArmAngleRight()); // // The following function call exist to make a point clear. Adding // comments for the sake of having comments is silly, pointless and an // outdated idea. Self-documenting code (by using appropriate symbol // names and logical structures) is much more useful than adding // comments for every statement (50% comments...). High-level // descriptions of functionality could be given in a design document or // added as comments, but something like "draws a leg" for a function // named "drawLeg" is useless. If you want to know what exactly happens, // look in the method code, it likely contains some comments to // describe what happens. thisFunctionDoesAbsolutelyNothing(); // // restore position gl.glPopMatrix(); } /** * Draws a 3d figure with given dimensions and color. If a stick figure * must be drawn, then the figure will become a thin line in the X, Y * or Z direction depending on the direction parameter. * @param dir Direction of the line segment, relevant for stick figures. * @param color If non-null, it becomes the color for this beam. Ignored * when drawing a stick figure, in that case the boneColor constant will * be used. */ private void drawBeam(float x, float y, float z, Direction dir, Color color, boolean isTextured) { if (asStickFigure) { // for a stick figure, draw a thin figure without colors switch (dir) { case X: assert x != 0; y = z = boneSize; break; case Y: assert y != 0; x = z = boneSize; break; case Z: assert z != 0; x = y = boneSize; break; default: throw new AssertionError(dir.name()); } // stick figures always get a "bone" color setColor(boneColor); } else { assert x != 0; assert y != 0; assert z != 0; if (color != null) { setColor(color); } } // turn a cube into a small beam gl.glScalef(x, y, z); // Depending on if it needs to be textured, use our own method // or the glut solid cube. if (isTextured && race.enableTextures) { drawCube(); } else { glut.glutSolidCube(1); } // return to previous scales gl.glScalef(1 / x, 1 / y, 1 / z); } /** * Draw a cube with our own defined method with quads, to support * texturing. */ private void drawCube() { /* Define the points of the cube, our cube is defined as follows: * * H ------------ G * /| /| * / | / | * D ------------ C | * | E ---------|-- F * | / | / * | / | / * A ------------ B */ Vector point_A = new Vector(-0.5f, -.5f, -.5f); Vector point_B = new Vector(0.5f, -.5f, -.5f); Vector point_C = new Vector(0.5f, -.5f, 0.5f); Vector point_D = new Vector(-0.5f, -.5f, 0.5f); Vector point_E = new Vector(-0.5f, 0.5f, -.5f); Vector point_F = new Vector(.5f, 0.5f, -.5f); Vector point_G = new Vector(.5f, 0.5f, 0.5f); Vector point_H = new Vector(-0.5f, 0.5f, 0.5f); // Define the six normals Vector norm_up = new Vector(0, 0, 1); Vector norm_right = new Vector(0, 1, 0); Vector norm_towards = new Vector(1, 0, 0); Vector norm_down = norm_up.scale(-1); Vector norm_left = norm_right.scale(-1); Vector norm_outwards = norm_towards.scale(-1); // Start drawing the points of the cube gl.glBegin(GL_QUADS); // Front face glNormal(norm_towards); gl.glTexCoord2f(0, 0); glVertex(point_A); gl.glTexCoord2f(1, 0); glVertex(point_B); gl.glTexCoord2f(0, 1); glVertex(point_C); gl.glTexCoord2f(1, 1); glVertex(point_D); // Right face glNormal(norm_right); gl.glTexCoord2f(0, 0); glVertex(point_B); gl.glTexCoord2f(1, 0); glVertex(point_F); gl.glTexCoord2f(0, 1); glVertex(point_G); gl.glTexCoord2f(1, 1); glVertex(point_C); // Back face glNormal(norm_outwards); gl.glTexCoord2f(0, 0); glVertex(point_F); gl.glTexCoord2f(1, 0); glVertex(point_G); gl.glTexCoord2f(0, 1); glVertex(point_H); gl.glTexCoord2f(1, 1); glVertex(point_E); // Left face glNormal(norm_left); gl.glTexCoord2f(0, 0); glVertex(point_A); gl.glTexCoord2f(1, 0); glVertex(point_G); gl.glTexCoord2f(0, 1); glVertex(point_H); gl.glTexCoord2f(1, 1); glVertex(point_D); // Top face glNormal(norm_up); gl.glTexCoord2f(0, 0); glVertex(point_D); gl.glTexCoord2f(1, 0); glVertex(point_C); gl.glTexCoord2f(0, 1); glVertex(point_G); gl.glTexCoord2f(1, 1); glVertex(point_H); // Bottom face glNormal(norm_down); gl.glTexCoord2f(0, 0); glVertex(point_A); gl.glTexCoord2f(1, 0); glVertex(point_B); gl.glTexCoord2f(0, 1); glVertex(point_F); gl.glTexCoord2f(1, 1); glVertex(point_E); gl.glEnd(); } /** * Draws a joint for stick figure model. */ private void drawJoint() { if (asStickFigure) { glut.glutSolidSphere(boneSize * 1.5, 16, 16); } } /** * Draws the torso of the robot. */ private void drawTorso() { // Scale the torso to specified values drawBeam(torsoWidth, depth, torsoHeight, Direction.Z, Color.LIGHT_GRAY, true); if (asStickFigure) { // draw the bone connecting the arms (visible for stick figure) gl.glTranslatef(0, 0, torsoHeight / 2); drawBeam(torsoWidth + armWidth * 3, boneSize, boneSize, Direction.X, null, false); gl.glTranslatef(0, 0, -torsoHeight); // draw the bone connecting the legs (visible for stick figure) drawBeam(.5f * torsoWidth + legWidth / 2, boneSize, boneSize, Direction.X, null, false); // return to torso center gl.glTranslatef(0, 0, torsoHeight / 2); } } /** * Draws both shoulders of the robot. */ private void drawShoulderTop() { // save position gl.glPushMatrix(); float shoulder_x = torsoWidth / 2 + shoulderRadius / 1.5f; // Translate to the left of the robot for left shoulder gl.glTranslatef(shoulder_x, 0f, torsoHeight / 2); setColor(Colors.BLUEISH); // Set the drawing color and draw right shoulder glut.glutSolidSphere(shoulderRadius, 16, 16); // left shoulder gl.glTranslatef(-2 * shoulder_x, 0, 0); glut.glutSolidSphere(shoulderRadius, 16, 16); // restore position gl.glPopMatrix(); } /** * Draws the upper and lower legs and feet of the robot. * @param isRight True if at the robot's right (from the robot POV). * @param hip_angle Angle at the hip in degrees. * @param knee_angle Angle behind knee in degrees. */ private void drawLeg(boolean isRight, double hip_angle, double knee_angle) { // save center position gl.glPushMatrix(); // The legs are located on the first and third quarter float leg_top_x = -torsoWidth / 4; if (!isRight) { leg_top_x += torsoWidth / 2; } gl.glTranslatef(leg_top_x, 0f, -torsoHeight / 2); drawJoint(); // rotate upper leg around hips gl.glRotated(hip_angle, 1, 0, 0); // upper leg half gl.glTranslatef(0, 0, -legLength / 4); drawBeam(legWidth, legWidth, legLength / 2, Direction.Z, Color.DARK_GRAY, false); // knee gl.glTranslatef(0, 0, -legLength / 4); drawJoint(); // rotate lower leg around back of knee (clock-wise "180 - knee angle") gl.glRotated(knee_angle - 180, 1, 0, 0); // lower leg half gl.glTranslatef(0, 0, -legLength / 4); drawBeam(legWidth, legWidth, legLength / 2, Direction.Z, Color.DARK_GRAY, false); // for the stick figure, the "foot" is just an extension of the leg line // and therefore stays at the same y-position float foot_y = 0; if (!asStickFigure) { // go to the heel and then move to the center of the foot foot_y = -legWidth / 2 + footLength / 2; } // draw foot! gl.glTranslatef(0, foot_y, -legLength / 4 - footHeight / 2); drawBeam(footWidth, footLength, footHeight, Direction.Z, Colors.GRAYISH, false); // Restore position gl.glPopMatrix(); } /** * Draw both arms and both hands of the robot. * @param isRight True if at the robot's right (from the robot POV). * @param armAngle angle of the arm in degrees. */ private void drawArm(boolean isRight, double armAngle) { // Push the translation matrix so we can return to the origin gl.glPushMatrix(); // the arm is located outside the torso float arm_x = torsoWidth / 2 + armWidth * 1.5f; if (isRight) { arm_x *= -1; } // arm starts next to the shoulder let's add a joint there... gl.glTranslatef(arm_x, 0, torsoHeight / 2); drawJoint(); // rotate arm around shoulder gl.glRotated(armAngle, 1, 0, 0); // here is your arm (start drawing from the elbow) gl.glTranslatef(0, 0, -armLength / 2); drawBeam(armWidth, armWidth, armLength, Direction.Z, Colors.ARM_GRAY_COLOR, false); if (!asStickFigure) { // Give me a big hand! setColor(Colors.DIRTY_BLUE); gl.glTranslatef(0f, 0f, -armLength / 2); glut.glutSolidSphere(armWidth * 1.25f, 32, 32); } gl.glPopMatrix(); } /** * Draw the head and the neck of the robot. */ private void drawHead() { // Push matrix so we can go to the origin afterwards gl.glPushMatrix(); // position centered above the torso for the neck gl.glTranslatef(0f, 0f, torsoHeight / 2 + neckHeight / 2); float neckRadius = headRadius / 2.5f; drawBeam(neckRadius, neckRadius, neckHeight, Direction.Z, Colors.LAVENDER, false); // continue moving to the center of the head gl.glTranslatef(0f, 0f, neckHeight / 2 + headRadius / 2); // Set color and draw head setColor(asStickFigure ? boneColor : Colors.PALE_TURQOISE); // Create glu quadric object GLUquadric sphere = glu.gluNewQuadric(); // Set glu drawing settings glu.gluQuadricDrawStyle(sphere, glu.GLU_FILL); glu.gluQuadricTexture(sphere, true); glu.gluQuadricNormals(sphere, glu.GLU_SMOOTH); // Draw the glu sphere glu.gluSphere(sphere, headRadius, 16, 16); // glut.glutSolidSphere(headRadius, 32, 32); // Pop so we are at the origin again gl.glPopMatrix(); } /** * This function does nothing, its use is described at the caller in * Robot.draw(). */ private void thisFunctionDoesAbsolutelyNothing() { } /** * Determine the location of the robot on the track. * @return A positive number between 0 (begin) and 1 (end). Higher values * can also be returned which means that at least one full round has been * made. */ public double getTimePos() { double pos = meter2racepos(robot_pos_meters); return pos; } /** * Map meters to the position on the race track. * @param meter Distance in meters. * @return Position on the race track. */ public static double meter2racepos(double meter) { return meter / TRACK_LENGTH; } /** * Map the position on the race track to meters. * @param gst Elapsed global state time. * @return Distance in meters. */ public static double racepost2meter(double gst) { return gst * TRACK_LENGTH; } /** * Set the speed (meters per second) for this robot. */ public void setSpeed(double speed) { assert speed >= 0 : "Speed must be positive!"; this.speed = speed; } /** * Gets the speed of this robot (in meters per second). */ public double getSpeed() { return speed; } /** * Move the robot with the number of seconds based on its current speed. */ public void walkSome(double seconds) { assert seconds >= 0 : "Robot cannot walk backwards!"; robot_pos_meters += speed * seconds; } /** * Resets the position on the track to the begin. */ public void resetPosition() { robot_pos_meters = 0; } /** * Returns the lane number on which the robot should walk. */ public int getLane() { return laneNo; } }