I'm trying to code metaballs in C++/SFML, my program works just fine in a single thread. I tried to write an MRE to find the problem and here's what I got:
main.cpp
// main
#include <iostream>
#include "threader.h"
float func(float x, float y, float a, float b, float r) {
return 1.f / sqrt((x - a)*(x - a) + (y - b)*(x - b));
}
int main () {
sf::RenderWindow window(sf::VideoMode(2800, 1800), "");
window.setFramerateLimit(20);
sf::Event event{};
threader tf(window);
while (window.isOpen()) {
while (window.pollEvent(event)) {
switch (event.type) {
case sf::Event::Closed: {
window.close();
}
}
}
window.clear();
tf.parallel(func);
window.display();
}
}
threader.h
//threader_H
#pragma once
#include <array>
#include <thread>
#include <cmath>
#include <functional>
#include <SFML/Graphics.hpp>
class threader {
public:
int threadCount = 4;
std::array<std::thread, 4> threads;
sf::RenderWindow& window;
public:
explicit threader(sf::RenderWindow& w) : window(w) {};
void strip(int start, int end, const std::function<float(float, float, float, float, float)>& func);
void parallel(const std::function<float(float, float, float, float, float)>& func);
};
threader.cpp
// threader_CPP
#include "threader.h"
#include <iostream>
void threader::strip (int start, int end, const std::function<float (float, float, float, float, float)> &func) {
for (int X = start; X < end; X += 10) {
for (int Y = 0; Y < window.getSize().y; Y += 10) {
auto x = static_cast<float>(X);
auto y = static_cast<float>(Y);
x = x / 2800.f * 4 + 0 - 4 / 2.f;
y = -((1800.f - y) / 1800 * 4 + 0 - 4 / 2.f);
if (func(x, y, 1, 2, 3) > 1) {
sf::CircleShape circle(20);
circle.setPointCount(3);
circle.setFillColor(sf::Color::Cyan);
circle.setPosition(X, Y);
window.draw(circle);
}
}
}
}
void threader::parallel (const std::function<float (float, float, float, float, float)> &func) {
int start = 0;
int end = window.getSize().x / threadCount;
for (auto& t : threads) {
t = std::thread(&threader::strip, this, start, end, func);
start = end;
end += window.getSize().x / threadCount;
}
for (auto& t : threads) {
t.join();
}
}
Now for the explanation. threader is a class which has two methods: strip that calculates a function for a given strip of the window and parallel that creates threads to separately calculate my function for every strip. This code doesn't work:
But here's the catch: if I adjust the function void func(...) in main to return 1.f / sqrt((x - a) + (y - b)), everything works just fine. What is happening? How a simple calculation can cause a segfault? help please...
EDIT 1: Written in CLion, C++ 20.
EDIT 2: If anything here makes sense, please explain it to me.
Rather than try to draw to the window on multiple threads, I would instead use std algorithms to filter the points to draw circles, and draw them all on the main thread.
std::vector<std::pair<int, int>> getPoints(const sf::RenderWindow& window) {
std::vector<std::pair<int, int>> points;
for (int X = 0; X < window.getSize().x; X += 10) {
for (int Y = 0; Y < window.getSize().y; Y += 10) {
points.emplace_back(X, Y);
}
}
return points;
}
template<typename F>
auto filter(F f) {
return [f](const std::pair<int, int> & point) {
auto x = static_cast<float>(point.first);
auto y = static_cast<float>(point.second);
x = x / 2800.f * 4 + 0 - 4 / 2.f;
y = -((1800.f - y) / 1800 * 4 + 0 - 4 / 2.f);
return (func(x, y, 1, 2, 3) > 1);
}
}
sf::CircleShape toCircle(int X, int Y) {
sf::CircleShape circle(20);
circle.setPointCount(3);
circle.setFillColor(sf::Color::Cyan);
circle.setPosition(X, Y);
return circle;
}
template <typename F>
void drawCircles(sf::RenderWindow& window, F f) {
auto points = getPoints(window);
auto end = std::remove_if(std::execution::par, points.begin(), points.end(), filter(f));
points.erase(end, points.end());
for (auto & [X, Y] : points) {
window.draw(toCircle(X, Y));
}
}
float func(float x, float y, float a, float b, float r) {
return 1.f / sqrt((x - a)*(x - a) + (y - b)*(x - b));
}
int main () {
sf::RenderWindow window(sf::VideoMode(2800, 1800), "");
window.setFramerateLimit(20);
sf::Event event{};
threader tf(window);
while (window.isOpen()) {
while (window.pollEvent(event)) {
switch (event.type) {
case sf::Event::Closed: {
window.close();
}
}
}
window.clear();
drawCircles(window, func);
window.display();
}
}
There is a problem with trying to render from multiple threads without properly switching GL contexts.
Since you are doing this on the CPU, here are some options:
Make multiple sf::RenderTexture objects, render to each one of them in a separate thread (don't forget to call display() at the end), then draw them on the main thread in some sprites. (harder and not sure it is the way to go, but I don't know what you want to do in the end)
Make multiple sf::Image objects, render to each one of them in a separate thread, then draw them on the main thread in some sprites.
Make just one sf::Image object, each thread renders in a separate area of the image, then draw the image on the main thread in some sprite.
options 2 & 3 make it more easy to work with at a pixel level, also faster. Make sure you traverse the image in a cache friendly way (line by line most likely)
I would go with 2 or 3 depending on how you think it's better for your application.
Also, use sf::Transform for the transformation from screen space (Image space) to world space where the metaballs live.
https://www.sfml-dev.org/documentation/2.5.1/classsf_1_1Image.php
https://www.sfml-dev.org/documentation/2.5.1/classsf_1_1Transform.php#ab42a0bb7a252c6d221004f6372ce5fdc
Related
I have a problem to properly setup a test for a turtle-graphic interface.
As sample I have a simplified interface for just drawing a line.
Now I want to write a test for drawing some grid draw_grid and ensure that each line of the grid is drawn, but the actual order of drawn lines does not matter. I only need to ensure, that the method calls to move_to and line_to are properly paired. I tried this with InSequence but this leads to several issues, because a couple of line_to and move_to calls are used for two lines.
For example move_to(0,0) is used for the top edge of the grid and for the left border of the first cell. So the test will generate two EXPACT_CALL(sot, move_to(0,0)) in two different sequences, but each of them implicitly with Times(1). So I guess this is the main problem. This will happen for each left and top border of a row and similarly for line_to for right and bottom border of a row.
I also tried to use After for an EXPACT_CALL, but this just leads to different test errors.
Is there any nice way to specify the requested behavior? Thanks for your help!
using testing::InSequence;
struct ITurtle
{
virtual void move_to(int x, int y) = 0;
virtual void line_to(int x, int y) = 0;
void line(int x0, int y0, int x1, int y1) { move_to(x0, y0); line_to(x1, y1); }
};
class TurtleMock
: public ITurtle
{
public:
MOCK_METHOD(void, move_to, (int x, int y), (override));
MOCK_METHOD(void, line_to, (int x, int y), (override));
};
void draw_grid(ITurtle &t)
{
for (int r = 0; r < 100; r += 10)
{
// top
t.line(0,r,100,r);
for (int c = 0; c < 100; c += 10)
{ // left
t.line(c,r,c,r+10);
}
// right
t.line(100,r,100,r+10);
}
// bottom
t.line(0,100,100,100);
}
TEST(TurtleTest, lines)
{
TurtleMock sot;
for (int r = 0; r < 100; r += 10)
{
{
InSequence s;
EXPECT_CALL(sot, move_to(0, r));
EXPECT_CALL(sot, line_to(100, r));
}
for (int c = 0; c < 100; c += 10)
{
InSequence s;
EXPECT_CALL(sot, move_to(c,r));
EXPECT_CALL(sot, line_to(c,r+10));
}
{
InSequence s;
EXPECT_CALL(sot, move_to(100, r));
EXPECT_CALL(sot, line_to(100, r + 10));
}
}
{
InSequence s;
EXPECT_CALL(sot, move_to(0, 100));
EXPECT_CALL(sot, line_to(100, 100));
}
draw_grid(sot);
}
EDIT:
Extension to proposed solution of Sedenion to support polygon drawing with one initial move_to followed by a sequence of line_to statements.
void move_to(int x, int y) final
{
m_move_to_data = {x, y};
}
void line_to(int x, int y) final
{
ASSERT_TRUE(m_move_to_data.has_value());
auto const [x0, y0] = *m_move_to_data;
line_mock(x0, y0, x, y);
m_move_to_data = {x, y};
}
Consider the following simplified example of drawing just 2 lines (live example):
void draw_two_lines(ITurtle &t)
{
t.line(0, 0, 10, 0);
t.line(0, 0, 0, 10);
}
TEST(TurtleTest, two_lines)
{
TurtleMock sot;
{
InSequence s;
EXPECT_CALL(sot, move_to(0, 0)); // (2)
EXPECT_CALL(sot, line_to(10, 0));
}
{
InSequence s;
EXPECT_CALL(sot, move_to(0, 0)); // (1)
EXPECT_CALL(sot, line_to(0, 10));
}
draw_two_lines(sot);
}
Forgetting about InSequence for a moment, expectations are matched in reverse order (from bottom to top) by default by Google Mock.
Quote from the manual:
By default, when a mock method is invoked, gMock will search the expectations in the reverse order they are defined, and stop when an active expectation that matches the arguments is found (you can think of it as “newer rules override older ones.”).
Now, here we do have InSequence, i.e. two groups. However, the two groups themselves are not "in sequence". That means gMock will match the first call to move_to(0, 0) with the second group (marked by (1) in the code above). Thus, afterwards, line_to(0, 10) is expected but line_to(10, 0) gets called, resulting in a test failure. If you exchange the order of the two InSequence-groups, the test will pass. However, this is not really worth anything since your goal is to have the order independent.
What you want is basically to specify something like one "atomic" match of all 4 parameters.
I am not aware of any way to directly express this with the InSequence or After machinery of GoogleMock. Thus, I propose to take another approach and store the call of move_to in a temporary variable, and in the call of line_to take the remembered two values and the two given values to call a dedicated mock function (live example):
struct ITurtle
{
virtual void move_to(int x, int y) = 0;
virtual void line_to(int x, int y) = 0;
void line(int x0, int y0, int x1, int y1) { move_to(x0, y0); line_to(x1, y1); }
};
class TurtleMock : public ITurtle
{
public:
std::optional<std::pair<int, int>> move_to_data;
virtual void move_to(int x, int y) final {
ASSERT_FALSE(move_to_data.has_value());
move_to_data = {x, y};
}
virtual void line_to(int x1, int y1) final {
ASSERT_TRUE(move_to_data.has_value());
auto const [x0, y0] = *move_to_data;
line_mock(x0, y0, x1, y1);
move_to_data.reset();
}
MOCK_METHOD(void, line_mock, (int x0, int y0, int x1, int y1));
};
void draw_two_lines(ITurtle &t)
{
t.line(0, 0, 10, 0);
t.line(0, 0, 0, 10);
}
TEST(TurtleTest, two_lines)
{
TurtleMock sot;
EXPECT_CALL(sot, line_mock(0, 0, 10, 0));
EXPECT_CALL(sot, line_mock(0, 0, 0, 10));
draw_two_lines(sot);
}
This allows to specify all 4 parameters in one "atomic" match, making the whole stuff with InSequence unnecessary. The above example passes regardless of the order of the line() calls in draw_two_lines().
Your test for draw_grid() would then become (live example):
TEST(TurtleTest, grid)
{
TurtleMock sot;
for (int r = 0; r < 100; r += 10)
{
EXPECT_CALL(sot, line_mock(0, r, 100, r));
for (int c = 0; c < 100; c += 10) {
EXPECT_CALL(sot, line_mock(c, r, c, r+10));
}
EXPECT_CALL(sot, line_mock(100, r, 100, r + 10));
}
EXPECT_CALL(sot, line_mock(0, 100, 100, 100));
draw_grid(sot);
}
Note: This solution assumes that you cannot or do not want to make ITurtle::line() virtual. If it were, you could of course ditch the helper move_to_data and line_mock() and instead mock line() directly.
I decided to make a multiplayer game in sfml, the map is a text variable
one step lower,! - block with a collider, # - ground.
It looks something like this:
"## !;
! # !;
###;"
I have a special class "Qardrat" that represents a block with a texture, that is, an alternative to a sprite.
class Quardrat {
public:
void spriteSetPosition(int Vx, int Vy) {
sprite.setPosition(Vx, Vy);
}
void LoadTexture(String textureName) {
texture.loadFromFile(textureName);
// RectangleShape tmpSprite(texture);
sprite.setScale(sizeX, sizeY);
sprite.setTexture(&texture);
}
void ShapeMove(int Vx, int Vy) {
sprite.move(Vx, Vy);
//sprite.setPosition(sprite.getPosition().x+Vx, sprite.getPosition().y+Vy);
std::cout << "Сдвинулся на " << Vx << "По x" << std::endl;
}
Quardrat(float x = 0, float y = 0, float sx = 1, float sy = 1, String textureName = "player.png") {
LoadTexture(textureName);
sizeX = sx;
sizeY = sy;
sprite.setPosition(x, y);
sprite.setSize(Vector2f(sx, sy));
}
sf::RectangleShape GetShape() {
return sprite;
}
void DrawShape() {
::window.draw(sprite);
}
float GetSizeX() {
return sizeX;
}float GetSizeY() {
return sizeY;
}
private:
Texture texture;
std::string texutreName = "player.png";
float sizeX = 10;
float sizeY = 10;
//Sprite sprite;
RectangleShape sprite;
};
It is declared as follows: x position, y position, height, width, texture name.
To draw objects, 3 cities are used, which are placed in the square classes:
sloi0, sloi1, player /
here is the drawing code
void DrawOnDisplay(const std::vector<std::reference_wrapper<Quardrat>>& a) {//РИСОВАНИЕ
for (Quardrat i : sloi0)
i.DrawShape();
//::window.draw(i.GetShape());
for (Quardrat i : sloi1)
i.DrawShape();
// ::window.draw(i.GetShape());
for (Quardrat i : a) {
i.DrawShape();
// ::window.draw(i.GetShape());
}
}
And I'll leave the card reading code here, just in case:
for (char block : map) {
if (block == '#') {
Quardrat bl = Quardrat(BlockPosX * StandartBlockSize, BlockPosY * StandartBlockSize, StandartBlockSize, StandartBlockSize);
sloi0.push_back(bl); BlockPosX++;
// }
if (block == '!') {
Quardrat bl = Quardrat(BlockPosX * StandartBlockSize, BlockPosY * StandartBlockSize,
StandartBlockSize / 10, StandartBlockSize / 10, "block.png");
sloi0.push_back(bl); BlockPosX++;
collisions.push_back(bl);
}
if (block == ';') {
BlockPosY++;
BlockPosX = 0;
}
}
And here's the problem - from all the blocks of the map, a texture appears only for one, for all the rest - a white square.
I'm trying to move the user mouse using mouse_event function in WinAPI. This is my code:
while (LeftMouseDown)
{
POINT cursorPos;
GetCursorPos(&cursorPos);
//X Axis And Y Axis is DWORD Array
mouse_event(MOUSEEVENTF_MOVE, xaxis[iCount], yaxis[iCount], 0, 0);
iCount++;
Sleep(200);
}
It's working good but the problem is that I want the code smooth movement, because the function teleports the cursor instantly and I don't want that, I want it to be smooth transition or something like that.
From what it looks like, you are trying to move across a path of points in a smooth manner.
If so, then you are going to have to interpolate along that path via time.
Essentially, the idea is that you first obtain the total length of the path. Then as you are updating time you obtain a total distance via the total duration and the elapsed amount of time. Finally, you find the two points where the obtained distance is somewhere in the middle. Then you simply interpolate along those two points to get a relatively accurate point.
With this class you can pass the points for the path and a a duration to specify how long you would like to be moving along the path for. Then you would simply update it via time intervals.
Mover.h
#include <chrono>
#include <vector>
#ifndef MOVER_H
#define MOVER_H
struct Point {
int x, y;
Point(int x_, int y_)
: x(x_), y(y_) {
}
Point() : Point(0, 0) {
}
};
class Mover {
public:
struct PointData {
float total;
float distance;
Point p1;
Point p2;
PointData()
: total(0.f),
distance(0.f) {
}
PointData(float total, float distance, Point p1, Point p2)
: total(total),
distance(distance),
p1(p1),
p2(p2) {
}
};
using TimePoint = std::chrono::microseconds;
private:
std::vector<Point> m_points;
std::vector<PointData> m_distances;
TimePoint m_duration;
TimePoint m_elapsed;
float m_length;
public:
Mover(std::initializer_list<Point> points, TimePoint duration = std::chrono::microseconds(2000000));
template<typename iter_t>
Mover(iter_t begin, iter_t end, TimePoint duration = std::chrono::microseconds(2000000))
: m_points(begin, end),
m_duration(duration),
m_elapsed(std::chrono::milliseconds(0)) {
updateLength();
}
Mover(const Mover&) = default;
Mover& operator=(const Mover&) = default;
Point update(TimePoint delta);
bool isComplete() const;
void setDuration(TimePoint duration);
TimePoint getDuration() const;
TimePoint getElapsed() const;
private:
void updateLength();
};
#endif // MOVER_H
Mover.cpp
#include "Mover.h"
#include <algorithm>
#include <cmath>
Mover::Mover(std::initializer_list<Point> points, TimePoint duration)
: Mover(points.begin(), points.end(), duration)
{
}
Point Mover::update(TimePoint delta)
{
const auto comparison = [](float left, const PointData& right) {
return left < right.total;
};
m_elapsed = std::min(m_elapsed + delta, m_duration);
const float length = (static_cast<float>(m_elapsed.count()) / static_cast<float>(m_duration.count())) * m_length;
auto& data = *std::prev(std::upper_bound(m_distances.begin(), m_distances.end(), length, comparison));
const float percent = (length - data.total) / data.distance;
Point point(data.p1.x + percent * (data.p2.x - data.p1.x), data.p1.y + percent * (data.p2.y - data.p1.y));
return point;
}
bool Mover::isComplete() const
{
return m_duration == m_elapsed;
}
void Mover::setDuration(TimePoint duration)
{
m_duration = duration;
}
Mover::TimePoint Mover::getDuration() const
{
return m_duration;
}
Mover::TimePoint Mover::getElapsed() const
{
return m_elapsed;
}
void Mover::updateLength()
{
auto distance = [](float x1, float y1, float x2, float y2) -> float{
return std::sqrt(((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)));
};
float length = 0.f;
for (std::size_t index = 0; (index + 1) < m_points.size(); ++index) {
const float dist = distance(m_points[index].x, m_points[index].y, m_points[index + 1].x, m_points[index + 1].y);
m_distances.emplace_back(length, dist, m_points[index], m_points[index + 1]);
length += dist;
}
m_length = length;
}
Example
#include <iostream>
#include "Mover.h"
int main() {
std::vector<Point> points{ { 0, 0 }, { 100, 100 } };
Mover move(points.begin(), points.end());
auto t1 = std::chrono::steady_clock::now();
while (!move.isComplete()) {
auto t2 = std::chrono::steady_clock::now();
auto point = move.update(std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1));
std::cout << point.x << ' ' << point.y;
t1 = t2;
}
}
It's worth mentioning that in order to use this you are going to have to keep track of the time in order to update the mover. So it's probably worth implementing a Clock class or something to keep track of the time for you.
Also, if you want to move along the path relative to the current cursor position, then you would have to simply add the cursor position to the active path point.
I know this is very late but I might as well answer it.
while doing golang I used this library called robot.go and over there they have a function that does the smooth input. So if you want you can use that as a reference.
https://github.com/go-vgo/robotgo/blob/master/mouse/mouse_c.h
looking at your code your mouse will jump pixels every 200 ms.
while (LeftMouseDown)
{
POINT cursorPos;
GetCursorPos(&cursorPos);
//X Axis And Y Axis is DWORD Array
int i;
for (i = 0; i < 20; i++)
{
mouse_event(MOUSEEVENTF_MOVE, (xaxis[icount]-cursorPos.x)/20, (yaxis[icount]-cursorPos.y)/20, 0, 0);
iCount++;
Sleep(10);
}
}
I wanted to create a vector of randomly placed squares and draw them to the screen, tried passing a reference to a vector and i couldn't get that to work :(
consumable.h
#ifndef CONSUMABLE_H
#define CONSUMABLE_H
#include <SFML/Graphics.hpp>
using namespace std;
using namespace sf;
class consumable
{
public:
consumable();
virtual ~consumable();
vector<RectangleShape> fCreateConsumable(vector<RectangleShape>& vConsumable);
void fDrawTarget(float x, float y, RenderWindow &thatWindow);
protected:
private:
vector<RectangleShape> vConsumable;
RectangleShape _consumable;
};
consumable.cpp
#include "consumable.h"
consumable::consumable()
{
//ctor
}
consumable::~consumable()
{
//dtor
}
void consumable::fCreateConsumable(){
int consumableX{0}, consumableY{0};
for(int i=0;i<4;i++){
consumableX = (rand() % 31) + 1;
consumableY = (rand() % 22) + 1;
_consumable.setPosition((consumableX * 25), (consumableY * 25));
_consumable.setSize(sf::Vector2f(25.0f,25.0f));
_consumable.setFillColor(sf::Color::Magenta);
vConsumable.push_back(_consumable);
}
}
void consumable::fDrawTarget(float x, float y, RenderWindow &thatWindow){
void fCreateConsumable();
for(int i{0};i< vConsumable.size();i++){
thatWindow.draw(vConsumable[i]);
}
}
main.cpp
#include <iostream>
#include <SFML/Graphics.hpp>
#include "consumable.h"
using namespace std;
using namespace sf;
int main()
{
consumable Consumable;
RenderWindow window(VideoMode(800,750), "C++ Snake");
while (window.isOpen())
{
Event event;
while (window.pollEvent(event))
{
switch(event.type)
{
case Event::Closed:
window.close();
break;
default:
break;
}
}
window.clear();
Consumable.fDrawTarget(25,25,window);
window.display();
}
std::cout << "Finished" << std::endl;
return 0;
}
I wanted to loop over
Looking at your class consumable, I'd start by making _consumable local and refactor fCreateConsumable adding another function.
class consumable
{
public:
// ...
vector<RectangleShape> fCreateConsumable(vector<RectangleShape>& vConsumable);
void fDrawTarget(float x, float y, RenderWindow &thatWindow);
void UpdatePositions();
private:
vector<RectangleShape> vConsumable;
};
By removing fCreateConsumable from fDrawTarget you can avoid creating new RectangleShape, reuse the old three ones, and update them with new positions.
void consumable::UpdatePositions(){
srand(time(NULL));
for(int i{0};i< vConsumable.size();i++){
vConsumable[i].setPosition(((rand() % 31) + 1) * 25, ((rand() % 22) + 1) * 25);
}
}
void consumable::fCreateConsumable(){
int consumableX{0}, consumableY{0};
srand(time(NULL));
for(int i=0;i<4;i++){
consumableX = (rand() % 31) + 1;
consumableY = (rand() % 22) + 1;
_consumable.setPosition((consumableX * 25), (consumableY * 25));
_consumable.setSize(sf::Vector2f(25.0f,25.0f));
_consumable.setFillColor(sf::Color::Magenta);
vConsumable.push_back(_consumable);
}
}
void consumable::fDrawTarget(float x, float y, RenderWindow &thatWindow){
UpdatePositions();
for(int i{0};i< vConsumable.size();i++){
thatWindow.draw(vConsumable[i]);
}
}
Personally, I would move out UpdatePositions from fDrawTarget too and make the drawcall a const function. That way you separate the update and the rendering. If you don't, move UpdatePositions to private scope.
I'm trying to implement collision detection in sfml using the separating axis theorem but my function to get the min translation vector (getMtv()) is always returning that there is a collision (MTV::IsValid()). I was following a tutorial here
but I can't see where I went wrong.
#include <iostream>
#include <math.h>
#include <SFML/Graphics.hpp>
#include <gtest/gtest.h>
typedef sf::Vector2f Vector2;
typedef sf::Vector2f Axis;
typedef sf::Vector2f Projection;
typedef std::vector<Axis> AxesVec;
class MTV
{
private:
bool _valid;
Axis _axis;
float _overlap;
public:
MTV(Axis axis, float overlap, bool valid)
{
_valid = valid;
_overlap = overlap;
_axis = axis;
}
bool IsValid() const
{
return _valid;
}
};
Vector2 perpendicular(Vector2 v)
{
return Vector2(v.y, -v.x);
}
float dot(Vector2 vec1, Vector2 vec2)
{
return (vec1.x * vec2.x) + (vec1.y * vec2.y);
}
float magnitude(Vector2 v)
{
return std::sqrt(dot(v,v));
}
class Polygon : public sf::ConvexShape
{
public:
const AxesVec& GetAxes() const
{
return axes;
}
AxesVec axes;
void generateAxes()
{
for (int i = 0; i < getPointCount(); i++)
{
// get the current vertex
Vector2 p1 = getPoint(i); //shape.vertices[i];
// get the next vertex
Vector2 p2 = getPoint(i + 1 == getPointCount() ? 0 : i + 1);
// subtract the two to get the edge vector
Vector2 edge = p1 - p2;
// get either perpendicular vector
Vector2 normal = perpendicular(edge);
// the perp method is just (x, y) => (-y, x) or (y, -x)
axes.push_back(normal / magnitude(normal));
}
};
float cross(Vector2 vec1, Vector2 vec2)
{
return (vec1.x * vec2.y) - (vec1.y * vec2.x);
}
Vector2 project(Polygon p, Axis axis)
{
float min = dot(axis, p.getPoint(0)); //axis.dot(shape.vertices[0]);
float max = min;
for (int i = 1; i < p.getPointCount(); i++)
{
// NOTE: the axis must be normalized to get accurate projections
float prj = dot(axis, p.getPoint(i));//axis.dot(shape.vertices[i]);
if (prj < min)
{
min = prj;
}
else if (prj > max)
{
max = prj;
}
}
//Projection proj = new Projection(min, max);
return Projection(min, max);
}
class Collison
{
private:
Vector2 mtv;
Polygon a;
Polygon b;
};
bool overlap(Projection a, Projection b)
{
// x = min & y = max
return !(a.x > b.y || a.x > b.y);
}
float getOverlap(Projection a, Projection b)
{
// x = min & y = max
return (a.y < b.y) ? a.y - b.x : b.y - a.x;
}
MTV getMtv(Polygon a, Polygon b)
{
float overlapMax = std::numeric_limits<float>::infinity();// really large value;
float Overlap;
Axis smallest;// = null;
AxesVec axesA = a.GetAxes();
AxesVec axesB = b.GetAxes();
// loop over the axes1
for (auto&& axis : axesA) //for (int i = 0; i < axes1.length; i++)
{
//Axis axis = axes1[i];
// project both shapes onto the axis
Projection pA = project(a, axis);
Projection pB = project(b, axis);
// do the projections overlap?
if (!overlap(pA, pB)) //(!p1.overlap(p2))
{
// then we can guarantee that the shapes do not overlap
return MTV(smallest, 0, false);
}
else
{
// get the overlap
float o = getOverlap(pA, pB); //p1.getOverlap(p2);
// check for minimum
if (o < overlapMax)
{
// then set this one as the smallest
Overlap = o;
smallest = axis;
}
}
}
for (auto&& axis : axesB) //for (int i = 0; i < axes1.length; i++)
{
//Axis axis = axes1[i];
// project both shapes onto the axis
Projection pA = project(a, axis);
Projection pB = project(b, axis);
// do the projections overlap?
if (!overlap(pA, pB)) //(!p1.overlap(p2))
{
// then we can guarantee that the shapes do not overlap
return MTV(smallest, 0, false);
}
else
{
// get the overlap
double o = getOverlap(pA, pB); //p1.getOverlap(p2);
// check for minimum
if (o < overlapMax)
{
// then set this one as the smallest
Overlap = o;
smallest = axis;
}
}
}
//MTV mtv = new MTV(smallest, overlap);
// if we get here then we know that every axis had overlap on it
// so we can guarantee an intersection
return MTV(smallest, Overlap, true);
}
int main(int argc, char **argv)
{
Polygon polygon;
polygon.setPointCount(3);
polygon.setPoint(0, sf::Vector2f(500, 100));
polygon.setPoint(1, sf::Vector2f(250, 500));
polygon.setPoint(2, sf::Vector2f(750, 500));
polygon.setFillColor(sf::Color::Red);
polygon.generateAxes();
Polygon polygon2;
polygon2.setPointCount(3);
polygon2.setPoint(0, sf::Vector2f(100, 0));
polygon2.setPoint(1, sf::Vector2f(50, 150));
polygon2.setPoint(2, sf::Vector2f(150, 150));
polygon2.generateAxes();
//polygon2.setPoint(0, sf::Vector2f(100, 0));
//polygon2.setPoint(1, sf::Vector2f(500, 150));
//polygon2.setPoint(2, sf::Vector2f(250, 150));
polygon2.setFillColor(sf::Color::Green);
sf::RenderWindow window(sf::VideoMode(800, 600), "My window");
// run the program as long as the window is open
while (window.isOpen())
{
// check all the window's events that were triggered since the last iteration of the loop
sf::Event event;
while (window.pollEvent(event))
{
// "close requested" event: we close the window
if (event.type == sf::Event::Closed)
window.close();
}
// clear the window with black color
window.clear(sf::Color::Black);
// draw everything here...
window.draw(polygon);
window.draw(polygon2);
std::cout << getMtv(polygon, polygon2).IsValid() << std::endl;
// end the current frame
window.display();
}
return 0;
}