import robotrace.Vector; import static java.lang.Math.*; import robotrace.GlobalState; /** * Implementation of a camera with a position and orientation. */ class Camera { /** The position of the camera. */ public Vector eye = new Vector(3f, 6f, 5f); /** The point to which the camera is looking. */ public Vector center = Vector.O; /** The up vector. */ public Vector up = Vector.Z; /** Race track used. */ private final RaceTrack track; /** * A reference to the global game state from RobotRace. */ private final GlobalState gs; /** * Robots that are to be tracked by the camera. */ private final Robot[] robots; /** * True if the robot with the highest speed should be focused in Helicopter, * Motor and FP mode; false if the robot that made the longest distance * should be focused. */ boolean followTopSpeed = false; public Camera(GlobalState gs, RaceTrack track, Robot[] robots) { this.gs = gs; this.track = track; this.robots = robots; } /** * Updates the camera viewpoint and direction based on the * selected camera mode. */ public void update(int mode) { if (1 == mode) { // Helicopter mode setHelicopterMode(); } else if (2 == mode) { // Motor cycle mode setMotorCycleMode(); } else if (3 == mode) { // First person mode setFirstPersonMode(); } else if (4 == mode) { // Auto mode setAutoMode(); } else { // Default mode setDefaultMode(); } } /** * Computes {@code eye}, {@code center}, and {@code up}, based * on the camera's default mode. */ private void setDefaultMode() { /* z | * | vDist % * | % * Ez * |%________*________ y * Ex / % * * / s % * * x / - - - - - - - * * Ey * phi is angle between vDist and XY plane (Z direction) * theta is angle between X-axis and s (XY plane) * E = (Ex, Ey, Ez) * sin phi = Ez / vDist => Ez = vDist * sin phi * cos phi = s / vDist => s = vDist * cos phi * Ex = s * sin theta * Ey = s * cos theta */ float Ex, Ey, Ez, s; Ez = gs.vDist * (float) sin(gs.phi); s = gs.vDist * (float) cos(gs.phi); Ex = s * (float) sin(gs.theta); Ey = s * (float) cos(gs.theta); eye = new Vector(Ex, Ey, Ez); // WASD action: center point and eye point translate double Cx, Cy, Cz; // x and y are swapped because robot looks in y direction Cx = -gs.cnt.y(); Cy = gs.cnt.x(); Cz = gs.cnt.z(); center = new Vector(Cx, Cy, Cz); eye = eye.add(center); // just look straight forward up = Vector.Z; } /** * Computes {@code eye}, {@code center}, and {@code up}, based * on the helicopter mode. */ private void setHelicopterMode() { /** * In the Helicopter view, the camera (eye point) is located above the * robots. */ FocusPosition focus = smoothFocusTo(getFocusedRobot()); // center at the chosen robot. center = track.getPointForLane(focus.getTimePos(), focus.getLane()); // the head (up vector) points "forwards" up = track.getTangent(focus.getTimePos()); // "above" is 10 meters. eye = center.add(new Vector(0, 0, 10f)); } /** * Computes {@code eye}, {@code center}, and {@code up}, based * on the motorcycle mode. */ private void setMotorCycleMode() { /** * In the Motor Cycle view, the camera is at the side of a track, * following the robots. */ FocusPosition focus = smoothFocusTo(getFocusedRobot()); // Center at the focused robot. center = track.getPointForLane(focus.getTimePos(), focus.getLane()); // We are looking at the robot from the side. up = Vector.Z; // look at a distance of 10 meters from the center of the first lane eye = track.getPointForLane(focus.getTimePos(), 10); // assume that the motor camera is five meters above the race track. eye = eye.add(new Vector(0, 0, 5f)); } /** * Computes {@code eye}, {@code center}, and {@code up}, based * on the first person mode. */ private void setFirstPersonMode() { /** * First person mode: look from the slowest robot forward. */ FocusPosition focus = smoothFocusTo(getSlowestRobot()); // trivial: looks from the robot POV. eye = track.getPointForLane(focus.getTimePos(), focus.getLane()); // robots are two meter, look from head. eye = eye.add(new Vector(0, 0, 2f)); // The question is similar to question 2b of the intermediate test // http://www.win.tue.nl/~vanwijk/2IV60/2IV60_test_exam_161213_answers.pdf // C(t) = E(t) + P'(t) (P'(t) is the tangent vector). Vector robotTangent = track.getTangent(focus.getTimePos()); // look at a point one meter in front of the robot // TODO: this gets badly messed up when perspective is modified center = eye.add(robotTangent); // trivial: look forward, so up vector points up. up = Vector.Z; } /** * Alternates between the available camera modes. */ private void setAutoMode() { double slowest_pos = robots[0].getTimePos(); double fastest_pos = robots[0].getTimePos(); for (Robot robot : robots) { slowest_pos = Math.min(slowest_pos, robot.getTimePos()); fastest_pos = Math.max(fastest_pos, robot.getTimePos()); } double distance = Robot.racepost2meter(fastest_pos - slowest_pos); // the helicopter view is more suitable if robots are more distant if (distance > 5) { setHelicopterMode(); } else { setMotorCycleMode(); } } /** * Returns the robot on which the camera is focused. */ private Robot getFocusedRobot() { Robot selected = robots[0]; for (Robot robot : robots) { // Many possibilities here, fastest, slowest, loser, winner... if (followTopSpeed) { if (selected.getSpeed() < robot.getSpeed()) { // select fastest robot, the one walking in the front selected = robot; } } else { if (selected.getTimePos() < robot.getTimePos()) { // select the fastest accelerating robot. selected = robot; } } } return selected; } /** * Returns the robots which has the lowest Global State Time position. * (i.e. the last robot on the field). */ private Robot getSlowestRobot() { Robot slowest = robots[0]; for (Robot robot : robots) { if (robot.getTimePos() < slowest.getTimePos()) { slowest = robot; } } return slowest; } /** * Time when the transition started; */ private long transition_start_ms; private Robot old_target, moving_to_target; /** * Time that a transition takes to move from one to another target in ms. */ private static final long TRANSITION_PERIOD = 500; /** * Determine the time position for the track position, using the specified * robot parameter as new focus target. If the new focus target is different * from the old one, then a smooth transition will be made between the old * and new one. */ private FocusPosition smoothFocusTo(Robot target) { // target: goal // moving target: new goal (after complete, set target to this) // old target: previous goal (set to moving target when done) // states: // 1. same target // 2. moving to new target (post-condition: moving != null) long now = System.currentTimeMillis(); if (moving_to_target == null) { // state 1 // transition 1 -> 2 if old != new if (target != old_target) { old_target = target; moving_to_target = target; transition_start_ms = now; } else { // transition 1 -> 1 if old == new // "no transition in progress" or "transition is still complete" } } else if (moving_to_target != null) { // state 2 // transition 2 -> 2 if the aimed target has changed if (moving_to_target != target) { // pretend that the camera moved from the current moving target old_target = moving_to_target; moving_to_target = target; System.err.println("target changed at " + transition_start_ms); // XXX: this does not work well if the transition has just // started and the target is changed to something far away. It // results in a non-fluent switch to the new target. transition_start_ms = now; } else { // transition 2 -> 2 if target is still the same // "transition progresses as time is increased" } // transition complete 2 -> 1: set new old target if (now - transition_start_ms >= TRANSITION_PERIOD) { old_target = target; moving_to_target = null; } } if (moving_to_target != null) { // in move state double n = (now - transition_start_ms) / (double) TRANSITION_PERIOD; // time is distributied from TC% target and the remaining old target double time = n * target.getTimePos(); time += (1 - n) * old_target.getTimePos(); double lane = n * target.getLane(); lane += (1 - n) * old_target.getLane(); return new FocusPosition(lane, time); } else { // not moving return new FocusPosition(target.getLane(), target.getTimePos()); } } /** * Represents the target on the track to focus the camera on. */ class FocusPosition { private final double lane; private final double time; FocusPosition(double lane, double time) { this.lane = lane; this.time = time; } double getTimePos() { return time; } double getLane() { return lane; } } }