Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

Schorsch

Supermoderator

  • »Schorsch« ist der Autor dieses Themas

Beiträge: 5 145

Wohnort: Wickede

Beruf: Softwareentwickler

  • Private Nachricht senden

1

24.03.2017, 15:08

Box2D SFML2 DebugDraw Größenproblem

Aktuell benötige ich mal wieder Box2D. Dabei bekomme ich recht merkwürdiges Verhalten. Ich habe die DebugDraw für SFML implementiert, bzw eine fertige Implementierung aus dem Netz gesucht. An sich passiert da auch nichts wildes. Das komische ist nun, wenn ich eine Kiste auf einer anderen platziere, so befindet sich ein kleiner Spalt zwischen den beiden Körpern. Dieser Spalt tritt sowohl unten als auch rechts auf. Dies passiert solange ich die Strichstärke der Körper auf -1 stelle. Dabei wird der Rand eines Körpers innerhalb des Körpers gezeichnet. Bei einer Strichstärke von 1 passt die Kollision soweit unten und rechts, dafür überlappen die Körper aber am oberen und linken Rand. Der Körper scheint die Seiten links und oben also exklusiv und die Seiten rechts und unten inklusiv zu sehen. Hier mal ein Screenshot bei einer Strichstärke von -1. Klar zu sehen der Spalt am unteren Bildschirmrand.

Hier der Code zur DebugDraw Klasse:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#pragma once

#include <Box2D/Box2D.h>
#include <SFML/Window.hpp>
#include <SFML/Graphics.hpp>
#include <cmath>

namespace ts {

class Box2DDebugDraw : public b2Draw {
private:
  const float scale;
  const float lineThickness = -1.f;
  sf::RenderWindow *window;
  
  sf::Color GLColorToSFML(const b2Color &color, sf::Uint8 alpha = 255);
  
  sf::Vector2f B2VectorToSFVector(const b2Vec2 &vector, bool scaleToPixels = true);
  
public:
  Box2DDebugDraw(sf::RenderWindow *window, const float scale);
  
  void DrawPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color);

  void DrawSolidPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color);

  void DrawCircle(const b2Vec2& center, float32 radius, const b2Color& color);

  void DrawSolidCircle(const b2Vec2& center, float32 radius, const b2Vec2& axis, const b2Color& color);

  void DrawSegment(const b2Vec2& p1, const b2Vec2& p2, const b2Color& color);

  void DrawTransform(const b2Transform& xf);
  
  void DrawPoint(const b2Vec2& p, float32 size, const b2Color& color);
};

}


C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#include "Box2DDebugDraw.hpp"

sf::Color ts::Box2DDebugDraw::GLColorToSFML(const b2Color &color, sf::Uint8 alpha) {
  return sf::Color(static_cast<sf::Uint8>(color.r * 255), static_cast<sf::Uint8>(color.g * 255), static_cast<sf::Uint8>(color.b * 255), alpha);
}

sf::Vector2f ts::Box2DDebugDraw::B2VectorToSFVector(const b2Vec2 &vector, bool scaleToPixels) {
  return sf::Vector2f(vector.x * (scaleToPixels ? scale : 1.f), vector.y * (scaleToPixels ? scale : 1.f));
}

ts::Box2DDebugDraw::Box2DDebugDraw(sf::RenderWindow *window, const float scale) : window(window), scale(scale) {
}

void ts::Box2DDebugDraw::DrawPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color) {
  sf::ConvexShape polygon(vertexCount);
  sf::Vector2f center;
  for(int i = 0; i < vertexCount; i++) {
    sf::Vector2f transformedVec = B2VectorToSFVector(vertices[i]);
    polygon.setPoint(i, sf::Vector2f(std::floor(transformedVec.x), std::floor(transformedVec.y)));
  }
  polygon.setOutlineThickness(lineThickness);
  polygon.setFillColor(sf::Color::Transparent);
  polygon.setOutlineColor(GLColorToSFML(color));
  
  window->draw(polygon);
}

void ts::Box2DDebugDraw::DrawSolidPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color) {
  sf::ConvexShape polygon(vertexCount);
  for(int i = 0; i < vertexCount; i++) {
    sf::Vector2f transformedVec = B2VectorToSFVector(vertices[i]);
    polygon.setPoint(i, sf::Vector2f(std::floor(transformedVec.x), std::floor(transformedVec.y)));
  }
  polygon.setOutlineThickness(lineThickness);
  polygon.setFillColor(GLColorToSFML(color, 60));
  polygon.setOutlineColor(GLColorToSFML(color));
  
  window->draw(polygon);
}

