简体   繁体   中英

A ball animation in simple harmonic motion using SDL2 and C++

I'm trying to emulate the following ball. Notice the simple harmonic motion of the ball, with the very ends of the ball bounce having a smaller velocity compared to the velocity in the middle:

在此处输入图像描述

I'm able to implement a bouncing ball, however it's not simple harmonic motion:

在此处输入图像描述

The corresponding code is as follows:

Dot::Dot() {
//Initialize the offsets
mPosX = 300;
mPosY = 0;

//Initialize the velocity
mVelX = 0;
mVelY = 4;
}

void Dot::move() {

//Move the dot up or down
mPosY += mVelY;

//If the dot went too far up or down
if( ( mPosY < 0 ) || ( mPosY + DOT_HEIGHT > SCREEN_HEIGHT ) )
{
    //Move back
    mVelY = -mVelY;
    }
}

I have a simple harmonic motion model, like so:

在此处输入图像描述

The corresponding code is as follows:

Dot::Dot() {
//Initialize the offsets
mPosX = 300;
mPosY = 0;

//Initialize the velocity
mVelX = 0;
mVelY = 0;
}

void Dot::move() {
time_t current_time;
current_time = time(NULL);

mPosY = int(((460) - 10) * sin(2.4 * 2 * 3.141592 / 60 * current_time + (SCREEN_HEIGHT / 2) 
));

//const int SCREEN_HEIGHT = 480 
}

The issues with this implementation are that:

(1). the ball image appears every now and then, rather than continuously like in the blue ball model I tried to emulate at the very beginning

(2). the ball goes well beyond the top frame of the window, rather than slowing down at the very top of the window, again like the blue ball model.

For (2), I understand that I need to add a phase shift, ie x in A*sin(wt + x), however changing this value doesn't do anything to prevent the ball from disappearing at the top of the window.

Any ideas on how to solve these issues?

Edit: I was able to solve (1) by doing += to mPosY rather than =, such as:

mPosY += int(4 * cos(2.4 * 2 * 3.141592 / 60 * current_time + (SCREEN_HEIGHT / 2) ));

However, I'm still unable to get the ball to bounce up and down within the frame of the window I created.

I recommend using actual simple harmonic equations. For example, if your display dimensions are (500, 500), the center Y is 250. from there say your equation is in the form of x = a cos(n t + m) + c where x is displacement (meters), a is amplitude n is for the period, for example the period (T) = 2PI/nt is time (seconds) and m is for phase shift and c is for the center. That way when you need the velocity of the object, you have a function that follows along the lines of

double Velocity(double time){
   double vel = derivative_of_displacement_equation(time);
   return vel;
}

And so in the program, you adjust the equation to suit the display dimensions, then you set the objects X/Y coordinates as the value returned from the displacement equation (PLUS THE CENTER OFFSET, in this example, if the center is at the middle of the screen, you would set the Y coordinate to the equation PLUS 250). Keep in mind coordinates begin at (0,0) so your displacement equation (at least the part where it involves the proportional factor, which in this case is time), you make that negative instead.

Here is some code that I believe answers your question:

#include <SDL2/SDL.h>
#include <chrono>
#include <math.h>
#include <iostream>

const double PI = 3.14159265358979;

void draw_circle(SDL_Renderer *renderer, int x, int y, int radius, SDL_Color color)
{
    SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
    for (int w = 0; w < radius * 2; w++)
    {
        for (int h = 0; h < radius * 2; h++)
        {
            int dx = radius - w; // horizontal offset
            int dy = radius - h; // vertical offset
            if ((dx*dx + dy*dy) <= (radius * radius))
            {
                SDL_RenderDrawPoint(renderer, x + dx, y + dy);
            }
        }
    }
}

double Displacement(double time, double a, double n, double m, double c)
{
    double displacement = a*cos(n*time + m) + c;
    return displacement;
}

int main(int argc, char* argv[])
{
    SDL_Init(SDL_INIT_VIDEO);
    SDL_Window *window = SDL_CreateWindow("SHM", 0, 30, 500, 500, SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE);// | SDL_WINDOW_SHOWN);
    SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED );

    double timeDifference;
    std::chrono::steady_clock::time_point start, finish;
    start = std::chrono::steady_clock::now();
    finish = start;

    SDL_Event event;
    bool running = true;

    while (running){
        while (SDL_PollEvent(&event)){
            if (event.type == SDL_QUIT){
                running = false;
                break;
            }
        }

        SDL_SetRenderDrawColor(renderer, 255,255,255,255);
        SDL_RenderClear(renderer);

        finish = std::chrono::steady_clock::now();

        timeDifference = std::chrono::duration_cast<std::chrono::nanoseconds>(finish - start).count();
        timeDifference = timeDifference / 1000000000;

        ///The "-(250-20) is the center y (250) minus the radius of the circle (20), and its - out the front as negative a due to coordinates
        double yPosition = round( Displacement(timeDifference, -(250-20), 2, 0, 250 )  );
        draw_circle(renderer, 250, yPosition, 20, {255,0,0});

        SDL_RenderPresent(renderer);
    }

    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);

    SDL_Quit();

    return 0;
}

In general you have a0 + a/2*cos (2**t/T + ) where a0 is the vertical position of the half of the vertical travel, a is the height of the travel, t is time, T the period ie., the time to do a complete cycle for going and coming back to the same state or uple { position, momentum }, and the time shift, ie., the moment where the height is at zero of the cos.

So if you want the ball to be on the floor at t=0 , you want cos at the minimum, ie., = -/2 .

You want to manage your position in function of your game's time t , so you can decouple the time to compute (which depend on your compute calpabilities) and the game's time (that you want constant from a machine to another).

Therefore you want:

auto VerticalPosition(double t)
-> double { return CorrectedScreenHeight/2*(1 + cos(2*PI*t/T + phi)); }

And you define CorrectedScreenHeight = SCREEN_HEIGHT - DOT_HEIGHT , T and phi outside, as properties of your system.

Between two consecutive images, you increment t , in order to have the correct experienced time. Typically you have 60 images/s (WPF, DirectX, web, etc), hence a period of 1.0/60s between consecutive images, this goes in your function that modifies t . The speed of your ball then depend on T , that you can tune independently.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM