• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Program liczący parabole lotu Kulki.

VPS Starter Arubacloud
+1 głos
350 wizyt
pytanie zadane 12 kwietnia 2017 w Matematyka, fizyka, logika przez Knayder Nałogowiec (37,640 p.)

Witam, chciałem napisać program który obliczy mi parabole (A dokładnie odległość na jaką poleci) lotu.
W tym celu stworzyłem prosty program, wyświetlający wszystko w oknie.

Jednak, jest pewna niedokładność w obliczeniach programu.
https://pastebin.com/ct3vty36
Nie musicie analizować całego kodu. Podam tutaj "newralgiczne" momenty tegoż kodu.

Zmienna GRAVITY, trzyma w sobie wartość grawitacji, która jest dodawana do ruchu kulki w każdej klatce.


Dodałem do kodu funkcjonalność, która wyświetla najkrótszą drogę między zwykłą kulką, a tą pokazująca gdzie poleci w czasie lotu. Różnica ta, waha się od 2-5 pikseli. (Co jest widoczne, kulka ma 5 pikseli promienia).

Obliczanie pozycji x kulki która ma nam pokazywać gdzie poleci normalna kulka.

        sf::Vector2f deltaMouse = normalizeVector(getDeltaPosition({ 5,650 }, (sf::Vector2f)sf::Mouse::getPosition(window)), 7);
        float vectorLength = getVectorLength(deltaMouse);
        if (isBallFlying == false && releaseBall == false) {
            float x =
                vectorLength * vectorLength * sin(
                    2 * std::asin( std::abs(deltaMouse.y)/vectorLength )
                )
                / GRAVITY;
           
 
            predictBall.setPosition(sf::Vector2f(x, 650));
            //std::cout << x << std::endl;
        }

No i te trzy używane przeze mnie funkcje:
 

float getVectorLength(const sf::Vector2f &vector) {
	return sqrt(vector.x*vector.x + vector.y*vector.y);
}

sf::Vector2f normalizeVector(const sf::Vector2f &vector, const float &scale = 1.f) {
	return (vector / getVectorLength(vector) ) * scale;
}

sf::Vector2f getDeltaPosition(const sf::Vector2f &startPosition, const sf::Vector2f &endPosition) {
	return sf::Vector2f( endPosition - startPosition );
}

Wzór na obliczanie X znajduje się na tej stronie:

https://pl.wikipedia.org/wiki/Krzywa_balistyczna
Pozdrawiam :)

EDIT: Zapomniałem napisać jakie mam pytanie:
Z czego wynika ta niedoskonałość w obliczeniach. Ze wzoru, czy ja coś źle zrobiłem?

komentarz 12 kwietnia 2017 przez obl Maniak (51,280 p.)

No nie wiem, wzór który podałeś jest oczywiście poprawny, ale zastanawia mnie to:

            position += velocity;
            velocity.y += GRAVITY;

Do pozycji wyrażonej np w px dodajesz prędkość w px/s? Widzisz tutaj jakąś nieścisłość? No i później to samo z grawitację do składowej y prędkości dodajesz grawitację, ale prędkość jest wyrażana w px/s a grawitacja w px/s^2.

Ta metoda obliczania kolejnych pozycji jest metodą bardzo przybliżoną, gdzie główną rolę powinien odgrywać taki czynnik jak przedział czasu delta t, po którym obliczasz kolejne przemieszczanie. U ciebie tego nie ma więc to wychodzi tak jakbyś założył na sztywno delta t = 1s. Im mniejsze delta t tym dokładniej będziesz liczył, ale zawsze to będzie metoda symulacyjna, która będzie odchodziła od rzeczywistego wyniku.

komentarz 12 kwietnia 2017 przez Knayder Nałogowiec (37,640 p.)
Czyli powinienem ustalać długość jednego ticku, (deltaTime) i powinienem używać tej deltyTime w obliczeniach tak?
komentarz 12 kwietnia 2017 przez obl Maniak (51,280 p.)
Jeżeli chcesz aby to było realizowane w czasie rzeczywistym to tak. I podstawowe wzory:

V=a*t

S=V*t
komentarz 12 kwietnia 2017 przez niezalogowany

W przypadku gdy używasz setFrameLimit krok czasowy możesz obliczyć tak:

// Gdzieś powyżej pętli głównej programu:
Clock clock;

// W pętli 
double dt = clock.restart().asSeconds();

PS. funkcje statyczne typu isKeyPressed, isButtonPressed powinny być używane poza pętlą eventów.

komentarz 12 kwietnia 2017 przez Knayder Nałogowiec (37,640 p.)

Najwyraźniej nie do końca wszystko rozumiem.
Pobieram delteTime takim sposobem (Pod windows.display(), czyli na końcu pętli while)
 

window.display();
deltaTime = clock.restart().asSeconds();

Następnie dodałem do już istniejących linijek to o czym wspomniałeś (Chyba że źle to zrozumiałem)
 

if (isBallFlying) {
    position += velocity*deltaTime;
    velocity.y += GRAVITY*deltaTime;

Jednak nawet po tym jest rozstrzał dokładności:

1 odpowiedź

+2 głosów
odpowiedź 12 kwietnia 2017 przez niezalogowany
wybrane 14 kwietnia 2017 przez Knayder
 
Najlepsza

Trochę ogołociłem Twój kod do minimum, aby skupić się na sednie problemu:

#include <SFML/Graphics.hpp>
#include <iostream>
#include <cmath>

float vectorLength( const sf::Vector2f& vector)
{
    return sqrt( vector.x*vector.x + vector.y*vector.y );
}

int main()
{
    sf::RenderWindow window( sf::VideoMode( 1280, 720) , "Rzut ukosny");
    sf::Clock clock;
    sf::Event event;

    sf::Vector2f position(5, 650);

    float g = 100;
    bool releaseBall = false;
    bool isBallFlying = false;

    sf::RectangleShape rect({ 1280, 5 });
    rect.setFillColor(sf::Color(200, 100, 100, 100));
    rect.setPosition(0, 650);

    sf::CircleShape ball(5);
    ball.setOrigin(5, 5);
    ball.setPosition(position);
    ball.setFillColor(sf::Color::Red);

    sf::CircleShape predictBall(5);
    predictBall.setOrigin(5, 5);
    predictBall.setPosition(position);

    sf::Vector2f v; // aktualna predkosc
    sf::Vector2f velocity(200, -200); // poczatkowa predkosc


    while( window.isOpen() )
    {
        while( window.pollEvent(event) )
        {
            if( event.type == sf::Event::Closed )
                window.close();
            if( event.type == sf::Event::MouseButtonPressed )
            {
                if( event.mouseButton.button == sf::Mouse::Left)
                {
                    if( releaseBall == false && isBallFlying == false )
                        releaseBall = true;
                }
            }
        }

        sf::Time deltaTime = sf::Time::Zero;
        while( deltaTime < sf::seconds(1.0/20.0) ) // Pętla stałokrokowa ustawiajaca krok czasowy na 1/60s
        {
            deltaTime += clock.restart();
        }
        double dt = deltaTime.asSeconds();

        if (isBallFlying == false && releaseBall == false)
        {
            float vLength = vectorLength(velocity);
            float xmax =
                vLength * vLength * sin(
                    2 * std::asin( std::abs(velocity.y)/vLength )
                ) / g;


            predictBall.setPosition(sf::Vector2f(5+xmax, 650));
        }

        if (releaseBall)
        {
            v = velocity;
            isBallFlying = true;
            releaseBall = false;
        }

        if (isBallFlying)
        {

            v.y += g*dt;
            sf::Vector2f ds;
            ds.x = v.x*dt;
            ds.y = v.y*dt;

            ball.move(ds);

            if (ball.getPosition().y > 650)
            {
                //ball.setPosition( {0,650} );
                isBallFlying = false;
            }

        }

        window.clear(sf::Color(37,37,48));
        window.draw(rect);
        window.draw(predictBall);
        window.draw(ball);
        window.display();
    }

    return false;
}

Do obliczania właściwych położeń potrzebna jest znajomość kroku czasowego. Tak jak powiedział użytkownik obl im mniejszy krok czasowy tym większa dokładność. Metoda setFrameLimit jest bardzo niedokładna - czas powinno się kontrolować za pomocą odpowiedniej pętli stałokrokowej (którą zamieściłem w powyższym kodzie). Specjalnie dałem aktualizację co 1/20 s. Zwiększaj Zmniejszaj ją stopniowo i zauważ, że pozycja kulki jest coraz bliższa wartości oczekiwanej zasięgu. Mniejszy krok czasowy daje większą dokładność. Jeszcze lepiej można to rozwiązać tak, że rysować grafikę co 1/60s, a fizykę aktualizować co np 1/120s.

komentarz 12 kwietnia 2017 przez obl Maniak (51,280 p.)
Dzięki miałem jutro do tego zajrzeć, ale żeś mnie uprzedził więc ci plusa daję.
komentarz 13 kwietnia 2017 przez Knayder Nałogowiec (37,640 p.)

Dzięki bardzo. Postarałem się podziałać w stronę rad które mi udzieliłeś i napisałem lepszy kod który robi to samo.
https://github.com/Knayder/Parabola-Calculator/tree/master/src

Spróbowałem oddzielić od siebie update grafiki i symulacji.
Zrobiłem to w następujący sposób (wątki coś mi okno psuł):
 

void ParabolaCalculator::run() {
	while ( window.isOpen() ) {

		float deltaTime = displayClock.getElapsedTime().asSeconds();

		if (deltaTime >= 1.f / MAX_DISPLAY_FPS)//MAX_DISPLAY_FPS - 40
			display(deltaTime);

		deltaTime = simulationClock.getElapsedTime().asSeconds();

		if (deltaTime >= 1.f / MAX_SIMULATION_FPS)//MAX_SIMULATION_FPS - 240
			simulate(deltaTime);
		

	}
}

Te dwie funkcje, obsługują grafike i symulację, jednak ta od symulacji, wywoływana jest (MAX_SIMULATOR_FPS / MAX_DISPLAY_FPS) razy częściej.
np. 240/40 = 6.

void ParabolaCalculator::display(float deltaTime) {
	input();//Obsługa zdarzen
	window.clear(backgroundColor);

	window.draw(floor);
	window.draw(ball);
	window.draw(predictBall);

	window.display();
	displayClock.restart();
}

void ParabolaCalculator::simulate(float deltaTime) {
	ball.addToVelocity({ 0, GRAVITY * deltaTime });
	//Dzieki temu ifowi, kulka się zatrzyma (W zalozeniu)
	if(ball.getPosition().y < ball.getStartPosition().y || ball.getPosition().x <= ball.getStartPosition().x)
		ball.move(deltaTime);

	simulationClock.restart();
}
void Ball::move(const float &deltaTime){ sf::CircleShape::move(velocity*deltaTime);}


Pojawił się jednak problem. Ruch ten, lekko skacze co chwile, nie wiem czym może być to spowodowane :\

No i pytanie właściwe. Czy wszystko dobrze jest liczone?
Pozdrawiam i przepraszam za kłopot. :\

Podobne pytania

0 głosów
2 odpowiedzi 970 wizyt
pytanie zadane 30 grudnia 2016 w Matematyka, fizyka, logika przez gujanczyk Obywatel (1,680 p.)
0 głosów
2 odpowiedzi 2,746 wizyt
pytanie zadane 26 czerwca 2017 w Python przez DODO Bywalec (2,950 p.)
0 głosów
1 odpowiedź 180 wizyt

92,453 zapytań

141,262 odpowiedzi

319,088 komentarzy

61,854 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Akademia Sekuraka 2024 zapewnia dostęp do minimum 15 szkoleń online z bezpieczeństwa IT oraz dostęp także do materiałów z edycji Sekurak Academy z roku 2023!

Przy zakupie możecie skorzystać z kodu: pasja-akademia - użyjcie go w koszyku, a uzyskacie rabat -30% na bilety w wersji "Standard"! Więcej informacji na temat akademii 2024 znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...