Anonymous
Как получить стабильные коллизии с помощью интеграции Verlet?
Сообщение
Anonymous » 01 июл 2024, 13:01
Я некоторое время работал над этой простой физической системой, и изначально я начал с эйлеровой физики, но мне не удалось добиться стабильной работы столкновений, и после перехода на интеграцию верлетов я все еще не могу получить ничего стабильного. Это работает совершенно нормально, когда шаров недостаточно, чтобы их можно было сложить друг на друга, но как только я добавляю больше шаров, и они приземляются друг на друга, они начинают отлетать, и это хаотично. вы можете увидеть полный код и только конкретную функцию столкновения ниже, я использую C++ и sfml.
Код: Выделить всё
// Collision check
void checkCollisions(std::vector& balls, std::vector& grid, int gridWidth, int gridHeight, int cellSize) {
const float damping = 0.8f;
const float epsilon = 0.001f;
const float separationFactor = 1.01f; // Slight increase in separation
const int collisionIterations = 5; // Number of iterations for collision resolution
int cellX = static_cast(position.x) / cellSize;
int cellY = static_cast(position.y) / cellSize;
for (int iteration = 0; iteration < collisionIterations; ++iteration) {
// Check neighboring cells for potential collisions
for (int x = std::max(0, cellX - 1); x position.x;
float dy = position.y - otherBall->position.y;
float distance = std::sqrt(dx * dx + dy * dy);
if (distance < minDist && distance > epsilon) {
float nx = dx / distance;
float ny = dy / distance;
// Calculate relative velocity
float relativeVelocityX = (position.x - oldPosition.x) - (otherBall->position.x - otherBall->oldPosition.x);
float relativeVelocityY = (position.y - oldPosition.y) - (otherBall->position.y - otherBall->oldPosition.y);
float relativeVelocityDotNormal = relativeVelocityX * nx + relativeVelocityY * ny;
// Only proceed with collision resolution if balls are moving towards each other
if (relativeVelocityDotNormal < 0) {
float overlap = minDist - distance;
float moveDistance = overlap / 2.0f * separationFactor; // Slight increase in separation
// Move balls apart along the collision normal
position.x += nx * moveDistance;
position.y += ny * moveDistance;
otherBall->position.x -= nx * moveDistance;
otherBall->position.y -= ny * moveDistance;
// Calculate velocities
float v1x = position.x - oldPosition.x;
float v1y = position.y - oldPosition.y;
float v2x = otherBall->position.x - otherBall->oldPosition.x;
float v2y = otherBall->position.y - otherBall->oldPosition.y;
// Calculate normal velocity
float vn = (v2x - v1x) * nx + (v2y - v1y) * ny;
// Calculate impulse scalar
float impulse = (2.0f * vn) / (1.0f + 1.0f); // Assuming equal mass balls
// Apply impulse to change velocities
oldPosition.x -= impulse * nx * damping;
oldPosition.y -= impulse * ny * damping;
otherBall->oldPosition.x += impulse * nx * damping;
otherBall->oldPosition.y += impulse * ny * damping;
}
}
else if (distance position.x -= std::cos(angle) * perturbation / 2.0f;
otherBall->position.y -= std::sin(angle) * perturbation / 2.0f;
}
}
}
}
}
}
и вот полный код на всякий случай:
Код: Выделить всё
#include
#include
#include
#include
#include // for rand()
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
// Define the Vector2 class
class Vector2 {
public:
float x;
float y;
// Add a default constructor
Vector2() : x(0), y(0) {}
// Add a constructor that takes x and y as arguments
Vector2(float x, float y) : x(x), y(y) {}
// Define vector operations
Vector2 operator+(const Vector2& other) const {
Vector2 result;
result.x = x + other.x;
result.y = y + other.y;
return result;
}
Vector2 operator-(const Vector2& other) const {
Vector2 result;
result.x = x - other.x;
result.y = y - other.y;
return result;
}
Vector2 operator*(float scalar) const {
Vector2 result;
result.x = x * scalar;
result.y = y * scalar;
return result;
}
Vector2 operator/(float scalar) const {
Vector2 result;
result.x = x / scalar;
result.y = y / scalar;
return result;
}
float length() const {
return std::sqrt(x * x + y * y);
}
Vector2 normalize() const {
float len = length();
if (len > 0) {
return Vector2(x / len, y / len);
}
return *this;
}
float dot(const Vector2& other) const {
return x * other.x + y * other.y;
}
};
// Ball class
class Ball {
private:
sf::CircleShape shape;
Vector2 oldPosition;
Vector2 acceleration;
Vector2 gravity = Vector2(0, 980);
float radius;
public:
Vector2 position;
Ball(float radius, sf::Color color, float x, float y)
: radius(radius), position(x, y), oldPosition(x, y), acceleration(0, 0) {
shape.setRadius(radius);
shape.setFillColor(color);
shape.setPosition(x - radius, y - radius);
}
void updatePhysics(float dt) {
gravity.y = 980;
Vector2 velocity = (position - oldPosition) / dt; // Calculate velocity
oldPosition = position;
position = position + velocity * dt + acceleration * (0.5f * dt * dt);
acceleration = gravity; // Apply gravity
// Threshold values
const float restThreshold = 0.1f; // Lower threshold for resting
const float minMovement = 0.01f; // Minimum movement threshold
// Prevent jittering when at rest at bottom of window
if (std::abs(velocity.y) < restThreshold && position.y + radius >= 600) {
position.y = 600 - radius;
acceleration.y = 0;
gravity.y = 0;
oldPosition.y = position.y;
}
// Bounce off the top of the window
if (position.y - radius < 0) {
position.y = radius;
velocity.y = -velocity.y * 0.9f; // Dampen the bounce and invert the velocity
oldPosition.y = position.y - velocity.y * dt;
}
// Bounce off the bottom of the window
if (position.y + radius > 600) {
position.y = 600 - radius;
velocity.y = -velocity.y * 0.9f; // Dampen the bounce and invert the velocity
oldPosition.y = position.y - velocity.y * dt;
}
// Bounce off the sides of the window
if (position.x - radius < 0) {
position.x = radius;
velocity.x = -velocity.x * 0.7f; // Dampen the bounce and invert the velocity
oldPosition.x = position.x - velocity.x * dt;
}
else if (position.x + radius > 1000) {
position.x = 1000 - radius;
velocity.x = -velocity.x * 0.7f; // Dampen the bounce and invert the velocity
oldPosition.x = position.x - velocity.x * dt;
}
shape.setPosition(position.x - radius, position.y - radius);
}
// Collision check
void checkCollisions(std::vector& balls, std::vector& grid, int gridWidth, int gridHeight, int cellSize) {
const float damping = 0.8f;
const float epsilon = 0.001f;
const float separationFactor = 1.01f; // Slight increase in separation
const int collisionIterations = 5; // Number of iterations for collision resolution
int cellX = static_cast(position.x) / cellSize;
int cellY = static_cast(position.y) / cellSize;
for (int iteration = 0; iteration < collisionIterations; ++iteration) {
// Check neighboring cells for potential collisions
for (int x = std::max(0, cellX - 1); x position.x;
float dy = position.y - otherBall->position.y;
float distance = std::sqrt(dx * dx + dy * dy);
if (distance < minDist && distance > epsilon) {
float nx = dx / distance;
float ny = dy / distance;
// Calculate relative velocity
float relativeVelocityX = (position.x - oldPosition.x) - (otherBall->position.x - otherBall->oldPosition.x);
float relativeVelocityY = (position.y - oldPosition.y) - (otherBall->position.y - otherBall->oldPosition.y);
float relativeVelocityDotNormal = relativeVelocityX * nx + relativeVelocityY * ny;
// Only proceed with collision resolution if balls are moving towards each other
if (relativeVelocityDotNormal < 0) {
float overlap = minDist - distance;
float moveDistance = overlap / 2.0f * separationFactor; // Slight increase in separation
// Move balls apart along the collision normal
position.x += nx * moveDistance;
position.y += ny * moveDistance;
otherBall->position.x -= nx * moveDistance;
otherBall->position.y -= ny * moveDistance;
// Calculate velocities
float v1x = position.x - oldPosition.x;
float v1y = position.y - oldPosition.y;
float v2x = otherBall->position.x - otherBall->oldPosition.x;
float v2y = otherBall->position.y - otherBall->oldPosition.y;
// Calculate normal velocity
float vn = (v2x - v1x) * nx + (v2y - v1y) * ny;
// Calculate impulse scalar
float impulse = (2.0f * vn) / (1.0f + 1.0f); // Assuming equal mass balls
// Apply impulse to change velocities
oldPosition.x -= impulse * nx * damping;
oldPosition.y -= impulse * ny * damping;
otherBall->oldPosition.x += impulse * nx * damping;
otherBall->oldPosition.y += impulse * ny * damping;
}
}
else if (distance position.x -= std::cos(angle) * perturbation / 2.0f;
otherBall->position.y -= std::sin(angle) * perturbation / 2.0f;
}
}
}
}
}
}
void draw(sf::RenderWindow& window) {
window.draw(shape);
}
};
int main() {
sf::RenderWindow window(sf::VideoMode(1000, 600), "Verlet Integration");
std::vector balls;
const int ballNum = 20;
const int Maxradius = 20;
const int Minradius = 20;
balls.reserve(ballNum);
// Initialize balls
for (int i = 0; i < ballNum; i++) {
balls.emplace_back(rand() % Maxradius + Minradius, sf::Color(rand() % 255, rand() % 255, rand() % 255), rand() % 1000, rand() % 600);
}
// Grid parameters
int gridWidth = 1000 / (Maxradius * 2);
int gridHeight = 600 / (Maxradius * 2);
int cellSize = 2 * Maxradius;
std::vector grid(gridWidth, std::vector(gridHeight));
sf::Clock displayClock;
sf::Clock physicsClock;
float physicsDt = 1.0f / 240.0f;
float physicsAccumulator = 0.0f;
while (window.isOpen()) {
sf::Event event;
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed)
window.close();
}
float frameTime = displayClock.restart().asSeconds();
physicsAccumulator += frameTime;
// Physics update loop
while (physicsAccumulator >= physicsDt) {
// Clear grid
for (auto& column : grid) {
for (auto& cell : column) {
cell.clear();
}
}
// Update ball positions
for (auto& ball : balls) {
ball.updatePhysics(physicsDt);
int cellX = static_cast(ball.position.x) / cellSize;
int cellY = static_cast(ball.position.y) / cellSize;
if (cellX >= 0 && cellX < gridWidth && cellY >= 0 && cellY < gridHeight) {
grid[cellX][cellY].push_back(&ball);
}
}
// Resolve collisions
for (auto& ball : balls) {
ball.checkCollisions(balls, grid, gridWidth, gridHeight, cellSize);
}
physicsAccumulator -= physicsDt;
}
// Clear window
window.clear(sf::Color::White);
// Draw balls
for (auto& ball : balls) {
ball.draw(window);
}
// Display frame
window.display();
// Ensure consistent frame rate
sf::sleep(sf::seconds(1.0f / 60.0f) - displayClock.getElapsedTime());
}
return 0;
}
Я попробовал несколько настроек демпфирования, и это, кажется, работает лучше всего, но в остальном я не уверен, что еще я могу сделать.
Подробнее здесь:
https://stackoverflow.com/questions/786 ... tergration
1719828071
Anonymous
Я некоторое время работал над этой простой физической системой, и изначально я начал с эйлеровой физики, но мне не удалось добиться стабильной работы столкновений, и после перехода на интеграцию верлетов я все еще не могу получить ничего стабильного. Это работает совершенно нормально, когда шаров недостаточно, чтобы их можно было сложить друг на друга, но как только я добавляю больше шаров, и они приземляются друг на друга, они начинают отлетать, и это хаотично. вы можете увидеть полный код и только конкретную функцию столкновения ниже, я использую C++ и sfml. [code] // Collision check void checkCollisions(std::vector& balls, std::vector& grid, int gridWidth, int gridHeight, int cellSize) { const float damping = 0.8f; const float epsilon = 0.001f; const float separationFactor = 1.01f; // Slight increase in separation const int collisionIterations = 5; // Number of iterations for collision resolution int cellX = static_cast(position.x) / cellSize; int cellY = static_cast(position.y) / cellSize; for (int iteration = 0; iteration < collisionIterations; ++iteration) { // Check neighboring cells for potential collisions for (int x = std::max(0, cellX - 1); x position.x; float dy = position.y - otherBall->position.y; float distance = std::sqrt(dx * dx + dy * dy); if (distance < minDist && distance > epsilon) { float nx = dx / distance; float ny = dy / distance; // Calculate relative velocity float relativeVelocityX = (position.x - oldPosition.x) - (otherBall->position.x - otherBall->oldPosition.x); float relativeVelocityY = (position.y - oldPosition.y) - (otherBall->position.y - otherBall->oldPosition.y); float relativeVelocityDotNormal = relativeVelocityX * nx + relativeVelocityY * ny; // Only proceed with collision resolution if balls are moving towards each other if (relativeVelocityDotNormal < 0) { float overlap = minDist - distance; float moveDistance = overlap / 2.0f * separationFactor; // Slight increase in separation // Move balls apart along the collision normal position.x += nx * moveDistance; position.y += ny * moveDistance; otherBall->position.x -= nx * moveDistance; otherBall->position.y -= ny * moveDistance; // Calculate velocities float v1x = position.x - oldPosition.x; float v1y = position.y - oldPosition.y; float v2x = otherBall->position.x - otherBall->oldPosition.x; float v2y = otherBall->position.y - otherBall->oldPosition.y; // Calculate normal velocity float vn = (v2x - v1x) * nx + (v2y - v1y) * ny; // Calculate impulse scalar float impulse = (2.0f * vn) / (1.0f + 1.0f); // Assuming equal mass balls // Apply impulse to change velocities oldPosition.x -= impulse * nx * damping; oldPosition.y -= impulse * ny * damping; otherBall->oldPosition.x += impulse * nx * damping; otherBall->oldPosition.y += impulse * ny * damping; } } else if (distance position.x -= std::cos(angle) * perturbation / 2.0f; otherBall->position.y -= std::sin(angle) * perturbation / 2.0f; } } } } } } [/code] и вот полный код на всякий случай: [code]#include #include #include #include #include // for rand() #ifndef M_PI #define M_PI 3.14159265358979323846 #endif // Define the Vector2 class class Vector2 { public: float x; float y; // Add a default constructor Vector2() : x(0), y(0) {} // Add a constructor that takes x and y as arguments Vector2(float x, float y) : x(x), y(y) {} // Define vector operations Vector2 operator+(const Vector2& other) const { Vector2 result; result.x = x + other.x; result.y = y + other.y; return result; } Vector2 operator-(const Vector2& other) const { Vector2 result; result.x = x - other.x; result.y = y - other.y; return result; } Vector2 operator*(float scalar) const { Vector2 result; result.x = x * scalar; result.y = y * scalar; return result; } Vector2 operator/(float scalar) const { Vector2 result; result.x = x / scalar; result.y = y / scalar; return result; } float length() const { return std::sqrt(x * x + y * y); } Vector2 normalize() const { float len = length(); if (len > 0) { return Vector2(x / len, y / len); } return *this; } float dot(const Vector2& other) const { return x * other.x + y * other.y; } }; // Ball class class Ball { private: sf::CircleShape shape; Vector2 oldPosition; Vector2 acceleration; Vector2 gravity = Vector2(0, 980); float radius; public: Vector2 position; Ball(float radius, sf::Color color, float x, float y) : radius(radius), position(x, y), oldPosition(x, y), acceleration(0, 0) { shape.setRadius(radius); shape.setFillColor(color); shape.setPosition(x - radius, y - radius); } void updatePhysics(float dt) { gravity.y = 980; Vector2 velocity = (position - oldPosition) / dt; // Calculate velocity oldPosition = position; position = position + velocity * dt + acceleration * (0.5f * dt * dt); acceleration = gravity; // Apply gravity // Threshold values const float restThreshold = 0.1f; // Lower threshold for resting const float minMovement = 0.01f; // Minimum movement threshold // Prevent jittering when at rest at bottom of window if (std::abs(velocity.y) < restThreshold && position.y + radius >= 600) { position.y = 600 - radius; acceleration.y = 0; gravity.y = 0; oldPosition.y = position.y; } // Bounce off the top of the window if (position.y - radius < 0) { position.y = radius; velocity.y = -velocity.y * 0.9f; // Dampen the bounce and invert the velocity oldPosition.y = position.y - velocity.y * dt; } // Bounce off the bottom of the window if (position.y + radius > 600) { position.y = 600 - radius; velocity.y = -velocity.y * 0.9f; // Dampen the bounce and invert the velocity oldPosition.y = position.y - velocity.y * dt; } // Bounce off the sides of the window if (position.x - radius < 0) { position.x = radius; velocity.x = -velocity.x * 0.7f; // Dampen the bounce and invert the velocity oldPosition.x = position.x - velocity.x * dt; } else if (position.x + radius > 1000) { position.x = 1000 - radius; velocity.x = -velocity.x * 0.7f; // Dampen the bounce and invert the velocity oldPosition.x = position.x - velocity.x * dt; } shape.setPosition(position.x - radius, position.y - radius); } // Collision check void checkCollisions(std::vector& balls, std::vector& grid, int gridWidth, int gridHeight, int cellSize) { const float damping = 0.8f; const float epsilon = 0.001f; const float separationFactor = 1.01f; // Slight increase in separation const int collisionIterations = 5; // Number of iterations for collision resolution int cellX = static_cast(position.x) / cellSize; int cellY = static_cast(position.y) / cellSize; for (int iteration = 0; iteration < collisionIterations; ++iteration) { // Check neighboring cells for potential collisions for (int x = std::max(0, cellX - 1); x position.x; float dy = position.y - otherBall->position.y; float distance = std::sqrt(dx * dx + dy * dy); if (distance < minDist && distance > epsilon) { float nx = dx / distance; float ny = dy / distance; // Calculate relative velocity float relativeVelocityX = (position.x - oldPosition.x) - (otherBall->position.x - otherBall->oldPosition.x); float relativeVelocityY = (position.y - oldPosition.y) - (otherBall->position.y - otherBall->oldPosition.y); float relativeVelocityDotNormal = relativeVelocityX * nx + relativeVelocityY * ny; // Only proceed with collision resolution if balls are moving towards each other if (relativeVelocityDotNormal < 0) { float overlap = minDist - distance; float moveDistance = overlap / 2.0f * separationFactor; // Slight increase in separation // Move balls apart along the collision normal position.x += nx * moveDistance; position.y += ny * moveDistance; otherBall->position.x -= nx * moveDistance; otherBall->position.y -= ny * moveDistance; // Calculate velocities float v1x = position.x - oldPosition.x; float v1y = position.y - oldPosition.y; float v2x = otherBall->position.x - otherBall->oldPosition.x; float v2y = otherBall->position.y - otherBall->oldPosition.y; // Calculate normal velocity float vn = (v2x - v1x) * nx + (v2y - v1y) * ny; // Calculate impulse scalar float impulse = (2.0f * vn) / (1.0f + 1.0f); // Assuming equal mass balls // Apply impulse to change velocities oldPosition.x -= impulse * nx * damping; oldPosition.y -= impulse * ny * damping; otherBall->oldPosition.x += impulse * nx * damping; otherBall->oldPosition.y += impulse * ny * damping; } } else if (distance position.x -= std::cos(angle) * perturbation / 2.0f; otherBall->position.y -= std::sin(angle) * perturbation / 2.0f; } } } } } } void draw(sf::RenderWindow& window) { window.draw(shape); } }; int main() { sf::RenderWindow window(sf::VideoMode(1000, 600), "Verlet Integration"); std::vector balls; const int ballNum = 20; const int Maxradius = 20; const int Minradius = 20; balls.reserve(ballNum); // Initialize balls for (int i = 0; i < ballNum; i++) { balls.emplace_back(rand() % Maxradius + Minradius, sf::Color(rand() % 255, rand() % 255, rand() % 255), rand() % 1000, rand() % 600); } // Grid parameters int gridWidth = 1000 / (Maxradius * 2); int gridHeight = 600 / (Maxradius * 2); int cellSize = 2 * Maxradius; std::vector grid(gridWidth, std::vector(gridHeight)); sf::Clock displayClock; sf::Clock physicsClock; float physicsDt = 1.0f / 240.0f; float physicsAccumulator = 0.0f; while (window.isOpen()) { sf::Event event; while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) window.close(); } float frameTime = displayClock.restart().asSeconds(); physicsAccumulator += frameTime; // Physics update loop while (physicsAccumulator >= physicsDt) { // Clear grid for (auto& column : grid) { for (auto& cell : column) { cell.clear(); } } // Update ball positions for (auto& ball : balls) { ball.updatePhysics(physicsDt); int cellX = static_cast(ball.position.x) / cellSize; int cellY = static_cast(ball.position.y) / cellSize; if (cellX >= 0 && cellX < gridWidth && cellY >= 0 && cellY < gridHeight) { grid[cellX][cellY].push_back(&ball); } } // Resolve collisions for (auto& ball : balls) { ball.checkCollisions(balls, grid, gridWidth, gridHeight, cellSize); } physicsAccumulator -= physicsDt; } // Clear window window.clear(sf::Color::White); // Draw balls for (auto& ball : balls) { ball.draw(window); } // Display frame window.display(); // Ensure consistent frame rate sf::sleep(sf::seconds(1.0f / 60.0f) - displayClock.getElapsedTime()); } return 0; } [/code] Я попробовал несколько настроек демпфирования, и это, кажется, работает лучше всего, но в остальном я не уверен, что еще я могу сделать. Подробнее здесь: [url]https://stackoverflow.com/questions/78691549/how-to-get-stable-collisions-using-verlet-intergration[/url]