1 /*
2  * See Licensing and Copyright notice in naev.h
3  */
4 
5 /**
6  * @file camera.c
7  *
8  * @brief Handles the camera.
9  */
10 
11 
12 #include "camera.h"
13 
14 #include "naev.h"
15 
16 #include "log.h"
17 #include "conf.h"
18 #include "space.h"
19 #include "gui.h"
20 #include "nebula.h"
21 #include "pause.h"
22 #include "background.h"
23 #include "player.h"
24 
25 
26 #define CAMERA_DIR      (M_PI/2.)
27 
28 
29 static unsigned int camera_followpilot = 0; /**< Pilot to follow. */
30 /* Current camera position. */
31 static double camera_Z     = 1.; /**< Current in-game zoom. */
32 static double camera_X     = 0.; /**< X position of camera. */
33 static double camera_Y     = 0.; /**< Y position of camera. */
34 /* Old is used to compensate pilot movement. */
35 static double old_X        = 0.; /**< Old X positiion. */
36 static double old_Y        = 0.; /**< Old Y position. */
37 /* Target is used why flying over with a target set. */
38 static double target_Z     = 0.; /**< Target zoom. */
39 static double target_X     = 0.; /**< Target X position. */
40 static double target_Y     = 0.; /**< Target Y position. */
41 static int camera_fly      = 0; /**< Camera is flying to target. */
42 static double camera_flyspeed = 0.; /**< Speed when flying. */
43 
44 
45 /*
46  * Prototypes.
47  */
48 static void cam_updateFly( double x, double y, double dt );
49 static void cam_updatePilot( Pilot *follow, double dt );
50 static void cam_updatePilotZoom( Pilot *follow, Pilot *target, double dt );
51 static void cam_updateManualZoom( double dt );
52 
53 
54 /**
55  * @brief Sets the camera zoom.
56  *
57  * This is the zoom used in game coordinates.
58  *
59  *    @param zoom Zoom to set to.
60  */
cam_setZoom(double zoom)61 void cam_setZoom( double zoom )
62 {
63    camera_Z = CLAMP( conf.zoom_far, conf.zoom_near, zoom );
64 }
65 
66 
67 /**
68  * @brief Sets the camera zoom target.
69  *
70  * This should be used in conjunction with manual zoom.
71  *
72  *    @param zoom Zoom to try to set camera to.
73  */
cam_setZoomTarget(double zoom)74 void cam_setZoomTarget( double zoom )
75 {
76    target_Z = CLAMP( conf.zoom_far, conf.zoom_near, zoom );
77 }
78 
79 
80 /**
81  * @brief Gets the camera zoom.
82  *
83  *    @return The camera's zoom.
84  */
cam_getZoom(void)85 double cam_getZoom (void)
86 {
87    return camera_Z;
88 }
89 
90 
91 /**
92  * @brief Gets the camera zoom.
93  *
94  *    @return The camera's zoom.
95  */
cam_getZoomTarget(void)96 double cam_getZoomTarget (void)
97 {
98    return target_Z;
99 }
100 
101 
102 /**
103  * @brief Gets the camera position.
104  *
105  *    @param[out] x X position to get.
106  *    @param[out] y Y position to get.
107  */
cam_getPos(double * x,double * y)108 void cam_getPos( double *x, double *y )
109 {
110    *x = camera_X;
111    *y = camera_Y;
112 }
113 
114 
115 /**
116  * @brief Sets the target to follow.
117  */
cam_setTargetPilot(unsigned int follow,int soft_over)118 void cam_setTargetPilot( unsigned int follow, int soft_over )
119 {
120    Pilot *p;
121    double dir, x, y;
122 
123    /* Set the target. */
124    camera_followpilot   = follow;
125    dir                  = CAMERA_DIR;
126 
127    /* Set camera if necessary. */
128    if (!soft_over) {
129       if (follow != 0) {
130          p = pilot_get( follow );
131          if (p != NULL) {
132             dir      = p->solid->dir;
133             x        = p->solid->pos.x;
134             y        = p->solid->pos.y;
135             camera_X = x;
136             camera_Y = y;
137             old_X    = x;
138             old_Y    = y;
139          }
140       }
141       camera_fly = 0;
142    }
143    else {
144       old_X    = camera_X;
145       old_Y    = camera_Y;
146       camera_fly = 1;
147       camera_flyspeed = (double) soft_over;
148    }
149    sound_updateListener( dir, camera_X, camera_Y, 0., 0. );
150 }
151 
152 
153 /**
154  * @brief Sets the camera target to a position.
155  */
cam_setTargetPos(double x,double y,int soft_over)156 void cam_setTargetPos( double x, double y, int soft_over )
157 {
158    /* Disable pilot override. */
159    camera_followpilot = 0;
160 
161    /* Handle non soft. */
162    if (!soft_over) {
163       camera_X = x;
164       camera_Y = y;
165       old_X    = x;
166       old_Y    = y;
167       camera_fly = 0;
168    }
169    else {
170       target_X = x;
171       target_Y = y;
172       old_X    = camera_X;
173       old_Y    = camera_Y;
174       camera_fly = 1;
175       camera_flyspeed = (double) soft_over;
176    }
177    sound_updateListener( CAMERA_DIR, camera_X, camera_Y, 0., 0. );
178 }
179 
180 
181 /**
182  * @brief Returns the camera's current target.
183  *
184  *    @return 0 if focused on position, else returns pilot ID.
185  */
cam_getTarget(void)186 int cam_getTarget( void )
187 {
188    return camera_followpilot;
189 }
190 
191 
192 /**
193  * @brief Updates the camera.
194  *
195  *    @param dt Current delta tick.
196  */
cam_update(double dt)197 void cam_update( double dt )
198 {
199    Pilot *p;
200    double dx, dy;
201 
202    /* Calculate differential. */
203    dx    = old_X;
204    dy    = old_Y;
205 
206    /* Going to position. */
207    p   = NULL;
208    if (camera_fly) {
209       if (camera_followpilot != 0) {
210          p = pilot_get( camera_followpilot );
211          if (p == NULL) {
212             camera_followpilot = 0;
213             camera_fly = 0;
214          }
215          else {
216             cam_updateFly( p->solid->pos.x, p->solid->pos.y, dt );
217             cam_updatePilotZoom( p, NULL, dt );
218          }
219       }
220       else
221          cam_updateFly( target_X, target_Y, dt );
222    }
223    else {
224       /* Track pilot. */
225       if (camera_followpilot != 0) {
226          p = pilot_get( camera_followpilot );
227          if (p == NULL)
228             camera_followpilot = 0;
229          else
230             cam_updatePilot( p, dt );
231       }
232    }
233 
234    /* Update manual zoom. */
235    if (conf.zoom_manual)
236       cam_updateManualZoom( dt );
237 
238    /* Set the sound. */
239    if ((p==NULL) || !conf.snd_pilotrel) {
240       dx = dt*(dx-camera_X);
241       dy = dt*(dy-camera_Y);
242       sound_updateListener( CAMERA_DIR, camera_X, camera_Y, dx, dy );
243    }
244    else {
245       sound_updateListener( p->solid->dir,
246             p->solid->pos.x, p->solid->pos.y,
247             p->solid->vel.x, p->solid->vel.y );
248    }
249 }
250 
251 
252 /**
253  * @brief Updates the camera flying to a position.
254  */
cam_updateFly(double x,double y,double dt)255 static void cam_updateFly( double x, double y, double dt )
256 {
257    double k, dx,dy, max;
258    double a, r;
259 
260    max = camera_flyspeed*dt;
261    k   = 25.*dt;
262    dx  = (x - camera_X)*k;
263    dy  = (y - camera_Y)*k;
264    if (pow2(dx)+pow2(dy) > pow2(max)) {
265       a  = atan2( dy, dx );
266       r  = max;
267       dx = r*cos(a);
268       dy = r*sin(a);
269    }
270    camera_X += dx;
271    camera_Y += dy;
272    background_moveStars( -dx, -dy );
273 
274    /* Stop within 100 pixels. */
275    if (fabs((pow2(camera_X)+pow2(camera_Y)) - (pow2(x)+pow2(y))) < 100*100) {
276       old_X = camera_X;
277       old_Y = camera_Y;
278       camera_fly = 0;
279    }
280 }
281 
282 
283 /**
284  * @brief Updates a camera following a pilot.
285  */
cam_updatePilot(Pilot * follow,double dt)286 static void cam_updatePilot( Pilot *follow, double dt )
287 {
288    Pilot *target;
289    double diag2, a, r, dir, k;
290    double x,y, dx,dy, mx,my, targ_x,targ_y, bias_x,bias_y, vx,vy;
291 
292    /* Get target. */
293    if (!pilot_isFlag(follow, PILOT_HYPERSPACE) && (follow->target != follow->id))
294       target = pilot_get( follow->target );
295    else
296       target = NULL;
297 
298    /* Real diagonal might be a bit too harsh since it can cut out the ship,
299     * we'll just use the largest of the two. */
300    /*diag2 = pow2(SCREEN_W) + pow2(SCREEN_H);*/
301    /*diag2 = pow2( MIN(SCREEN_W, SCREEN_H) );*/
302    diag2 = 100*100;
303    x = follow->solid->pos.x;
304    y = follow->solid->pos.y;
305 
306    /* Compensate player movement. */
307    mx        = x - old_X;
308    my        = y - old_Y;
309    camera_X += mx;
310    camera_Y += my;
311 
312    /* Set old position. */
313    old_X     = x;
314    old_Y     = y;
315 
316    /* No bias by default. */
317    bias_x = 0.;
318    bias_y = 0.;
319 
320    /* Bias towards target. */
321    if (target != NULL) {
322       bias_x += target->solid->pos.x - x;
323       bias_y += target->solid->pos.y - y;
324    }
325 
326    /* Bias towards velocity and facing. */
327    vx       = follow->solid->vel.x*1.5;
328    vy       = follow->solid->vel.y*1.5;
329    dir      = angle_diff( atan2(vy,vx), follow->solid->dir);
330    dir      = (M_PI - fabs(dir)) /  M_PI; /* Normalize. */
331    vx      *= dir;
332    vy      *= dir;
333    bias_x  += vx;
334    bias_y  += vy;
335 
336    /* Limit bias. */
337    if (pow2(bias_x)+pow2(bias_y) > diag2/2.) {
338       a        = atan2( bias_y, bias_x );
339       r        = sqrt(diag2)/2.;
340       bias_x   = r*cos(a);
341       bias_y   = r*sin(a);
342    }
343 
344    /* Compose the target. */
345    targ_x   = x + bias_x;
346    targ_y   = y + bias_y;
347 
348    /* Head towards target. */
349    k = 0.5*dt/dt_mod;
350    dx = (targ_x-camera_X)*k;
351    dy = (targ_y-camera_Y)*k;
352    background_moveStars( -(mx+dx), -(my+dy) );
353 
354    /* Update camera. */
355    camera_X += dx;
356    camera_Y += dy;
357 
358    /* DEBUG. */
359 #if 0
360    glColor4d( 1., 1., 1., 1. );
361    glBegin(GL_LINES);
362    gl_gameToScreenCoords( &x, &y, x, y );
363    glVertex2d( x, y );
364    gl_gameToScreenCoords( &x, &y, camera_X, camera_Y );
365    glVertex2d( x, y );
366    glEnd(); /* GL_LINES */
367 #endif
368 
369    /* Update zoom. */
370    cam_updatePilotZoom( follow, target, dt );
371 }
372 
373 /**
374  * @brief Updates the manual zoom target.
375  */
cam_updateManualZoom(double dt)376 static void cam_updateManualZoom( double dt )
377 {
378    double d;
379 
380    if (player.p == NULL)
381       return;
382 
383    /* Gradually zoom in/out. */
384    d  = CLAMP( -conf.zoom_speed, conf.zoom_speed, target_Z - camera_Z);
385    d *= dt / dt_mod; /* Remove dt dependence. */
386    if (d < 0) /** Speed up if needed. */
387       d *= 2.;
388    camera_Z =  CLAMP( conf.zoom_far, conf.zoom_near, camera_Z + d );
389 }
390 
391 
392 /**
393  * @brief Updates the camera zoom.
394  */
cam_updatePilotZoom(Pilot * follow,Pilot * target,double dt)395 static void cam_updatePilotZoom( Pilot *follow, Pilot *target, double dt )
396 {
397    double d, x,y, z,tz, dx, dy;
398    double zfar, znear;
399    double c;
400 
401    /* Must have auto zoom enabled. */
402    if (conf.zoom_manual)
403       return;
404 
405    /* Minimum depends on velocity normally.
406     *
407     * w*h = A, cte    area constant
408     * w/h = K, cte    proportion constant
409     * d^2 = A, cte    geometric longitud
410     *
411     * A_v = A*(1+v/d)   area of view is based on speed
412     * A_v / A = (1 + v/d)
413     *
414     * z = A / A_v = 1. / (1 + v/d)
415     */
416    d     = sqrt(SCREEN_W*SCREEN_H);
417    znear = MIN( conf.zoom_near, 1. / (0.8 + VMOD(follow->solid->vel)/d) );
418 
419    /* Maximum is limited by nebulae. */
420    if (cur_system->nebu_density > 0.) {
421       c     = MIN( SCREEN_W, SCREEN_H ) / 2;
422       zfar  = CLAMP( conf.zoom_far, conf.zoom_near, c / nebu_getSightRadius() );
423    }
424    else
425       zfar = conf.zoom_far;
426    znear = MAX( znear, zfar );
427 
428    /*
429     * Set Zoom to pilot target.
430     */
431    z = cam_getZoom();
432    if (target != NULL) {
433       /* Get current relative target position. */
434       gui_getOffset( &x, &y );
435       x += target->solid->pos.x - follow->solid->pos.x;
436       y += target->solid->pos.y - follow->solid->pos.y;
437 
438       /* Get distance ratio. */
439       dx = (SCREEN_W/2.) / (FABS(x) + 2*target->ship->gfx_space->sw);
440       dy = (SCREEN_H/2.) / (FABS(y) + 2*target->ship->gfx_space->sh);
441 
442       /* Get zoom. */
443       tz = MIN( dx, dy );
444    }
445    else
446       tz = znear; /* Aim at in. */
447 
448    /* Gradually zoom in/out. */
449    d  = CLAMP(-conf.zoom_speed, conf.zoom_speed, tz - z);
450    d *= dt / dt_mod; /* Remove dt dependence. */
451    if (d < 0) /** Speed up if needed. */
452       d *= 2.;
453    camera_Z =  CLAMP( zfar, znear, z + d);
454 }
455 
456 
457