C++ 保持窗口中的力驱动实体

问题描述

我有一个宽度为 1024、高度为 768 的窗口应用程序,其中包含一堆陨石、船只和船只。陨石在力的驱使下自由地穿过窗户。 这些力是:随机位置、朝向/远离船只、朝向/远离船只和凝聚力、分离、与其他陨石对齐

我觉得这些力并没有完全发挥作用,因为它们有时会离开屏幕并以相反的速度移动,例如:它们从右上直游到左上,到达时直接到左下。

我的计算是正确的还是我在力量上搞砸了什么?

陨石标题

#include <chrono>
#include <cmath>
#include <array>
#include <random>
#include <algorithm>

using scalar = float;

template <typename Scalar> class basic_vector2d {
public:
    constexpr basic_vector2d() noexcept = default;
    constexpr basic_vector2d(Scalar x,Scalar y) noexcept : x_{ x },y_{ y } {}
    constexpr Scalar x() const noexcept { return x_; }
    constexpr void x(Scalar newX) noexcept { x_ = newX; }
    constexpr Scalar y() const noexcept { return y_; }
    constexpr void y(Scalar newY) noexcept { y_ = newY; }

    constexpr bool operator==(basic_vector2d other) const noexcept {
        return x_ == other.x_ && y_ == other.y_;
    }

    constexpr bool operator!=(basic_vector2d other) const noexcept {
        return x_ != other.x_ || y_ != other.y_;
    }

    constexpr basic_vector2d& operator+=(basic_vector2d other) noexcept {
        x_ += other.x_;
        y_ += other.y_;
        return *this;
    }

    constexpr basic_vector2d& operator-=(basic_vector2d other) noexcept {
        x_ -= other.x_;
        y_ -= other.y_;
        return *this;
    }

    constexpr basic_vector2d& operator*=(Scalar s) noexcept {
        x_ *= s;
        y_ *= s;
        return *this;
    }

    constexpr basic_vector2d& operator/=(Scalar s) noexcept {
        x_ /= s;
        y_ /= s;
        return *this;
    }

private:
    Scalar x_{};
    Scalar y_{};
};

template <typename Scalar>
constexpr basic_vector2d<Scalar> operator-(basic_vector2d<Scalar> a,basic_vector2d<Scalar> b) {
    return { a.x() - b.x(),a.y() - b.y() };
}

template <typename Scalar>
constexpr basic_vector2d<Scalar> operator+(basic_vector2d<Scalar> a,basic_vector2d<Scalar> b) {
    return { a.x() + b.x(),a.y() + b.y() };
}

template <typename Scalar>
constexpr basic_vector2d<Scalar> operator*(basic_vector2d<Scalar> v,scalar s) {
    return v *= s;
}

template <typename Scalar>
constexpr basic_vector2d<Scalar> operator*(scalar s,basic_vector2d<Scalar> v) {
    return operator*(v,s);
}

template <typename Scalar>
constexpr basic_vector2d<Scalar> operator/(basic_vector2d<Scalar> v,scalar s) {
    return v /= s;
}

template <typename Scalar>
constexpr basic_vector2d<Scalar> operator/(scalar s,basic_vector2d<Scalar> v) {
    return operator/(v,s);
}

template <typename Scalar>
constexpr scalar dot(basic_vector2d<Scalar> a,basic_vector2d<Scalar> b) {
    return a.x() * b.x() + a.y() * b.y();
}

template <typename Scalar> constexpr auto norm(basic_vector2d<Scalar> p) {
    return std::sqrt(dot(p,p));
}

template <typename Scalar>
constexpr basic_vector2d<Scalar> normalize(basic_vector2d<Scalar> p) {
    auto ls = norm(p);
    return { p.x() / ls,p.y() / ls };
}

using vector2d = basic_vector2d<scalar>;

template <typename T> class basic_size {
public:
    constexpr basic_size() noexcept = default;
    constexpr basic_size(T width,T height) noexcept
        : width_{ width },height_{ height } {}
    constexpr T width() const noexcept { return width_; }
    constexpr T height() const noexcept { return height_; }
    constexpr void width(T new_width) noexcept { width_ = new_width; }
    constexpr void height(T new_height) noexcept { height_ = new_height; }
    constexpr basic_size& operator*=(T x) {
        width(width() * x);
        height(height() * x);
        return *this;
    }

private:
    T width_{};
    T height_{};
};

using size = basic_size<scalar>;

template <typename Scalar> class basic_rectangle {
public:
    constexpr basic_rectangle(basic_vector2d<Scalar> top_left,basic_size<Scalar> size)
        : top_left_{ top_left },size_{ size } {}
    constexpr basic_vector2d<Scalar> const& top_left() const noexcept {
        return top_left_;
    }
    constexpr basic_size<Scalar> const& size() const noexcept { return size_; }

private:
    basic_vector2d<Scalar> top_left_;
    basic_size<Scalar> size_;
};
using rectangle = basic_rectangle<scalar>;

inline float to_seconds(std::chrono::nanoseconds dt) {
    return std::chrono::duration_cast<std::chrono::duration<float>>(dt).count();
}

class meteorite {
public:
    meteorite(int id,vector2d location);
    int id;
    /*!
   * Called every tick
   * \param dt the time that has passed since the prevIoUs tick
   */
    void act(std::chrono::nanoseconds dt);
    vector2d location() const { return location_; }
    std::vector<vector2d> random_meteorite_locations(std::size_t n);
private:
    vector2d veLocity;
    scalar max_veLocity;
    vector2d location_;

