额
今天一时兴起,打算写个2D的布料模拟。
思索了一番布料节点碰撞,发现可能会发生线段穿模。
然后就写了这个。
原理
其实很简单
求出线段的函数解析式
struct LinearFunction
{
float k;
float b;
};
LinearFunction calcFunction()
{
LinearFunction lf;
lf.k = (vertex0.y - vertex1.y) / (vertex0.x - vertex1.x);
lf.b = vertex0.y - vertex0.x * lf.k;
return lf;
}
求出交点
sf::Vector2f calcIntersection(LinearFunction lf0, LinearFunction lf1)
{
sf::Vector2f intersection;
intersection.x = (lf1.b - lf0.b) / (lf0.k - lf1.k);
intersection.y = lf0.k * intersection.x + lf0.b;
return intersection;
}
判断交点的X坐标(或Y坐标)是否在两个线段之间
float x = intersection.x;
float x000 = ls0.vertex0.x;
float x010 = ls0.vertex1.x;
float x100 = ls1.vertex0.x;
float x110 = ls1.vertex1.x;
float x00 = std::min(x000, x010);
float x01 = std::max(x000, x010);
float x10 = std::min(x100, x110);
float x11 = std::max(x100, x110);
return (x > x00 && x < x01) && (x > x10 && x < x11);
返回的值就是碰撞结果
代码
拿SFML写的
#include <SFML/Graphics.hpp>
#include <iostream>
#include <cmath>
const float RADIUS = 5.0f;
struct LinearFunction
{
float k;
float b;
};
class LineSegment
{
public:
sf::Vector2f vertex0;
sf::Vector2f vertex1;
sf::CircleShape vcir0;
sf::CircleShape vcir1;
public:
LineSegment()
{
vcir0 = sf::CircleShape();
vcir1 = sf::CircleShape();
vcir0.setRadius(RADIUS);
vcir1.setRadius(RADIUS);
vcir0.setFillColor(sf::Color::Green);
vcir1.setFillColor(sf::Color::Green);
vcir0.setOrigin(sf::Vector2f(RADIUS, RADIUS));
vcir1.setOrigin(sf::Vector2f(RADIUS, RADIUS));
}
LinearFunction calcFunction()
{
LinearFunction lf;
lf.k = (vertex0.y - vertex1.y) / (vertex0.x - vertex1.x);
lf.b = vertex0.y - vertex0.x * lf.k;
return lf;
}
LineSegment& draw(sf::RenderTarget& target)
{
sf::VertexArray va(sf::PrimitiveType::Lines, 2);
va[0].position = vertex0;
va[0].color = sf::Color::Red;
va[1].position = vertex1;
va[1].color = sf::Color::Red;
target.draw(va);
vcir0.setPosition(vertex0);
vcir1.setPosition(vertex1);
target.draw(vcir0);
target.draw(vcir1);
return *this;
}
};
sf::Vector2f calcIntersection(LinearFunction lf0, LinearFunction lf1)
{
sf::Vector2f intersection;
intersection.x = (lf1.b - lf0.b) / (lf0.k - lf1.k);
intersection.y = lf0.k * intersection.x + lf0.b;
return intersection;
}
bool isCollision(LineSegment ls0, LineSegment ls1)
{
LinearFunction lf0 = ls0.calcFunction();
LinearFunction lf1 = ls1.calcFunction();
sf::Vector2f intersection = calcIntersection(lf0, lf1);
float x = intersection.x;
float x000 = ls0.vertex0.x;
float x010 = ls0.vertex1.x;
float x100 = ls1.vertex0.x;
float x110 = ls1.vertex1.x;
float x00 = std::min(x000, x010);
float x01 = std::max(x000, x010);
float x10 = std::min(x100, x110);
float x11 = std::max(x100, x110);
return (x > x00 && x < x01) && (x > x10 && x < x11);
}
bool isPressedVertex(sf::Vector2f pos, sf::Vector2f vertex)
{
float deltaX = pos.x - vertex.x;
float deltaY = pos.y - vertex.y;
float deltaX2 = deltaX * deltaX;
float deltaY2 = deltaY * deltaY;
float sum = deltaX2 + deltaY2;
float distance = std::sqrt(sum);
return (distance <= RADIUS);
}
sf::Vector2f toVector2f(sf::Vector2i vec)
{
return sf::Vector2f(float(vec.x), float(vec.y));
}
sf::Vector2f getMousePosition()
{
return toVector2f(sf::Mouse::getPosition());
}
sf::Vector2f getMousePosition(sf::Window& window)
{
return toVector2f(sf::Mouse::getPosition(window));
}
int main()
{
sf::RenderWindow window(sf::VideoMode(640, 480), "Line Segment");
window.setFramerateLimit(60);
window.setVerticalSyncEnabled(true);
sf::Vector2f* targetVertex = nullptr;
sf::Font font;
font.loadFromFile("simsun.ttc");
sf::Text text;
text.setString("not collision");
text.setFillColor(sf::Color::Blue);
text.setPosition(sf::Vector2f(0, 0));
text.setFont(font);
LineSegment ls1;
ls1.vertex0 = sf::Vector2f(200, 200);
ls1.vertex1 = sf::Vector2f(400, 400);
LineSegment ls2;
ls2.vertex0 = sf::Vector2f(200, 400);
ls2.vertex1 = sf::Vector2f(400, 200);
text.setString((isCollision(ls1, ls2) ? "collision" : "not collision"));
sf::Event event;
while (window.isOpen())
{
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
{
window.close();
}
else if (event.type == sf::Event::MouseButtonPressed)
{
sf::Vector2f pos = getMousePosition(window);
if (isPressedVertex(pos, ls1.vertex0))
{
targetVertex = &ls1.vertex0;
std::cout << "ls1.vertex0" << std::endl;
}
else if (isPressedVertex(pos, ls1.vertex1))
{
targetVertex = &ls1.vertex1;
std::cout << "ls1.vertex1" << std::endl;
}
else if (isPressedVertex(pos, ls2.vertex0))
{
targetVertex = &ls2.vertex0;
std::cout << "ls2.vertex0" << std::endl;
}
else if (isPressedVertex(pos, ls2.vertex1))
{
targetVertex = &ls2.vertex1;
std::cout << "ls2.vertex1" << std::endl;
}
}
else if (event.type == sf::Event::MouseMoved)
{
if (targetVertex != nullptr)
{
*targetVertex = getMousePosition(window);
}
text.setString((isCollision(ls1, ls2) ? "collision" : "not collision"));
}
else if (event.type == sf::Event::MouseButtonReleased)
{
targetVertex = nullptr;
}
}
window.clear();
window.draw(text);
ls1.draw(window);
ls2.draw(window);
window.display();
}
return 0;
}
运行结果