2 * Copyright (C) 2003 Robert Kooima
4 * NEVERBALL is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published
6 * by the Free Software Foundation; either version 2 of the License,
7 * or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
31 /*---------------------------------------------------------------------------*/
33 static struct s_file file
;
34 static struct s_file back
;
36 static float clock
= 0.f
; /* Clock time */
38 static float game_ix
; /* Input rotation about X axis */
39 static float game_iz
; /* Input rotation about Z axis */
40 static float game_rx
; /* Floor rotation about X axis */
41 static float game_rz
; /* Floor rotation about Z axis */
43 static float view_a
; /* Ideal view rotation about Y axis */
44 static float view_ry
; /* Angular velocity about Y axis */
45 static float view_dc
; /* Ideal view distance above ball */
46 static float view_dp
; /* Ideal view distance above ball */
47 static float view_dz
; /* Ideal view distance behind ball */
48 static float view_fov
; /* Field of view */
50 static float view_c
[3]; /* Current view center */
51 static float view_v
[3]; /* Current view vector */
52 static float view_p
[3]; /* Current view position */
53 static float view_e
[3][3]; /* Current view orientation */
55 static int swch_e
= 1; /* Switching enabled flag */
56 static int jump_e
= 1; /* Jumping enabled flag */
57 static int jump_b
= 0; /* Jump-in-progress flag */
58 static float jump_dt
; /* Jump duration */
59 static float jump_p
[3]; /* Jump destination */
61 static GLuint shadow_text
; /* Shadow texture object */
63 /*---------------------------------------------------------------------------*/
65 static void view_init(void)
70 view_fov
= (float) config_get(CONFIG_VIEW_FOV
);
71 view_dp
= (float) config_get(CONFIG_VIEW_DP
) / 100.0f
;
72 view_dc
= (float) config_get(CONFIG_VIEW_DC
) / 100.0f
;
73 view_dz
= (float) config_get(CONFIG_VIEW_DZ
) / 100.0f
;
94 int game_init(const char *file_name
, const char *back_name
, int t
)
105 part_init(GOAL_HEIGHT
);
111 clock
= (float) t
/ 100.f
;
113 shadow_text
= make_image_from_file(NULL
, NULL
, NULL
, NULL
, IMG_SHADOW
);
115 if (config_get(CONFIG_SHADOW
) == 2)
117 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_WRAP_S
, GL_CLAMP
);
118 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_WRAP_T
, GL_CLAMP
);
121 return (sol_load(&back
, back_name
, config_get(CONFIG_TEXTURES
), 0) &&
122 sol_load(&file
, file_name
, config_get(CONFIG_TEXTURES
),
123 config_get(CONFIG_SHADOW
)));
128 if (glIsTexture(shadow_text
))
129 glDeleteTextures(1, &shadow_text
);
136 /*---------------------------------------------------------------------------*/
140 return (int) (clock
* 100.f
);
143 char *curr_intro(void)
145 return (file
.ac
> 0) ? file
.av
: NULL
;
148 /*---------------------------------------------------------------------------*/
150 static void game_draw_balls(const struct s_file
*fp
)
152 float c
[4] = { 1.0f
, 1.0f
, 1.0f
, 0.7f
};
155 m_basis(M
, fp
->uv
[0].e
[0], fp
->uv
[0].e
[1], fp
->uv
[0].e
[2]);
159 glTranslatef(fp
->uv
[0].p
[0],
160 fp
->uv
[0].p
[1] + BALL_FUDGE
,
163 glScalef(fp
->uv
[0].r
,
174 static void game_draw_coins(const struct s_file
*fp
)
176 float r
= 360.f
* SDL_GetTicks() / 1000.f
;
181 for (ci
= 0; ci
< fp
->cc
; ci
++)
182 if (fp
->cv
[ci
].n
> 0)
186 glTranslatef(fp
->cv
[ci
].p
[0],
189 glRotatef(r
, 0.0f
, 1.0f
, 0.0f
);
190 coin_draw(fp
->cv
[ci
].n
, r
);
198 static void game_draw_goals(const struct s_file
*fp
, float rx
, float ry
)
202 for (zi
= 0; zi
< fp
->zc
; zi
++)
206 glTranslatef(fp
->zv
[zi
].p
[0],
210 part_draw_goal(rx
, ry
, fp
->zv
[zi
].r
);
212 glScalef(fp
->zv
[zi
].r
, 1.f
, fp
->zv
[zi
].r
);
219 static void game_draw_jumps(const struct s_file
*fp
)
223 for (ji
= 0; ji
< fp
->jc
; ji
++)
227 glTranslatef(fp
->jv
[ji
].p
[0],
231 glScalef(fp
->jv
[ji
].r
, 1.f
, fp
->jv
[ji
].r
);
238 static void game_draw_swchs(const struct s_file
*fp
)
242 for (xi
= 0; xi
< fp
->xc
; xi
++)
246 glTranslatef(fp
->xv
[xi
].p
[0],
250 glScalef(fp
->xv
[xi
].r
, 1.f
, fp
->xv
[xi
].r
);
251 swch_draw(fp
->xv
[xi
].f
);
257 /*---------------------------------------------------------------------------*/
260 * A note about lighting and shadow: technically speaking, it's wrong.
261 * The light position and shadow projection behave as if the
262 * light-source rotates with the floor. However, the skybox does not
263 * rotate, thus the light should also remain stationary.
265 * The correct behavior would eliminate a significant 3D cue: the
266 * shadow of the ball indicates the ball's position relative to the
267 * floor even when the ball is in the air. This was the motivating
268 * idea behind the shadow in the first place, so correct shadow
269 * projection would only magnify the problem.
272 static void game_set_shadow(const struct s_file
*fp
)
274 const float *ball_p
= fp
->uv
->p
;
275 const float ball_r
= fp
->uv
->r
;
277 if (config_get(CONFIG_SHADOW
))
279 glActiveTexture(GL_TEXTURE1
);
280 glMatrixMode(GL_TEXTURE
);
282 float k
= 0.5f
/ ball_r
;
284 glEnable(GL_TEXTURE_2D
);
286 glTexEnvi(GL_TEXTURE_ENV
, GL_TEXTURE_ENV_MODE
, GL_MODULATE
);
287 glBindTexture(GL_TEXTURE_2D
, shadow_text
);
290 glTranslatef(0.5f
- ball_p
[0] * k
,
291 0.5f
- ball_p
[2] * k
, 0.f
);
294 glMatrixMode(GL_MODELVIEW
);
295 glActiveTexture(GL_TEXTURE0
);
299 static void game_clr_shadow(void)
301 if (config_get(CONFIG_SHADOW
))
303 glActiveTexture(GL_TEXTURE1
);
305 glDisable(GL_TEXTURE_2D
);
307 glActiveTexture(GL_TEXTURE0
);
311 /*---------------------------------------------------------------------------*/
313 static void game_refl_all(int s
)
315 const float *ball_p
= file
.uv
->p
;
319 /* Rotate the environment about the position of the ball. */
321 glTranslatef(+ball_p
[0], +ball_p
[1], +ball_p
[2]);
322 glRotatef(-game_rz
, view_e
[2][0], view_e
[2][1], view_e
[2][2]);
323 glRotatef(-game_rx
, view_e
[0][0], view_e
[0][1], view_e
[0][2]);
324 glTranslatef(-ball_p
[0], -ball_p
[1], -ball_p
[2]);
326 /* Draw the floor. */
328 if (s
) game_set_shadow(&file
);
330 if (s
) game_clr_shadow();
335 /*---------------------------------------------------------------------------*/
337 static void game_draw_light(void)
339 const float light_p
[2][4] = {
340 { -8.0f
, +32.0f
, -8.0f
, 1.0f
},
341 { +8.0f
, +32.0f
, +8.0f
, 1.0f
},
343 const float light_c
[2][4] = {
344 { 1.0f
, 0.8f
, 0.8f
, 1.0f
},
345 { 0.8f
, 1.0f
, 0.8f
, 1.0f
},
348 /* Configure the lighting. */
351 glLightfv(GL_LIGHT0
, GL_POSITION
, light_p
[0]);
352 glLightfv(GL_LIGHT0
, GL_DIFFUSE
, light_c
[0]);
353 glLightfv(GL_LIGHT0
, GL_SPECULAR
, light_c
[0]);
356 glLightfv(GL_LIGHT1
, GL_POSITION
, light_p
[1]);
357 glLightfv(GL_LIGHT1
, GL_DIFFUSE
, light_c
[1]);
358 glLightfv(GL_LIGHT1
, GL_SPECULAR
, light_c
[1]);
363 static void game_draw_back(int pose
, float rx
, float ry
, int d
, const float p
[3])
365 float o
[3] = { 0.0f
, 0.0f
, 0.0f
};
366 float c
[4] = { 1.0f
, 1.0f
, 1.0f
, 1.0f
};
368 glPushAttrib(GL_FOG_BIT
);
373 glRotatef(game_rz
* 2, view_e
[2][0], view_e
[2][1], view_e
[2][2]);
374 glRotatef(game_rx
* 2, view_e
[0][0], view_e
[0][1], view_e
[0][2]);
377 glTranslatef(p
[0], p
[1], p
[2]);
379 glColor3f(1.0f
, 1.0f
, 1.0f
);
381 /* Draw all background layers back to front. */
383 sol_back(&back
, o
, BACK_DIST
, FAR_DIST
);
385 sol_back(&back
, o
, 0, BACK_DIST
);
387 /* Draw all foreground geometry in the background file. */
390 glFogf(GL_FOG_START
, BACK_DIST
/ 2.0f
);
391 glFogf(GL_FOG_END
, BACK_DIST
);
392 glFogfv(GL_FOG_COLOR
, c
);
400 static void game_draw_fore(int pose
, float rx
, float ry
, int d
, const float p
[3])
402 const float *ball_p
= file
.uv
->p
;
404 glPushAttrib(GL_LIGHTING_BIT
);
405 glPushAttrib(GL_COLOR_BUFFER_BIT
);
409 /* Rotate the environment about the position of the ball. */
411 glTranslatef(+ball_p
[0], +ball_p
[1] * d
, +ball_p
[2]);
412 glRotatef(-game_rz
* d
, view_e
[2][0], view_e
[2][1], view_e
[2][2]);
413 glRotatef(-game_rx
* d
, view_e
[0][0], view_e
[0][1], view_e
[0][2]);
414 glTranslatef(-ball_p
[0], -ball_p
[1] * d
, -ball_p
[2]);
425 glEnable(GL_CLIP_PLANE0
);
426 glClipPlane(GL_CLIP_PLANE0
, e
);
429 /* Draw the floor. */
431 if (pose
== 0) game_set_shadow(&file
);
432 sol_draw(&file
, config_get(CONFIG_SHADOW
));
433 if (pose
== 0) game_clr_shadow();
435 /* Draw the game elements. */
438 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
442 part_draw_coin(-rx
* d
, -ry
);
443 game_draw_coins(&file
);
444 game_draw_balls(&file
);
446 game_draw_goals(&file
, -rx
* d
, -ry
);
447 game_draw_jumps(&file
);
448 game_draw_swchs(&file
);
450 glDisable(GL_CLIP_PLANE0
);
458 void game_draw(int pose
, float st
)
460 float fov
= view_fov
;
462 if (jump_b
) fov
*= 2.f
* fabsf(jump_dt
- 0.5);
464 config_push_persp(fov
, 0.1f
, FAR_DIST
);
475 /* Compute and apply the view. */
477 v_sub(v
, view_c
, view_p
);
479 rx
= V_DEG(fatan2f(-v
[1], fsqrtf(v
[0] * v
[0] + v
[2] * v
[2])));
480 ry
= V_DEG(fatan2f(+v
[0], -v
[2])) + st
;
482 glTranslatef(0.f
, 0.f
, -v_len(v
));
483 glRotatef(rx
, 1.f
, 0.f
, 0.f
);
484 glRotatef(ry
, 0.f
, 1.f
, 0.f
);
485 glTranslatef(-view_c
[0], -view_c
[1], -view_c
[2]);
487 if (config_get(CONFIG_REFLECTION
))
489 /* Draw the mirror only into the stencil buffer. */
491 glDisable(GL_DEPTH_TEST
);
492 glEnable(GL_STENCIL_TEST
);
493 glStencilFunc(GL_ALWAYS
, 1, 0xFFFFFFFF);
494 glStencilOp(GL_REPLACE
, GL_REPLACE
, GL_REPLACE
);
495 glColorMask(GL_FALSE
, GL_FALSE
, GL_FALSE
, GL_FALSE
);
499 /* Draw the scene reflected into color and depth buffers. */
501 glColorMask(GL_TRUE
, GL_TRUE
, GL_TRUE
, GL_TRUE
);
502 glStencilOp(GL_KEEP
, GL_KEEP
, GL_KEEP
);
503 glStencilFunc(GL_EQUAL
, 1, 0xFFFFFFFF);
504 glEnable(GL_DEPTH_TEST
);
509 glScalef(+1.f
, -1.f
, +1.f
);
512 game_draw_back(pose
, rx
, ry
, -1, pdn
);
513 game_draw_fore(pose
, rx
, ry
, -1, pdn
);
518 glDisable(GL_STENCIL_TEST
);
521 /* Draw the scene normally. */
524 game_refl_all(config_get(CONFIG_SHADOW
));
525 game_draw_back(pose
, rx
, ry
, +1, pup
);
526 game_draw_fore(pose
, rx
, ry
, +1, pup
);
532 /*---------------------------------------------------------------------------*/
534 static void game_update_grav(float h
[3], const float g
[3])
536 struct s_file
*fp
= &file
;
539 float y
[3] = { 0.f
, 1.f
, 0.f
};
545 /* Compute the gravity vector from the given world rotations. */
547 v_sub(z
, view_p
, fp
->uv
->p
);
553 m_rot (Z
, z
, V_RAD(game_rz
));
554 m_rot (X
, x
, V_RAD(game_rx
));
559 static void game_update_view(float dt
)
561 const float y
[3] = { 0.f
, 1.f
, 0.f
};
563 float dc
= view_dc
* (jump_b
? 2.0f
* fabsf(jump_dt
- 0.5f
) : 1.0f
);
564 float dx
= view_ry
* dt
* 5.0f
;
569 view_a
+= view_ry
* dt
* 90.f
;
571 /* Center the view about the ball. */
573 v_cpy(view_c
, file
.uv
->p
);
574 v_inv(view_v
, file
.uv
->v
);
576 switch (config_get(CONFIG_CAMERA
))
579 /* Camera 1: Viewpoint chases the ball position. */
581 v_sub(view_e
[2], view_p
, view_c
);
585 /* Camera 2: View vector is given by view angle. */
587 view_e
[2][0] = fsinf(V_RAD(view_a
));
589 view_e
[2][2] = fcosf(V_RAD(view_a
));
596 /* Default: View vector approaches the ball velocity vector. */
598 v_mad(e
, view_v
, y
, v_dot(view_v
, y
));
601 k
= v_dot(view_v
, view_v
);
603 v_sub(view_e
[2], view_p
, view_c
);
604 v_mad(view_e
[2], view_e
[2], view_v
, k
* dt
* 0.25f
);
609 /* Orthonormalize the basis of the view in its new position. */
611 v_crs(view_e
[0], view_e
[1], view_e
[2]);
612 v_crs(view_e
[2], view_e
[0], view_e
[1]);
613 v_nrm(view_e
[0], view_e
[0]);
614 v_nrm(view_e
[2], view_e
[2]);
616 /* Compute the new view position. */
618 v_cpy(view_p
, file
.uv
->p
);
619 v_mad(view_p
, view_p
, view_e
[0], dx
);
620 v_mad(view_p
, view_p
, view_e
[1], view_dp
);
621 v_mad(view_p
, view_p
, view_e
[2], view_dz
);
623 v_cpy(view_c
, file
.uv
->p
);
624 v_mad(view_c
, view_c
, view_e
[1], dc
);
626 view_a
= V_DEG(fatan2f(view_e
[2][0], view_e
[2][2]));
629 static void game_update_time(float dt
, int b
)
631 int tick
= (int) floor(clock
);
632 int tock
= (int) floor(clock
* 2);
634 /* The ticking clock. */
643 if (0 < tick
&& tick
<= 10 && tick
== (int) ceil(clock
))
645 audio_play(AUD_TICK
, 1.f
);
646 hud_time_pulse(1.50);
648 else if (0 < tock
&& tock
<= 10 && tock
== (int) ceil(clock
* 2))
650 audio_play(AUD_TOCK
, 1.f
);
651 hud_time_pulse(1.25);
656 static int game_update_state(void)
658 struct s_file
*fp
= &file
;
663 /* Test for a coin grab and a possible 1UP. */
665 if ((n
= sol_coin_test(fp
, p
, COIN_RADIUS
)) > 0)
672 /* Test for a switch. */
674 if ((swch_e
= sol_swch_test(fp
, swch_e
, 0)) != e
&& e
)
675 audio_play(AUD_SWITCH
, 1.f
);
677 /* Test for a jump. */
679 if (jump_e
== 1 && jump_b
== 0 && sol_jump_test(fp
, jump_p
, 0) == 1)
685 audio_play(AUD_JUMP
, 1.f
);
687 if (jump_e
== 0 && jump_b
== 0 && sol_jump_test(fp
, jump_p
, 0) == 0)
690 /* Test for a goal. */
692 if (sol_goal_test(fp
, p
, 0))
695 /* Test for time-out. */
700 /* Test for fall-out. */
702 if (fp
->uv
[0].p
[1] < -20.f
)
709 * On most hardware, rendering requires much more computing power than
710 * physics. Since physics takes less time than graphics, it make sense to
711 * detach the physics update time step from the graphics frame rate. By
712 * performing multiple physics updates for each graphics update, we get away
713 * with higher quality physics with little impact on overall performance.
715 * Toward this end, we establish a baseline maximum physics time step. If
716 * the measured frame time exceeds this maximum, we cut the time step in
717 * half, and do two updates. If THIS time step exceeds the maximum, we do
718 * four updates. And so on. In this way, the physics system is allowed to
719 * seek an optimal update rate independant of, yet in integral sync with, the
720 * graphics frame rate.
723 int game_step(const float g
[3], float dt
, int bt
)
725 struct s_file
*fp
= &file
;
735 /* Smooth jittery or discontinuous input. */
739 game_rx
+= (game_ix
- game_rx
) * t
/ RESPONSE
;
740 game_rz
+= (game_iz
- game_rz
) * t
/ RESPONSE
;
748 game_update_grav(h
, g
);
759 fp
->uv
[0].p
[0] = jump_p
[0];
760 fp
->uv
[0].p
[1] = jump_p
[1];
761 fp
->uv
[0].p
[2] = jump_p
[2];
770 while (t
> MAX_DT
&& n
< MAX_DN
)
776 for (i
= 0; i
< n
; i
++)
777 if (b
< (d
= sol_step(fp
, h
, t
, 0, NULL
)))
780 /* Mix the sound of a ball bounce. */
783 audio_play(AUD_BUMP
, (b
- 0.5f
) * 2.0f
);
786 game_update_view(dt
);
787 game_update_time(dt
, bt
);
789 return game_update_state();
792 /*---------------------------------------------------------------------------*/
794 void game_set_x(int k
)
796 game_ix
= -20.f
* k
/ JOY_MAX
;
799 void game_set_z(int k
)
801 game_iz
= +20.f
* k
/ JOY_MAX
;
804 void game_set_pos(int x
, int y
)
808 game_ix
+= 40.f
* y
/ config_get(CONFIG_MOUSE_SENSE
);
809 game_iz
+= 40.f
* x
/ config_get(CONFIG_MOUSE_SENSE
);
811 if (game_ix
> +bound
) game_ix
= +bound
;
812 if (game_ix
< -bound
) game_ix
= -bound
;
813 if (game_iz
> +bound
) game_iz
= +bound
;
814 if (game_iz
< -bound
) game_iz
= -bound
;
817 void game_set_rot(int r
)
822 /*---------------------------------------------------------------------------*/
824 void game_set_fly(float k
)
826 struct s_file
*fp
= &file
;
828 float x
[3] = { 1.f
, 0.f
, 0.f
};
829 float y
[3] = { 0.f
, 1.f
, 0.f
};
830 float z
[3] = { 0.f
, 0.f
, 1.f
};
831 float c0
[3] = { 0.f
, 0.f
, 0.f
};
832 float p0
[3] = { 0.f
, 0.f
, 0.f
};
833 float c1
[3] = { 0.f
, 0.f
, 0.f
};
834 float p1
[3] = { 0.f
, 0.f
, 0.f
};
841 /* k = 0.0 view is at the ball. */
845 v_cpy(c0
, fp
->uv
[0].p
);
846 v_cpy(p0
, fp
->uv
[0].p
);
849 v_mad(p0
, p0
, y
, view_dp
);
850 v_mad(p0
, p0
, z
, view_dz
);
851 v_mad(c0
, c0
, y
, view_dc
);
853 /* k = +1.0 view is s_view 0 */
855 if (k
>= 0 && fp
->wc
> 0)
857 v_cpy(p1
, fp
->wv
[0].p
);
858 v_cpy(c1
, fp
->wv
[0].q
);
861 /* k = -1.0 view is s_view 1 */
863 if (k
<= 0 && fp
->wc
> 1)
865 v_cpy(p1
, fp
->wv
[1].p
);
866 v_cpy(c1
, fp
->wv
[1].q
);
869 /* Interpolate the views. */
872 v_mad(view_p
, p0
, v
, k
* k
);
875 v_mad(view_c
, c0
, v
, k
* k
);
877 /* Orthonormalize the view basis. */
879 v_sub(view_e
[2], view_p
, view_c
);
880 v_crs(view_e
[0], view_e
[1], view_e
[2]);
881 v_crs(view_e
[2], view_e
[0], view_e
[1]);
882 v_nrm(view_e
[0], view_e
[0]);
883 v_nrm(view_e
[2], view_e
[2]);
886 void game_look(float phi
, float theta
)
888 view_c
[0] = view_p
[0] + fsinf(V_RAD(theta
)) * fcosf(V_RAD(phi
));
889 view_c
[1] = view_p
[1] + fsinf(V_RAD(phi
));
890 view_c
[2] = view_p
[2] - fcosf(V_RAD(theta
)) * fcosf(V_RAD(phi
));
893 /*---------------------------------------------------------------------------*/
895 int game_put(FILE *fout
)
897 return (float_put(fout
, &game_rx
) &&
898 float_put(fout
, &game_rz
) &&
899 vector_put(fout
, view_c
) &&
900 vector_put(fout
, view_p
) &&
901 sol_put(fout
, &file
));
904 int game_get(FILE *fin
)
906 return (float_get(fin
, &game_rx
) &&
907 float_get(fin
, &game_rz
) &&
908 vector_get(fin
, view_c
) &&
909 vector_get(fin
, view_p
) &&
910 sol_get(fin
, &file
));
913 /*---------------------------------------------------------------------------*/