void ts::Box2DDebugDraw::DrawCircle(const b2Vec2& center, float32 radius, const b2Color& color) {
  sf::CircleShape circle(radius * scale);
  circle.setOrigin(radius * scale, radius * scale);
  circle.setPosition(B2VectorToSFVector(center));
  circle.setFillColor(sf::Color::Transparent);
  circle.setOutlineThickness(lineThickness);
  circle.setOutlineColor(GLColorToSFML(color));
  
  window->draw(circle);
}

void ts::Box2DDebugDraw::DrawSolidCircle(const b2Vec2& center, float32 radius, const b2Vec2& axis, const b2Color& color) {
  sf::CircleShape circle(radius * scale);
  circle.setOrigin(radius * scale, radius * scale);
  circle.setPosition(B2VectorToSFVector(center));
  circle.setFillColor(GLColorToSFML(color, 60));
  circle.setOutlineThickness(lineThickness);
  circle.setOutlineColor(GLColorToSFML(color));
  
  b2Vec2 endPoint = center + radius * axis;
  sf::Vertex line[2] = {
    sf::Vertex(B2VectorToSFVector(center), GLColorToSFML(color)),
    sf::Vertex(B2VectorToSFVector(endPoint), GLColorToSFML(color))
  };
  
  window->draw(circle);  
  window->draw(line, 2, sf::Lines);
}

void ts::Box2DDebugDraw::DrawSegment(const b2Vec2& p1, const b2Vec2& p2, const b2Color& color) {
  sf::Vertex line[] = {
    sf::Vertex(B2VectorToSFVector(p1), GLColorToSFML(color)),
    sf::Vertex(B2VectorToSFVector(p2), GLColorToSFML(color))
  };
  
  window->draw(line, 2, sf::Lines);
}

void ts::Box2DDebugDraw::DrawTransform(const b2Transform& xf) {
  float lineLength = 0.4f;
  
  b2Vec2 xAxis = xf.p + lineLength * xf.q.GetXAxis();
  sf::Vertex redLine[] = {
    sf::Vertex(B2VectorToSFVector(xf.p), sf::Color::Red),
    sf::Vertex(B2VectorToSFVector(xAxis), sf::Color::Red)
  };
  
  b2Vec2 yAxis = xf.p + lineLength * xf.q.GetYAxis();
  sf::Vertex greenLine[] = {
    sf::Vertex(B2VectorToSFVector(xf.p), sf::Color::Green),
    sf::Vertex(B2VectorToSFVector(yAxis), sf::Color::Green)
  };
  
  window->draw(redLine, 2, sf::Lines);
  window->draw(greenLine, 2, sf::Lines);
}

void ts::Box2DDebugDraw::DrawPoint(const b2Vec2& p, float32 size, const b2Color& color) {
  this->DrawCircle(p, size, color);
}


Wie gesagt, nichts wildes. Zusätzlich der Code meiner Testszene:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#pragma once

#include "IGameState.hpp"
#include "EGameState.hpp"

#include <Box2D/Box2D.h>

#include "../misc/Box2DDebugDraw.hpp"
#include "../input/Controller.hpp"

#include <iostream>

namespace ts {

namespace gamestates {

class Box2DTestGameState : public GameState {
private:
  b2Vec2 gravity;
  b2World world;
  b2Body *ground;
  b2Body *player;
  Box2DDebugDraw debugDraw;
  Controller *controller;
  
  int remainingJumpSteps = 0;
public:
  Box2DTestGameState(sf::RenderWindow &window, Controller &controller) : gravity(0.f, 200.f), world(gravity), debugDraw(&window, 8.f), controller(&controller) {
    world.SetDebugDraw(&debugDraw);
    debugDraw.SetFlags(b2Draw::e_shapeBit);
    {
      b2BodyDef groundBodyDef;
      groundBodyDef.type = b2_staticBody;
      groundBodyDef.position.Set(86., 94.);
      ground = world.CreateBody(&groundBodyDef);
      b2PolygonShape groundShape;
      groundShape.SetAsBox(87, 8);
      b2FixtureDef groundFixtureDef;
      groundFixtureDef.shape = &groundShape;
      groundFixtureDef.density = 1;
      ground->CreateFixture(&groundFixtureDef);
    }
    {
      b2BodyDef playerBodyDef;
      playerBodyDef.type = b2_dynamicBody;
      playerBodyDef.position.Set(50, 50);
      playerBodyDef.fixedRotation = true;
      player = world.CreateBody(&playerBodyDef);
      b2PolygonShape playerShape;
      playerShape.SetAsBox(5, 5);
      b2FixtureDef playerFixture;
      playerFixture.shape = &playerShape;
      playerFixture.density = 1;
      player->CreateFixture(&playerFixture);
    }
  }
  