    vector2d acceleration;

    void location(vector2d loc) { location_ = loc; }   
    void random_position_force();
    void screen_force(std::chrono::nanoseconds dt);
    void move(std::chrono::nanoseconds dt);
    void add_force(vector2d force);
    void island_avoidance();
};

陨石来源:

#include "meteorite.h"

meteorite::meteorite(int id,vector2d location) : id(id),veLocity{ 0,0 },max_veLocity(0.15),acceleration{ 0,location_(location) {}

void meteorite::act(std::chrono::nanoseconds dt) {
    move(dt);
}

void meteorite::move(std::chrono::nanoseconds dt) {
    this->location(this->location() + veLocity);

    random_position_force();
    screen_force(dt);

    this->veLocity += this->acceleration * to_seconds(dt);

    // prevent veLocity from exceeding max_veLocity
    float veLocity_length = std::sqrt((this->veLocity.x() * this->veLocity.x()) + (this->veLocity.y() * this->veLocity.y()));
    if (veLocity_length >= this->max_veLocity) {
        this->veLocity = normalize(this->veLocity) * this->max_veLocity;
    }

    /*directions:
        * y -1 up
        * y 1 down
        *
        * x 1 right
        * x -1 left
        */

        // reset acceleration to 0 for the next set of forces to be applied
    this->acceleration = vector2d(0,0);
}

// add force propeling meteorite to a random position
void meteorite::random_position_force() {
    float x = (rand() % 100 - 50);
    float y = (rand() % 100 - 50);
    add_force(this->veLocity + vector2d((x / 5),(y / 5)));
}

void meteorite::add_force(vector2d force) {
    this->acceleration += force;
}

void meteorite::screen_force(std::chrono::nanoseconds dt)
{
    auto new_position = this->location() + (this->veLocity + (this->acceleration * to_seconds(dt)));
    auto height = 1068 - 32;
    auto width = 724 - 32;

    if (new_position.x() <= 32) {
        vector2d screen_vector = vector2d(0,0);

        if (this->acceleration.x() < 0)
        {
            screen_vector = vector2d(-this->acceleration.x() * 2,0);
        }

        add_force(screen_vector);
    }
    else if (new_position.x() >= width)
    {
        vector2d screen_vector = vector2d(0,0);

        if (this->acceleration.x() > 0)
        {
            screen_vector = vector2d(-this->acceleration.x() * 2,0);
        }

        add_force(screen_vector);
    }

    if (new_position.y() <= 32) {
        vector2d screen_vector = vector2d(0,0);

        if (this->acceleration.y() < 0)
        {
            screen_vector = vector2d(0,-this->acceleration.y() * 2);
        }

        add_force(screen_vector);
    }
    else if (new_position.y() >= height)
    {
        vector2d screen_vector = vector2d(0,0);

        if (this->acceleration.y() > 0)
        {
            screen_vector = vector2d(0,-this->acceleration.y() * 2);
        }

        add_force(screen_vector);
    }
}

std::vector<vector2d> meteorite::random_meteorite_locations(std::size_t n) {
    // from 0x2 to 13x17  = 195
    // from 13x0 to 28x9  = 135
    // from 20x9 to 32x19 = 120
    // from 6x17 to 25x24 = 133
    // sum                = 583
    std::random_device rd{};
    std::default_random_engine re{ rd() };
    std::uniform_int_distribution<> id{ 0,583 };
    std::uniform_real_distribution<scalar> sd{ 0,1 };

    auto rv = [&](rectangle const& r) {
        return r.top_left() + vector2d{ r.size().width() * sd(re),r.size().height() * sd(re) };
    };

    std::array<rectangle,4> rects{
        rectangle{vector2d{0.1f,2},size{13,15}},rectangle{vector2d{13.f,0.1f},size{15,9}},rectangle{vector2d{20,9},size{12,10}},rectangle{vector2d{6,17},size{17,6}} };
    auto to_index = [](int i) -> std::size_t {
        if (i < 195)
            return 0;
        else if (i < 330)
            return 1;
        else if (i < 450)
            return 2;
        else
            return 3;
    };

    std::vector<vector2d> result(n);
    std::generate_n(result.begin(),result.size(),[&] {
        auto val = id(re);
        auto index = to_index(val);
        auto rect = rects[index];
        return 32 * rv(rect);
        });
    return result;
}

Main.cpp

#include <iostream>
#include "meteorite.h"

int main()
{
    meteorite m = meteorite{ 0,{} };
    std::vector<meteorite*> meteorites;
    std::vector<vector2d> locations = m.random_meteorite_locations(1);
    int i = 1;
    
    for (auto& loc : locations) {
        meteorites.push_back(new meteorite(i,loc));
    }

    auto t_prev = std::chrono::high_resolution_clock::Now();

    while (true) {
        auto t_current = std::chrono::high_resolution_clock::Now();
        std::chrono::nanoseconds dt = std::chrono::nanoseconds(200);
        t_prev = t_current;
       
        for (auto& m : meteorites) {
            m->act(dt);
            std::cout << m->location().x() << " " << m->location().y() << "\n";
        }
    }

    for (auto& m : meteorites) {
        delete m;
    }
}

解决方法

您在 move() 和 screen_force() 这两个地方都错误地计算了新位置。您正在执行 s = s0 + (v + a * t),但您应该执行 s = s0 + v * t + (a * t^2) / 2

这是一个工作示例: http://cpp.sh/9uu3w