summaryrefslogtreecommitdiff
path: root/src/Camera.java
blob: 740328e0056e8c33481c45e798862b08f8c5ea3a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333

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 = true;

    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());

        /* look in the direction where the robots walks, namely the tangent
         Add the actual robot position to the tangent vector, and calculate
         the normal vector based on the resulting vector. This is the up vector. */
        Vector robotPos = track.getPointForLane(focus.getTimePos(),
                focus.getLane());
        Vector robotTangent = track.getTangent(focus.getTimePos());
        Vector totalVector = robotTangent.add(robotPos);

        up = new Vector(-totalVector.y(), totalVector.x(), 0);

        // "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 one meter above the race track.
        eye = eye.add(new Vector(0, 0, 1f));
    }

    /**
     * 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.
     */
    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;
        }
    }
}