cpp-cheat/sdl/animation_user_control_inertia.c
2016-11-02 07:06:34 +00:00

224 lines
7.1 KiB
C

/*Some basic inertia / friction on a single body.
Gives a strong sense of realism.
Makes you want to play around with two or more bodies and collision detection,
but that can only lead to a time consuming broken re-implementation of a subset
of Box2D... right?
*/
#include "common.h"
#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT WINDOW_WIDTH
#define WINDOW_CENTER_X (WINDOW_WIDTH / 2)
#define WINDOW_CENTER_Y (WINDOW_HEIGHT / 2)
#define WINDOW_HEIGHT WINDOW_WIDTH
#define RECT_WIDTH (WINDOW_WIDTH / 50)
#define RECT_HEIGHT RECT_WIDTH
#define MAX_RECT_POS (WINDOW_WIDTH - RECT_WIDTH)
/*
* Accelerations in meters per second.
*
* I like to think like this: how long will it take to cross the screen
* starting from speed 0 with this constant acceleration?
*
* Remember that:
*
* x = acc * t^2 / 2
*
* so if:
*
* - `acc = MAX_RECT_POS * 2.0` it takes 1 second to cross the screen
* - `acc = MAX_RECT_POS` it takes sqrt(2) seconds to cross the screen
* - `acc = MAX_RECT_POS / 2.0` it takes 2 seconds to cross the screen
**/
#define ACC_USER_METERS_PER_SECOND (MAX_RECT_POS)
#define ACC_FRICTION_METERS_PER_SECOND (MAX_RECT_POS / 2.5)
#define ACC_GRAVITY_METERS_PER_SECOND (MAX_RECT_POS / 1.5)
typedef struct {
double
x,
y,
speed_x,
speed_y
;
} PlayerState;
void init_state(
PlayerState *player_state,
double *user_acc_x,
double *user_acc_y,
unsigned int *last_time
) {
player_state->x = WINDOW_WIDTH / 2.0;
player_state->y = WINDOW_HEIGHT / 2.0;
player_state->speed_x = 0.0;
player_state->speed_y = 0.0;
/* Encode if the movement keys are initially pressed or not. */
*user_acc_x = 0.0;
*user_acc_y = 0.0;
*last_time = SDL_GetTicks();
common_fps_init();
}
double get_new_coord(double x, double step) {
x += step;
x = fmod(x, MAX_RECT_POS);
if (x < 0)
x = MAX_RECT_POS;
return x;
}
/* A constant force in the direction opposite to movement,
* or stop the movement if it is too slow.
* */
double apply_constant_friction(double speed, double dt) {
double dv;
dv = ACC_FRICTION_METERS_PER_SECOND * dt;
if (fabs(speed) < dv)
speed = 0.0;
else
speed -= copysign(dv, speed);
return speed;
}
int main(void) {
PlayerState player_state;
SDL_Event event;
SDL_Rect rect, rect_sun;
SDL_Renderer *renderer;
SDL_Window *window;
int quit = 0;
unsigned int current_time, last_time;
double dt, acc_x, acc_y, user_acc_x, user_acc_y;
SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO);
SDL_CreateWindowAndRenderer(WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer);
SDL_SetWindowTitle(window, "arrow keys: accelerate | esc: reset | q: quit");
rect.w = RECT_WIDTH;
rect.h = RECT_HEIGHT;
rect_sun.w = RECT_WIDTH;
rect_sun.h = RECT_HEIGHT;
rect_sun.x = WINDOW_CENTER_X;
rect_sun.y = WINDOW_CENTER_Y;
main_loop:
init_state(&player_state, &user_acc_x, &user_acc_y, &last_time);
while (!quit) {
while (SDL_PollEvent(&event) == 1) {
if (event.type == SDL_QUIT) {
quit = 1;
} else if (event.type == SDL_KEYDOWN) {
switch (event.key.keysym.sym) {
case SDLK_LEFT:
user_acc_x = -1;
break;
case SDLK_RIGHT:
user_acc_x = 1;
break;
case SDLK_UP:
user_acc_y = -1;
break;
case SDLK_DOWN:
user_acc_y = 1;
break;
case SDLK_ESCAPE:
goto main_loop;
case SDLK_q:
goto quit;
}
} else if (event.type == SDL_KEYUP) {
switch (event.key.keysym.sym) {
case SDLK_LEFT:
case SDLK_RIGHT:
user_acc_x = 0;
break;
case SDLK_UP:
case SDLK_DOWN:
user_acc_y = 0;
break;
}
}
}
current_time = SDL_GetTicks();
if (current_time != last_time) {
dt = (current_time - last_time) / 1000.0;
/* Player movement. */
acc_x = user_acc_x * ACC_USER_METERS_PER_SECOND;
acc_y = user_acc_y * ACC_USER_METERS_PER_SECOND;
/* Other forces. */
{
/* Constant gravity down. */
/*acc_y += ACC_GRAVITY_METERS_PER_SECOND;*/
/* Constant gravity to center of screen, the "sun". */
{
double dx = player_state.x - WINDOW_CENTER_X;
double dy = player_state.y - WINDOW_CENTER_Y;
double angle = atan2(dy, dx);
double hyp = hypot(dx, dy);
/* TODO Failed attempt at force proportional to inverse square of distance. */
/*double mod = hyp > 0.001 ? hyp : 1.0;*/
/*double mod2 = mod * mod;*/
acc_x -= cos(angle) * ACC_GRAVITY_METERS_PER_SECOND;
acc_y -= sin(angle) * ACC_GRAVITY_METERS_PER_SECOND;
}
}
/* Update speed. */
player_state.speed_x += dt * acc_x;
player_state.speed_y += dt * acc_y;
/*
Constant friction
- decelerates very quickly
- speed is limited to a maximum
Ideally this force should be added to the acceleration simply,
but we must consider the case where the object stops completely
because of it separately.
*/
/*player_state.speed_x = apply_constant_friction(player_state.speed_x, dt);*/
/*player_state.speed_y = apply_constant_friction(player_state.speed_y, dt);*/
/* Friction proportional to speed:
- decelerates very quickly
- speed is limited to a maximum
Does not feel very natural.
*/
/* player_state.speed_x *= 0.999; */
/* player_state.speed_y *= 0.999; */
/* Update position. */
player_state.x = get_new_coord(player_state.x, player_state.speed_x * dt);
player_state.y = get_new_coord(player_state.y, player_state.speed_y * dt);
/* Render. */
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
SDL_RenderClear(renderer);
SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
rect.x = player_state.x;
rect.y = player_state.y;
SDL_RenderFillRect(renderer, &rect);
SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);
SDL_RenderFillRect(renderer, &rect_sun);
SDL_RenderPresent(renderer);
/* Update time. */
last_time = current_time;
}
common_fps_update_and_print();
}
quit:
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return EXIT_SUCCESS;
}