  ~Box2DTestGameState() {
    world.DestroyBody(ground);
    world.DestroyBody(player);
  }
  
  EGameState update(sf::Time deltaTime) {
    b2Vec2 vel = player->GetLinearVelocity();
    float desiredVel = 0;
    float acc = 1.f;
    float damping = 0.4f;
    float maxSpeed = 16.f;
    if(controller->pressedLeft()) {
      desiredVel = b2Max(vel.x - acc, -maxSpeed);
    } else if(controller->pressedRight()) {
      desiredVel = b2Min(vel.x + acc, maxSpeed);
    } else {
      desiredVel = vel.x * damping;
    }
    float velChange = desiredVel - vel.x;
    float impulse = player->GetMass() * velChange;
    player->ApplyLinearImpulse(b2Vec2(impulse, 0), player->GetWorldCenter(), true);
    if(controller->hitX()) {
      remainingJumpSteps = 6;
    }
    if(remainingJumpSteps > 0) {
      float force = player->GetMass() * 150 / (1.f/60.f);
      force /= 6.f;
      player->ApplyForce(b2Vec2(0, -force), player->GetWorldCenter(), true);
      remainingJumpSteps--;
    }
    world.Step(1.f/60.f, 16, 16);
    return EGameState::NOTHING;
  }
  
  void render() {
    world.DrawDebugData();
  }
};

}

}


Der restliche Code kümmert sich nur um die Verwaltung, das Rendering etc. Für dieses Problem ist es aber nicht wichtig. Ich könnte jetzt natürlich gucken dass ich das Problem einfach umgehe, lieber würde ich aber den Grund wissen und eine vernünftige Lösung finden.
„Es ist doch so. Zwei und zwei macht irgendwas, und vier und vier macht irgendwas. Leider nicht dasselbe, dann wär's leicht.
Das ist aber auch schon höhere Mathematik.“

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

2

24.03.2017, 16:43

Das liegt nicht am Debug-Draw. Das liegt daran, wie Box2D Kollisionen handhabt. Es lässt eine kleine Lücke, wenn ich mich richtig erinnere. Deswegen soll man seine Sprites immer minimal größer machen (1 Pixel) als die Box2D-Shapes. Wir machen das in RicketyRacquet z.B. ebenfalls so. Der ursprüngliche DebugDraw-Code ist also vermutlich korrekt. Die Rohdaten von Box2D sollten das bestätigen.

Übrigens sollte man Box2D niemals im Pixel-Space verwenden. Bei RR sind 10 Pixel z.B. eine Box2D-Einheit und empfohlen ist ein Faktor zwischen 10 und 100.

Ich hoffe das war das passende Thema, denn ich habe ehrlich gesagt deine Beschreibung mit "Rand eines Körpers innerhalb" und "links und oben also exklusiv und die Seiten rechts und unten inklusiv" nicht verstanden.
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von »BlueCobold« (24.03.2017, 16:50)


BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

3

24.03.2017, 17:05

Oder redest du eventuell darüber, dass zwei Quadrate der Größe 20/20, wobei eins bei 0/0 und das andere bei 20/0 sitzt sich nicht überlappen, obwohl die rechte Kante des ersten und die linke Kante des zweiten *beide* bei x=20 sitzen? Das ist Magic des OpenGL/GPU-Rasterizers und durchaus logisch, denn sonst wären die Quadrate ja nicht 20 Pixel breit, sondern 21 oder sogar 22, wenn sie die jeweilige Kante mitzeichnen würden. Vielleicht hilft da die Vorstellung, dass eine Koordinate nicht in der Mitte eines Pixels sitzt, sondern in dessen linker oberer Ecke.
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von »BlueCobold« (24.03.2017, 17:11)


Schorsch

Supermoderator

  • »Schorsch« ist der Autor dieses Themas

Beiträge: 5 145

Wohnort: Wickede

Beruf: Softwareentwickler

  • Private Nachricht senden

4

25.03.2017, 15:15

Das hat es genau getroffen, danke. Wir haben eine Skalierung, wobei die relativ klein ist. Das liegt aber einfach daran dass unsere Sprites sehr klein sind und somit 1m im Spiel nur einigen Pixeln entspricht. Der Rest leuchtet ein. So in etwa hatte ich mir das schon gedacht. Dann werde ich das Problem einfach umgehen. Danke nochmals.
„Es ist doch so. Zwei und zwei macht irgendwas, und vier und vier macht irgendwas. Leider nicht dasselbe, dann wär's leicht.
Das ist aber auch schon höhere Mathematik.“

Werbeanzeige