import java.awt.Color; import javax.media.opengl.GL; import static javax.media.opengl.GL2.*; import javax.media.opengl.fixedfunc.GLLightingFunc; /** * Represents a Robot, to be implemented according to the Assignments. */ class Robot extends BetterBase { private final Color boneColor = Colors.CHOCOLATE; /** 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 terms of GlobalState time). */ private double robot_time_pos; /** * The lane number on which this robot is positioned. Must be a positive * number greater or equal to zero. */ private final int laneNo; /** * Constructs the robot with initial parameters. */ public Robot(Material material, int laneNo) { /* 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; } /** * 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); // 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 + legLength + footHeight); // Draw the robot, everything is relative to the center of torso. // Static parts (that do not animate): drawTorso(); drawHead(); // 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); drawLeg(true); // draw left and right arms drawArm(false); drawArm(true); // // 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) { 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); glut.glutSolidCube(1); // return to previous scales gl.glScalef(1 / x, 1 / y, 1 / z); } /** * 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); 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); 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); // 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, 32, 32); // left shoulder gl.glTranslatef(-2 * shoulder_x, 0, 0); glut.glutSolidSphere(shoulderRadius, 32, 32); // 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). */ private void drawLeg(boolean isRight) { // 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(); // upper leg half gl.glTranslatef(0, 0, -legLength / 4); drawBeam(legWidth, legWidth, legLength / 2, Direction.Z, Color.DARK_GRAY); // knee gl.glTranslatef(0, 0, -legLength / 4); drawJoint(); // lower leg half gl.glTranslatef(0, 0, -legLength / 4); drawBeam(legWidth, legWidth, legLength / 2, Direction.Z, Color.DARK_GRAY); // 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); // 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). */ private void drawArm(boolean isRight) { // 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(); // 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); 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); // 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); 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 (time). */ public double getTimePos() { return robot_time_pos; } /** * Set the speed (meters per second) for this robot. */ public void setSpeed(double speed) { this.speed = speed; } /** * Gets the speed of this robot. */ 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_time_pos += speed * seconds; } /** * Returns the lane number on which the robot should walk. */ public int getLane() { return laneNo; } }