refac: project overhaul, cpp industry standards and google code style

This commit is contained in:
Florian Sylvain
2025-05-13 22:58:04 +02:00
parent 7f0a191855
commit e25d32f8c6
23 changed files with 949 additions and 620 deletions
+280
View File
@@ -0,0 +1,280 @@
---
Language: Cpp
# BasedOnStyle: Google
AccessModifierOffset: -1
AlignAfterOpenBracket: Align
AlignArrayOfStructures: None
AlignConsecutiveAssignments:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: true
AlignConsecutiveBitFields:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveDeclarations:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveMacros:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveShortCaseStatements:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCaseColons: false
AlignEscapedNewlines: Left
AlignOperands: Align
AlignTrailingComments:
Kind: Always
OverEmptyLines: 0
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowBreakBeforeNoexceptSpecifier: Never
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortCompoundRequirementOnASingleLine: true
AllowShortEnumsOnASingleLine: true
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: WithoutElse
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: Yes
AttributeMacros:
- __capability
BinPackArguments: true
BinPackParameters: true
BitFieldColonSpacing: Both
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterExternBlock: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakAdjacentStringLiterals: true
BreakAfterAttributes: Leave
BreakAfterJavaFieldAnnotations: false
BreakArrays: true
BreakBeforeBinaryOperators: None
BreakBeforeConceptDeclarations: Always
BreakBeforeBraces: Attach
BreakBeforeInlineASMColon: OnlyMultiline
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
BreakStringLiterals: true
ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: true
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IfMacros:
- KJ_IF_MAYBE
IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^<ext/.*\.h>'
Priority: 2
SortPriority: 0
CaseSensitive: false
- Regex: '^<.*\.h>'
Priority: 1
SortPriority: 0
CaseSensitive: false
- Regex: '^<.*'
Priority: 2
SortPriority: 0
CaseSensitive: false
- Regex: '.*'
Priority: 3
SortPriority: 0
CaseSensitive: false
IncludeIsMainRegex: '([-_](test|unittest))?$'
IncludeIsMainSourceRegex: ''
IndentAccessModifiers: false
IndentCaseBlocks: false
IndentCaseLabels: true
IndentExternBlock: AfterExternBlock
IndentGotoLabels: true
IndentPPDirectives: None
IndentRequiresClause: true
IndentWidth: 2
IndentWrappedFunctionNames: false
InsertBraces: false
InsertNewlineAtEOF: false
InsertTrailingCommas: None
IntegerLiteralSeparator:
Binary: 0
BinaryMinDigits: 0
Decimal: 0
DecimalMinDigits: 0
Hex: 0
HexMinDigits: 0
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
KeepEmptyLinesAtEOF: false
LambdaBodyIndentation: Signature
LineEnding: DeriveLF
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Never
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PackConstructorInitializers: NextLine
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakOpenParenthesis: 0
PenaltyBreakScopeResolution: 500
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyIndentedWhitespace: 0
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Left
PPIndentWidth: -1
QualifierAlignment: Leave
RawStringFormats:
- Language: Cpp
Delimiters:
- cc
- CC
- cpp
- Cpp
- CPP
- 'c++'
- 'C++'
CanonicalDelimiter: ''
BasedOnStyle: google
- Language: TextProto
Delimiters:
- pb
- PB
- proto
- PROTO
EnclosingFunctions:
- EqualsProto
- EquivToProto
- PARSE_PARTIAL_TEXT_PROTO
- PARSE_TEST_PROTO
- PARSE_TEXT_PROTO
- ParseTextOrDie
- ParseTextProtoOrDie
- ParseTestProto
- ParsePartialTestProto
CanonicalDelimiter: pb
BasedOnStyle: google
ReferenceAlignment: Pointer
ReflowComments: true
RemoveBracesLLVM: false
RemoveParentheses: Leave
RemoveSemicolon: false
RequiresClausePosition: OwnLine
RequiresExpressionIndentation: OuterScope
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 1
SkipMacroDefinitionBody: false
SortIncludes: CaseSensitive
SortJavaStaticImport: Before
SortUsingDeclarations: LexicographicNumeric
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceAroundPointerQualifiers: Default
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeJsonColon: false
SpaceBeforeParens: ControlStatements
SpaceBeforeParensOptions:
AfterControlStatements: true
AfterForeachMacros: true
AfterFunctionDefinitionName: false
AfterFunctionDeclarationName: false
AfterIfMacros: true
AfterOverloadedOperator: false
AfterPlacementOperator: true
AfterRequiresInClause: false
AfterRequiresInExpression: false
BeforeNonEmptyParentheses: false
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: Never
SpacesInContainerLiterals: true
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParens: Never
SpacesInParensOptions:
InCStyleCasts: false
InConditionalStatements: false
InEmptyParentheses: false
Other: false
SpacesInSquareBrackets: false
Standard: Auto
StatementAttributeLikeMacros:
- Q_EMIT
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 8
UseTab: Never
VerilogBreakBetweenInstancePorts: true
WhitespaceSensitiveMacros:
- BOOST_PP_STRINGIZE
- CF_SWIFT_NAME
- NS_SWIFT_NAME
- PP_STRINGIZE
- STRINGIZE
...
+2 -1
View File
@@ -1,4 +1,5 @@
build build/
cmake-build-debug/
out out
.cache .cache
.idea .idea
+53 -51
View File
@@ -4,12 +4,12 @@
#include "Constants.hpp" #include "Constants.hpp"
Ball::Ball(float radius, const sf::Vector2f& pos, const sf::Vector2f& vel, Ball::Ball(const float radius, const sf::Vector2f &pos, const sf::Vector2f &vel,
const sf::Color& color) const sf::Color &color)
: m_radius(radius), : m_velocity(vel),
m_velocity(vel),
m_lastPosition(pos), m_lastPosition(pos),
m_pixelVelocity(vel), m_pixelVelocity(vel),
m_radius(radius),
m_baseColor(color) { m_baseColor(color) {
m_shape.setRadius(radius); m_shape.setRadius(radius);
m_shape.setOrigin(sf::Vector2f(radius, radius)); m_shape.setOrigin(sf::Vector2f(radius, radius));
@@ -17,12 +17,12 @@ Ball::Ball(float radius, const sf::Vector2f& pos, const sf::Vector2f& vel,
m_shape.setFillColor(color); m_shape.setFillColor(color);
} }
void Ball::update(float dt, const sf::Vector2f& windowSize) { void Ball::update(const float dt, const sf::Vector2f &windowSize) {
if (m_atRest) return; if (m_atRest) return;
m_velocity.y += Constants::GRAVITY * dt; m_velocity.y += Constants::GRAVITY * dt;
m_shape.move(m_velocity * dt); m_shape.move(m_velocity * dt);
sf::Vector2f currentPosition = m_shape.getPosition(); const sf::Vector2f currentPosition = m_shape.getPosition();
m_pixelVelocity = (currentPosition - m_lastPosition) / dt; m_pixelVelocity = (currentPosition - m_lastPosition) / dt;
m_lastPosition = currentPosition; m_lastPosition = currentPosition;
@@ -31,59 +31,61 @@ void Ball::update(float dt, const sf::Vector2f& windowSize) {
} }
void Ball::updateColor() { void Ball::updateColor() {
float currentSpeed = const float currentSpeed =
std::sqrt(m_velocity.x * m_velocity.x + m_velocity.y * m_velocity.y); std::sqrt(m_velocity.x * m_velocity.x + m_velocity.y * m_velocity.y);
static float lastSpeed = 0.0f; static float lastSpeed = 0.0f;
if (std::abs(currentSpeed - lastSpeed) < 10.0f) { if (std::abs(currentSpeed - lastSpeed) < 10.0f) return;
return;
}
lastSpeed = currentSpeed; lastSpeed = currentSpeed;
float speed = const float speed =
std::sqrt(m_velocity.x * m_velocity.x + m_velocity.y * m_velocity.y); std::sqrt(m_velocity.x * m_velocity.x + m_velocity.y * m_velocity.y);
const float t = std::min(speed / 2000.0f, 1.0f);
const float MAX_SPEED = 2000.0f;
float t = std::min(speed / MAX_SPEED, 1.0f);
sf::Color targetColor; sf::Color targetColor;
if (t < 0.33f) { // Red to orange if (t < 0.33f) {
float scaledT = t * 3.0f; // Red to orange
const float scaledT = t * 3.0f;
targetColor.r = static_cast<std::uint8_t>(255); targetColor.r = static_cast<std::uint8_t>(255);
targetColor.g = static_cast<std::uint8_t>(0 + scaledT * 165); targetColor.g = static_cast<std::uint8_t>(0 + scaledT * 165);
targetColor.b = static_cast<std::uint8_t>(0); targetColor.b = static_cast<std::uint8_t>(0);
} else if (t < 0.66f) { // Orange to yellow } else if (t < 0.66f) {
float scaledT = (t - 0.33f) * 3.0f; // Orange to yellow
const float scaledT = (t - 0.33f) * 3.0f;
targetColor.r = static_cast<std::uint8_t>(255); targetColor.r = static_cast<std::uint8_t>(255);
targetColor.g = static_cast<std::uint8_t>(165 + scaledT * 90); targetColor.g = static_cast<std::uint8_t>(165 + scaledT * 90);
targetColor.b = static_cast<std::uint8_t>(0); targetColor.b = static_cast<std::uint8_t>(0);
} else { // Yellow to white } else {
float scaledT = (t - 0.66f) * 3.0f; // Yellow to white
const float scaledT = (t - 0.66f) * 3.0f;
targetColor.r = static_cast<std::uint8_t>(255); targetColor.r = static_cast<std::uint8_t>(255);
targetColor.g = static_cast<std::uint8_t>(255); targetColor.g = static_cast<std::uint8_t>(255);
targetColor.b = static_cast<std::uint8_t>(0 + scaledT * 255); targetColor.b = static_cast<std::uint8_t>(0 + scaledT * 255);
} }
sf::Color currentColor = m_shape.getFillColor(); const sf::Color currentColor = m_shape.getFillColor();
const float TRANSITION_SPEED = 0.05f; constexpr float TRANSITION_SPEED = 0.05f;
sf::Color newColor; sf::Color newColor;
newColor.r = static_cast<std::uint8_t>( newColor.r = static_cast<std::uint8_t>(static_cast<float>(currentColor.r) +
currentColor.r + TRANSITION_SPEED * (targetColor.r - currentColor.r)); TRANSITION_SPEED *
newColor.g = static_cast<std::uint8_t>( (targetColor.r - currentColor.r));
currentColor.g + TRANSITION_SPEED * (targetColor.g - currentColor.g)); newColor.g = static_cast<std::uint8_t>(static_cast<float>(currentColor.g) +
newColor.b = static_cast<std::uint8_t>( TRANSITION_SPEED *
currentColor.b + TRANSITION_SPEED * (targetColor.b - currentColor.b)); (targetColor.g - currentColor.g));
newColor.b = static_cast<std::uint8_t>(static_cast<float>(currentColor.b) +
TRANSITION_SPEED *
(targetColor.b - currentColor.b));
newColor.a = 255; newColor.a = 255;
m_shape.setFillColor(newColor); m_shape.setFillColor(newColor);
} }
void Ball::draw(sf::RenderWindow& window) { window.draw(m_shape); } void Ball::draw(sf::RenderWindow &window) { window.draw(m_shape); }
void Ball::applyImpulse(const sf::Vector2f& impulse) { void Ball::applyImpulse(const sf::Vector2f &impulse) {
m_velocity += impulse; m_velocity += impulse;
m_atRest = false; m_atRest = false;
} }
@@ -94,12 +96,13 @@ sf::Vector2f Ball::getVelocity() const { return m_velocity; }
bool Ball::isAtRest() const { return m_atRest; } bool Ball::isAtRest() const { return m_atRest; }
void Ball::handleWallCollision(const sf::Vector2f& windowSize) { void Ball::handleWallCollision(const sf::Vector2f &windowSize) {
sf::Vector2f pos = m_shape.getPosition(); sf::Vector2f pos = m_shape.getPosition();
auto handleAxis = [&](int axis, float min, float max, float& velocity, auto handleAxis = [&](const int axis, const float min, const float max,
float radius, float restitution) { float &velocity, const float radius,
float value = (axis == 0) ? pos.x : pos.y; const float restitution) {
float value = axis == 0 ? pos.x : pos.y;
if (value - radius < min) { if (value - radius < min) {
value = min + radius; value = min + radius;
@@ -133,31 +136,30 @@ void Ball::handleWallCollision(const sf::Vector2f& windowSize) {
m_shape.setPosition(pos); m_shape.setPosition(pos);
} }
void Ball::resolveCollision(Ball& other) { void Ball::resolveCollision(Ball &other) {
std::lock_guard<std::mutex> lockA(m_mutex); std::lock_guard lockA(m_mutex);
std::lock_guard<std::mutex> lockB(other.m_mutex); std::lock_guard lockB(other.m_mutex);
sf::Vector2f posA = getPosition(); const sf::Vector2f posA = getPosition();
sf::Vector2f posB = other.getPosition(); const sf::Vector2f posB = other.getPosition();
sf::Vector2f delta = posB - posA; const sf::Vector2f delta = posB - posA;
float dist = std::sqrt(delta.x * delta.x + delta.y * delta.y); const float dist = std::sqrt(delta.x * delta.x + delta.y * delta.y);
float minDist = m_radius + other.m_radius; const float minDist = m_radius + other.m_radius;
if (dist >= minDist || dist < 1e-6f) return; if (dist >= minDist || dist < 1e-6f) return;
sf::Vector2f normal = delta / dist; const sf::Vector2f normal = delta / dist;
float overlap = minDist - dist; const float overlap = minDist - dist;
m_shape.move(-normal * (overlap / 2.f)); m_shape.move(-normal * (overlap / 2.f));
other.m_shape.move(normal * (overlap / 2.f)); other.m_shape.move(normal * (overlap / 2.f));
sf::Vector2f vA = m_velocity; const sf::Vector2f vA = m_velocity;
sf::Vector2f vB = other.m_velocity; const sf::Vector2f vB = other.m_velocity;
float vA_n = vA.x * normal.x + vA.y * normal.y; const float vA_n = vA.x * normal.x + vA.y * normal.y;
float vB_n = vB.x * normal.x + vB.y * normal.y; const float vB_n = vB.x * normal.x + vB.y * normal.y;
float restitution = 0.95f; const float vA_n_new = vB_n * Constants::RESTITUTION;
float vA_n_new = vB_n * restitution; const float vB_n_new = vA_n * Constants::RESTITUTION;
float vB_n_new = vA_n * restitution;
m_velocity += (vA_n_new - vA_n) * normal; m_velocity += (vA_n_new - vA_n) * normal;
other.m_velocity += (vB_n_new - vB_n) * normal; other.m_velocity += (vB_n_new - vB_n) * normal;
+15 -9
View File
@@ -4,8 +4,7 @@
#include "PhysicalObject.hpp" #include "PhysicalObject.hpp"
class Ball : public PhysicalObject { class Ball final : public PhysicalObject {
private:
// Position and physics data // Position and physics data
sf::Vector2f m_velocity; sf::Vector2f m_velocity;
sf::Vector2f m_lastPosition; sf::Vector2f m_lastPosition;
@@ -20,21 +19,28 @@ class Ball : public PhysicalObject {
bool m_atRest = false; bool m_atRest = false;
mutable std::mutex m_mutex; mutable std::mutex m_mutex;
void handleWallCollision(const sf::Vector2f& windowSize); void handleWallCollision(const sf::Vector2f &windowSize);
void updateColor(); void updateColor();
public: public:
Ball(float radius, const sf::Vector2f& pos, const sf::Vector2f& vel, Ball(float radius, const sf::Vector2f &pos, const sf::Vector2f &vel,
const sf::Color& color); const sf::Color &color);
void update(float dt, const sf::Vector2f& windowSize) override; void update(float dt, const sf::Vector2f &windowSize) override;
void draw(sf::RenderWindow& window) override;
void applyImpulse(const sf::Vector2f& impulse) override; void draw(sf::RenderWindow &window) override;
void resolveCollision(Ball& other);
void applyImpulse(const sf::Vector2f &impulse) override;
void resolveCollision(Ball &other);
sf::Vector2f getPosition() const; sf::Vector2f getPosition() const;
sf::Vector2f getVelocity() const; sf::Vector2f getVelocity() const;
bool isAtRest() const; bool isAtRest() const;
sf::Vector2f getPixelVelocity() const { return m_pixelVelocity; } sf::Vector2f getPixelVelocity() const { return m_pixelVelocity; }
float getRadius() const { return m_radius; } float getRadius() const { return m_radius; }
sf::Color getColor() const { return m_shape.getFillColor(); } sf::Color getColor() const { return m_shape.getFillColor(); }
+19 -16
View File
@@ -1,33 +1,36 @@
#include "BallFactory.hpp" #include "BallFactory.hpp"
#include <cstdlib> #include <random>
#include <ctime>
#include "Constants.hpp" #include "Constants.hpp"
namespace BallFactory { namespace BallFactory {
std::unique_ptr<Ball> generateRandBall() { std::unique_ptr<Ball> generateRandBall() {
float radius = Constants::BALL_RADIUS; static std::random_device rd;
int diameter = static_cast<int>(2 * radius); static std::mt19937 rng(rd());
int maxX = Constants::WIDTH - diameter; constexpr float x1 = Constants::WIDTH - Constants::BALL_RADIUS * 2;
int maxY = Constants::HEIGHT - diameter; constexpr float y1 = Constants::HEIGHT - Constants::BALL_RADIUS * 2;
float x = static_cast<float>((std::rand() % maxX) + radius); std::uniform_real_distribution posXDist(Constants::BALL_RADIUS, x1);
float y = static_cast<float>((std::rand() % maxY) + radius); std::uniform_real_distribution posYDist(Constants::BALL_RADIUS, y1);
float vel = static_cast<float>((std::rand() % 401) - 200); std::uniform_real_distribution velDist(-200.0f, 200.0f);
std::uniform_int_distribution colorDist(64, 255);
auto randColorComp = []() { float radius = Constants::BALL_RADIUS;
return static_cast<std::uint8_t>(64 + (std::rand() % (256 - 64))); const float x = posXDist(rng);
}; const float y = posYDist(rng);
sf::Color color(randColorComp(), randColorComp(), randColorComp()); const float vel = velDist(rng);
sf::Color color(static_cast<std::uint8_t>(colorDist(rng)),
static_cast<std::uint8_t>(colorDist(rng)),
static_cast<std::uint8_t>(colorDist(rng)));
auto position = sf::Vector2f(x, y); auto position = sf::Vector2f(x, y);
auto velocity = sf::Vector2f(vel, vel); auto velocity = sf::Vector2f(vel, vel);
return std::make_unique<Ball>(radius, position, velocity, color); return std::make_unique<Ball>(radius, position, velocity, color);
} }
std::vector<std::unique_ptr<Ball>> generateBalls() { std::vector<std::unique_ptr<Ball> > generateBalls() {
std::vector<std::unique_ptr<Ball>> balls; std::vector<std::unique_ptr<Ball> > balls;
std::srand(static_cast<unsigned>(std::time(nullptr)));
for (int i = 0; i < Constants::BALL_QUANTITY; ++i) { for (int i = 0; i < Constants::BALL_QUANTITY; ++i) {
auto ball = generateRandBall(); auto ball = generateRandBall();
+2 -1
View File
@@ -6,5 +6,6 @@
namespace BallFactory { namespace BallFactory {
std::unique_ptr<Ball> generateRandBall(); std::unique_ptr<Ball> generateRandBall();
std::vector<std::unique_ptr<Ball>> generateBalls();
std::vector<std::unique_ptr<Ball> > generateBalls();
} // namespace BallFactory } // namespace BallFactory
+23 -23
View File
@@ -5,24 +5,24 @@
#include <cmath> #include <cmath>
BatchRenderer::BatchRenderer() : m_vertices(sf::PrimitiveType::Triangles) { BatchRenderer::BatchRenderer() : m_vertices(sf::PrimitiveType::Triangles) {
const unsigned int size = 64; constexpr unsigned int size = 64;
sf::Image image(sf::Vector2u(size, size), sf::Color::Transparent); sf::Image image(sf::Vector2u(size, size), sf::Color::Transparent);
unsigned int radius = size / 2; constexpr unsigned int radius = size / 2;
sf::Vector2u center(radius, radius); constexpr sf::Vector2u center(radius, radius);
for (unsigned int y = 0; y < size; ++y) { for (unsigned int y = 0; y < size; ++y) {
for (unsigned int x = 0; x < size; ++x) { for (unsigned int x = 0; x < size; ++x) {
sf::Vector2u pixel(x, y); const sf::Vector2u pixel(x, y);
int dx = static_cast<int>(center.x) - static_cast<int>(pixel.x); const int dx = static_cast<int>(center.x) - static_cast<int>(pixel.x);
int dy = static_cast<int>(center.y) - static_cast<int>(pixel.y); const int dy = static_cast<int>(center.y) - static_cast<int>(pixel.y);
float distance = std::sqrt(dx * dx + dy * dy);
if (distance <= radius) { if (const auto distance =
static_cast<float>(std::sqrt(dx * dx + dy * dy));
distance <= radius) {
float alpha = 255.0f; float alpha = 255.0f;
if (distance > radius - 2.0f) { if (distance > radius - 2.0f) {
alpha = alpha = 255.0f * (1.0f - (distance - (radius - 2.0f)) / 2.0f);
255.0f * (1.0f - (distance - (radius - 2.0f)) / 2.0f);
} }
image.setPixel( image.setPixel(
sf::Vector2u(x, y), sf::Vector2u(x, y),
@@ -38,10 +38,10 @@ BatchRenderer::BatchRenderer() : m_vertices(sf::PrimitiveType::Triangles) {
void BatchRenderer::clear() { m_vertices.clear(); } void BatchRenderer::clear() { m_vertices.clear(); }
void BatchRenderer::addBall(const Ball& ball) { void BatchRenderer::addBall(const Ball &ball) {
sf::Vector2f position = ball.getPosition(); const sf::Vector2f position = ball.getPosition();
float radius = ball.getRadius(); const float radius = ball.getRadius();
sf::Color color = ball.getColor(); const sf::Color color = ball.getColor();
sf::Vertex topLeft; sf::Vertex topLeft;
sf::Vertex topRight; sf::Vertex topRight;
@@ -50,16 +50,16 @@ void BatchRenderer::addBall(const Ball& ball) {
topLeft.position = sf::Vector2f(position.x - radius, position.y - radius); topLeft.position = sf::Vector2f(position.x - radius, position.y - radius);
topRight.position = sf::Vector2f(position.x + radius, position.y - radius); topRight.position = sf::Vector2f(position.x + radius, position.y - radius);
bottomRight.position = bottomRight.position = sf::Vector2f(position.x + radius, position.y + radius);
sf::Vector2f(position.x + radius, position.y + radius); bottomLeft.position = sf::Vector2f(position.x - radius, position.y + radius);
bottomLeft.position =
sf::Vector2f(position.x - radius, position.y + radius); const float xSize = static_cast<float>(m_circleTexture.getSize().x);
const float ySize = static_cast<float>(m_circleTexture.getSize().y);
topLeft.texCoords = sf::Vector2f(0, 0); topLeft.texCoords = sf::Vector2f(0, 0);
topRight.texCoords = sf::Vector2f(m_circleTexture.getSize().x, 0); topRight.texCoords = sf::Vector2f(xSize, 0);
bottomRight.texCoords = bottomRight.texCoords = sf::Vector2f(xSize, ySize);
sf::Vector2f(m_circleTexture.getSize().x, m_circleTexture.getSize().y); bottomLeft.texCoords = sf::Vector2f(0, ySize);
bottomLeft.texCoords = sf::Vector2f(0, m_circleTexture.getSize().y);
topLeft.color = color; topLeft.color = color;
topRight.color = color; topRight.color = color;
@@ -75,7 +75,7 @@ void BatchRenderer::addBall(const Ball& ball) {
m_vertices.append(bottomLeft); m_vertices.append(bottomLeft);
} }
void BatchRenderer::draw(sf::RenderWindow& window) { void BatchRenderer::draw(sf::RenderWindow &window) const {
if (m_vertices.getVertexCount() == 0) return; if (m_vertices.getVertexCount() == 0) return;
sf::RenderStates states; sf::RenderStates states;
+5 -4
View File
@@ -2,18 +2,19 @@
#include <SFML/Graphics/RenderWindow.hpp> #include <SFML/Graphics/RenderWindow.hpp>
#include <SFML/Graphics/Texture.hpp> #include <SFML/Graphics/Texture.hpp>
#include <SFML/Graphics/VertexArray.hpp> #include <SFML/Graphics/VertexArray.hpp>
#include <vector>
#include "Ball.hpp" #include "Ball.hpp"
class BatchRenderer { class BatchRenderer {
private:
sf::VertexArray m_vertices; sf::VertexArray m_vertices;
sf::Texture m_circleTexture; sf::Texture m_circleTexture;
public: public:
BatchRenderer(); BatchRenderer();
void clear(); void clear();
void addBall(const Ball& ball);
void draw(sf::RenderWindow& window); void addBall(const Ball &ball);
void draw(sf::RenderWindow &window) const;
}; };
+2 -2
View File
@@ -1,8 +1,8 @@
#pragma once #pragma once
namespace Constants { namespace Constants {
constexpr unsigned WIDTH = 1920; constexpr int WIDTH = 1920;
constexpr unsigned HEIGHT = 1080; constexpr int HEIGHT = 1080;
constexpr float GRAVITY = 781.f; constexpr float GRAVITY = 781.f;
constexpr float RESTITUTION = 0.8f; constexpr float RESTITUTION = 0.8f;
constexpr float FRICTION = 0.9f; constexpr float FRICTION = 0.9f;
+19 -18
View File
@@ -6,41 +6,42 @@
#include "VectorMath.hpp" #include "VectorMath.hpp"
float clamp(float value, float minVal, float maxVal) { float clamp(const float value, const float minVal, const float maxVal) {
return std::max(minVal, std::min(value, maxVal)); return std::max(minVal, std::min(value, maxVal));
} }
void DebugDraw::addLine(sf::VertexArray& lines, const sf::Vector2f& start, void DebugDraw::addLine(sf::VertexArray &lines, const sf::Vector2f &start,
const sf::Vector2f& direction, float length, const sf::Vector2f &direction, const float length,
const sf::Color& color) { const sf::Color &color) {
sf::Vector2f endPoint = start + VectorMath::normalize(direction) * length; const sf::Vector2f endPoint =
start + VectorMath::normalize(direction) * length;
lines.append(sf::Vertex({start, color})); lines.append(sf::Vertex({start, color}));
lines.append(sf::Vertex({endPoint, color})); lines.append(sf::Vertex({endPoint, color}));
} }
void DebugDraw::addDirectionLine(sf::VertexArray& lines, const Ball* ball, void DebugDraw::addDirectionLine(sf::VertexArray &lines, const Ball *ball,
const sf::RenderWindow& window) { const sf::RenderWindow &window) {
sf::Vector2f ballCenter = ball->getPosition(); const sf::Vector2f ballCenter = ball->getPosition();
sf::Vector2i mousePixel = sf::Mouse::getPosition(window); const sf::Vector2i mousePixel = sf::Mouse::getPosition(window);
sf::Vector2f mouseWorld(static_cast<float>(mousePixel.x), const sf::Vector2f mouseWorld(static_cast<float>(mousePixel.x),
static_cast<float>(mousePixel.y)); static_cast<float>(mousePixel.y));
sf::Vector2f dir = mouseWorld - ballCenter; const sf::Vector2f dir = mouseWorld - ballCenter;
addLine(lines, ballCenter, dir, 100.f, sf::Color::Green); addLine(lines, ballCenter, dir, 100.f, sf::Color::Green);
} }
void DebugDraw::addVelocityLine(sf::VertexArray& lines, const Ball* ball) { void DebugDraw::addVelocityLine(sf::VertexArray &lines, const Ball *ball) {
if (ball->isAtRest()) return; if (ball->isAtRest()) return;
sf::Vector2f ballCenter = ball->getPosition(); const sf::Vector2f ballCenter = ball->getPosition();
sf::Vector2f velocity = ball->getVelocity(); const sf::Vector2f velocity = ball->getVelocity();
float velLength = VectorMath::length(velocity); const float velLength = VectorMath::length(velocity);
float clampedLength = std::max(0.f, std::min(velLength, 100.f)); const float clampedLength = std::max(0.f, std::min(velLength, 100.f));
addLine(lines, ballCenter, velocity, clampedLength, sf::Color::Red); addLine(lines, ballCenter, velocity, clampedLength, sf::Color::Red);
} }
void DebugDraw::drawBatchedLines(sf::RenderWindow& window, void DebugDraw::drawBatchedLines(sf::RenderWindow &window,
const sf::VertexArray& lines) { const sf::VertexArray &lines) {
if (lines.getVertexCount() > 0) window.draw(lines); if (lines.getVertexCount() > 0) window.draw(lines);
} }
+11 -8
View File
@@ -7,12 +7,15 @@
class DebugDraw { class DebugDraw {
public: public:
static void addLine(sf::VertexArray& lines, const sf::Vector2f& start, static void addLine(sf::VertexArray &lines, const sf::Vector2f &start,
const sf::Vector2f& direction, float length, const sf::Vector2f &direction, float length,
const sf::Color& color); const sf::Color &color);
static void addDirectionLine(sf::VertexArray& lines, const Ball* ball,
const sf::RenderWindow& window); static void addDirectionLine(sf::VertexArray &lines, const Ball *ball,
static void addVelocityLine(sf::VertexArray& lines, const Ball* ball); const sf::RenderWindow &window);
static void drawBatchedLines(sf::RenderWindow& window,
const sf::VertexArray& lines); static void addVelocityLine(sf::VertexArray &lines, const Ball *ball);
static void drawBatchedLines(sf::RenderWindow &window,
const sf::VertexArray &lines);
}; };
+10 -9
View File
@@ -2,25 +2,26 @@
#include <sstream> #include <sstream>
DebugOverlay::DebugOverlay(const std::string& fontPath) DebugOverlay::DebugOverlay(const std::string &fontPath)
: m_font(fontPath), m_text(m_font) { : m_font(fontPath), m_text(m_font) {
m_text.setCharacterSize(18); m_text.setCharacterSize(18);
m_text.setFillColor(sf::Color::White); m_text.setFillColor(sf::Color::White);
m_text.setPosition(sf::Vector2f(5.f, 5.f)); m_text.setPosition(sf::Vector2f(5.f, 5.f));
} }
void DebugOverlay::update(int drawCalls, float timeScale, void DebugOverlay::update(const int drawCalls, const float timeScale,
sf::RenderWindow& window, size_t threadCount, const sf::RenderWindow &window,
size_t ballsPerThread) { const size_t threadCount,
float elapsed = m_fpsClock.restart().asSeconds(); const size_t ballsPerThread) {
if (elapsed > 0.f) m_fps = static_cast<int>(1.f / elapsed); const float elapsed = m_fpsClock.restart().asSeconds();
if (elapsed > 0.2f) m_fps = 1.f / elapsed;
sf::Vector2i mousePos = sf::Mouse::getPosition(window); const sf::Vector2i mousePos = sf::Mouse::getPosition(window);
std::ostringstream oss; std::ostringstream oss;
oss << "Draw calls: " << drawCalls << "\n"; oss << "Draw calls: " << drawCalls << "\n";
oss << "Framerate: " << m_fps << " FPS\n"; oss << "Framerate: " << m_fps << " FPS\n";
oss << "Frametime: " << (elapsed * 1000.f) << " ms\n"; oss << "Frame time: " << (elapsed * 1000.f) << " ms\n";
oss << "Mouse: " << mousePos.x << ", " << mousePos.y << "\n"; oss << "Mouse: " << mousePos.x << ", " << mousePos.y << "\n";
oss << "Time scale: " << timeScale << "\n"; oss << "Time scale: " << timeScale << "\n";
oss << "\nThreads: " << threadCount; oss << "\nThreads: " << threadCount;
@@ -28,4 +29,4 @@ void DebugOverlay::update(int drawCalls, float timeScale,
m_text.setString(oss.str()); m_text.setString(oss.str());
} }
void DebugOverlay::draw(sf::RenderWindow& window) { window.draw(m_text); } void DebugOverlay::draw(sf::RenderWindow &window) const { window.draw(m_text); }
+4 -3
View File
@@ -7,11 +7,12 @@
class DebugOverlay { class DebugOverlay {
public: public:
DebugOverlay(const std::string& fontPath); explicit DebugOverlay(const std::string &fontPath);
void update(int drawCalls, float timeScale, sf::RenderWindow& window, void update(int drawCalls, float timeScale, const sf::RenderWindow &window,
size_t threadCount, size_t ballsPerThread); size_t threadCount, size_t ballsPerThread);
void draw(sf::RenderWindow& window);
void draw(sf::RenderWindow &window) const;
private: private:
sf::Font m_font; sf::Font m_font;
+80 -73
View File
@@ -4,24 +4,25 @@
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <thread> #include <thread>
#include <tuple>
#include <unordered_map> #include <unordered_map>
#include "BallFactory.hpp" #include "BallFactory.hpp"
#include "Constants.hpp" #include "Constants.hpp"
#include "DebugDraw.hpp" #include "DebugDraw.hpp"
#include "ThreadUtils.hpp"
#include "VectorMath.hpp" #include "VectorMath.hpp"
Game::Game() Game::Game()
: m_inputManager( : m_inputManager(
std::bind(&Game::processKeyPressed, this, std::placeholders::_1), [this](auto &&PH1) {
std::bind(&Game::processMousePressed, this, std::placeholders::_1)), processKeyPressed(std::forward<decltype(PH1)>(PH1));
},
[this](auto &&PH1) {
processMousePressed(std::forward<decltype(PH1)>(PH1));
}),
m_debugLines(sf::PrimitiveType::Lines), m_debugLines(sf::PrimitiveType::Lines),
m_drawCallCount(0),
m_debugOverlay("assets/consolas.ttf"), m_debugOverlay("assets/consolas.ttf"),
m_threadPool(std::max(1u, std::thread::hardware_concurrency() > 2 m_threadPool(ThreadUtils::calculateSafeWorkerThreads()) {
? std::thread::hardware_concurrency() - 2
: 1u)) {
m_window.create(sf::VideoMode({Constants::WIDTH, Constants::HEIGHT}), m_window.create(sf::VideoMode({Constants::WIDTH, Constants::HEIGHT}),
"SFML Playground"); "SFML Playground");
m_window.setVerticalSyncEnabled(true); m_window.setVerticalSyncEnabled(true);
@@ -34,37 +35,46 @@ Game::Game()
} }
} }
void Game::processKeyPressed(const sf::Event::KeyPressed &kP) { void Game::processKeyPressed(const sf::Event::KeyPressed &keyPressed) {
if (kP.code == sf::Keyboard::Key::Equal) { switch (keyPressed.code) {
m_timeScale += 0.25f; case sf::Keyboard::Key::Equal:
if (m_timeScale > 10.0f) m_timeScale = 10.0f; m_timeScale = std::min(10.0f, m_timeScale + 0.25f);
} else if (kP.code == sf::Keyboard::Key::Hyphen) { break;
m_timeScale -= 0.25f; case sf::Keyboard::Key::Hyphen:
if (m_timeScale < 0.25f) m_timeScale = 0.25f; m_timeScale = std::max(0.25f, m_timeScale - 0.25f);
} break;
case sf::Keyboard::Key::D:
if (kP.code == sf::Keyboard::Key::D) {
m_toggleDebug = !m_toggleDebug; m_toggleDebug = !m_toggleDebug;
break;
default:
break;
} }
} }
void Game::processMousePressed(const sf::Event::MouseButtonPressed &mP) { void Game::processMousePressed(
sf::Vector2f mousePos(static_cast<float>(mP.position.x), const sf::Event::MouseButtonPressed &mousePressed) {
static_cast<float>(mP.position.y)); const sf::Vector2f mousePos(static_cast<float>(mousePressed.position.x),
if (mP.button == sf::Mouse::Button::Left) impulseBalls(mousePos); static_cast<float>(mousePressed.position.y));
if (mP.button == sf::Mouse::Button::Right) spawnBall(mousePos); switch (mousePressed.button) {
case sf::Mouse::Button::Left:
impulseBalls(mousePos);
break;
case sf::Mouse::Button::Right:
spawnBall(mousePos);
break;
default:
break;
}
} }
void Game::spawnBall(const sf::Vector2f &mousePos) { void Game::spawnBall(const sf::Vector2f &mousePos) {
auto newBallPtr = std::make_unique<Ball>( auto newBallPtr =
Constants::BALL_RADIUS, std::make_unique<Ball>(Constants::BALL_RADIUS, mousePos,
mousePos, sf::Vector2f(0.f, 0.f), sf::Color::Black);
sf::Vector2f(0.f, 0.f),
sf::Color::Black);
m_objects.push_back(std::move(newBallPtr)); m_objects.push_back(std::move(newBallPtr));
} }
void Game::impulseBalls(const sf::Vector2f &mousePos) { void Game::impulseBalls(const sf::Vector2f &mousePos) const {
for (auto &object : m_objects) { for (auto &object : m_objects) {
auto *ball = dynamic_cast<Ball *>(object.get()); auto *ball = dynamic_cast<Ball *>(object.get());
if (!ball) continue; if (!ball) continue;
@@ -74,20 +84,21 @@ void Game::impulseBalls(const sf::Vector2f &mousePos) {
} }
} }
Game::Grid Game::buildSpatialGrid() { Game::Grid Game::buildSpatialGrid() const {
const float cellSize = 2 * Constants::BALL_RADIUS; constexpr float safeCellSize = std::max(2 * Constants::BALL_RADIUS, 0.001f);
const float safeCellSize = std::max(cellSize, 0.001f);
Grid grid; Grid grid;
for (auto &object : m_objects) { for (auto &object : m_objects) {
if (Ball *ball = dynamic_cast<Ball *>(object.get())) { auto ball = dynamic_cast<Ball *>(object.get());
if (!ball) continue;
const sf::Vector2f &pos = ball->getPosition(); const sf::Vector2f &pos = ball->getPosition();
if (std::isfinite(pos.x) && std::isfinite(pos.y)) { if (!std::isfinite(pos.x) || !std::isfinite(pos.y)) continue;
int cellX = static_cast<int>(std::floor(pos.x / safeCellSize)); int cellX = static_cast<int>(std::floor(pos.x / safeCellSize));
int cellY = static_cast<int>(std::floor(pos.y / safeCellSize)); int cellY = static_cast<int>(std::floor(pos.y / safeCellSize));
grid[{cellX, cellY}].push_back(ball); grid[{cellX, cellY}].push_back(ball);
} }
}
}
return grid; return grid;
} }
@@ -99,23 +110,23 @@ void Game::resolveSpatialCollisionsParallel(const Grid &grid) {
std::mutex collisionsMutex; std::mutex collisionsMutex;
for (const auto &[cell, cellBalls] : grid) { for (const auto &[cell, cellBalls] : grid) {
for (const auto &offset : forwardNeighbors) { for (const auto &[firstOffset, secondOffset] : forwardNeighbors) {
Cell neighborCell = {cell.first + offset.first, Cell neighborCell = {cell.first + firstOffset,
cell.second + offset.second}; cell.second + secondOffset};
auto neighborIt = grid.find(neighborCell); auto neighborIt = grid.find(neighborCell);
if (neighborIt == grid.end()) continue; if (neighborIt == grid.end()) continue;
if (neighborCell == cell) { if (neighborCell == cell) {
for (size_t i = 0; i < cellBalls.size(); ++i) { for (size_t i = 0; i < cellBalls.size(); ++i) {
for (size_t j = i + 1; j < cellBalls.size(); ++j) { for (size_t j = i + 1; j < cellBalls.size(); ++j) {
std::lock_guard<std::mutex> lock(collisionsMutex); std::lock_guard lock(collisionsMutex);
collisionPairs.emplace_back(cellBalls[i], cellBalls[j]); collisionPairs.emplace_back(cellBalls[i], cellBalls[j]);
} }
} }
} else { } else {
for (Ball *ballA : cellBalls) { for (Ball *ballA : cellBalls) {
for (Ball *ballB : neighborIt->second) { for (Ball *ballB : neighborIt->second) {
std::lock_guard<std::mutex> lock(collisionsMutex); std::lock_guard lock(collisionsMutex);
collisionPairs.emplace_back(ballA, ballB); collisionPairs.emplace_back(ballA, ballB);
} }
} }
@@ -123,14 +134,15 @@ void Game::resolveSpatialCollisionsParallel(const Grid &grid) {
} }
} }
const size_t chunkSize = std::max( const size_t chunkSize =
size_t(1), collisionPairs.size() / std::thread::hardware_concurrency()); std::max(static_cast<size_t>(1),
collisionPairs.size() / std::thread::hardware_concurrency());
std::vector<std::future<void>> futures; std::vector<std::future<void>> futures;
for (size_t i = 0; i < collisionPairs.size(); i += chunkSize) { for (size_t i = 0; i < collisionPairs.size(); i += chunkSize) {
size_t end = std::min(i + chunkSize, collisionPairs.size()); size_t end = std::min(i + chunkSize, collisionPairs.size());
futures.push_back(m_threadPool.enqueue([&collisionPairs, i, end]() { futures.push_back(m_threadPool.enqueue([&collisionPairs, i, end] {
for (size_t j = i; j < end; ++j) { for (size_t j = i; j < end; ++j) {
auto &[ballA, ballB] = collisionPairs[j]; auto &[ballA, ballB] = collisionPairs[j];
ballA->resolveCollision(*ballB); ballA->resolveCollision(*ballB);
@@ -138,9 +150,7 @@ void Game::resolveSpatialCollisionsParallel(const Grid &grid) {
})); }));
} }
for (auto &future : futures) { for (auto &future : futures) future.get();
future.get();
}
} }
void Game::update() { void Game::update() {
@@ -148,22 +158,20 @@ void Game::update() {
if (dt > 0.1f) dt = 0.1f; if (dt > 0.1f) dt = 0.1f;
updateBallsParallel(dt); updateBallsParallel(dt);
resolveSpatialCollisionsParallel(buildSpatialGrid());
auto grid = buildSpatialGrid();
resolveSpatialCollisionsParallel(grid);
} }
void Game::updateBallsParallel(float dt) { void Game::updateBallsParallel(float dt) {
const size_t chunkSize = std::max( const size_t chunkSize =
size_t(1), m_objects.size() / std::thread::hardware_concurrency()); std::max(static_cast<size_t>(1),
m_objects.size() / std::thread::hardware_concurrency());
std::vector<std::future<void>> futures; std::vector<std::future<void>> futures;
for (size_t i = 0; i < m_objects.size(); i += chunkSize) { for (size_t i = 0; i < m_objects.size(); i += chunkSize) {
size_t end = std::min(i + chunkSize, m_objects.size()); size_t end = std::min(i + chunkSize, m_objects.size());
futures.push_back(m_threadPool.enqueue([&, i, end, dt]() { futures.push_back(m_threadPool.enqueue([&, i, end, dt] {
for (size_t j = i; j < end; ++j) { for (size_t j = i; j < end; ++j) {
if (auto *ball = dynamic_cast<Ball *>(m_objects[j].get())) { if (auto *ball = dynamic_cast<Ball *>(m_objects[j].get())) {
ball->update(dt, m_windowSize); ball->update(dt, m_windowSize);
@@ -174,24 +182,20 @@ void Game::updateBallsParallel(float dt) {
})); }));
} }
for (auto &future : futures) { for (auto &future : futures) future.get();
future.get();
}
} }
void Game::render() { void Game::render() {
m_window.clear(sf::Color::Black); m_window.clear(sf::Color::Black);
m_debugLines.clear(); m_debugLines.clear();
m_drawCallCount = 0; m_drawCallCount = 0;
m_batchRenderer.clear(); m_batchRenderer.clear();
std::vector<const Ball *> balls; std::vector<const Ball *> balls;
balls.reserve(m_objects.size()); balls.reserve(m_objects.size());
for (const auto &object : m_objects) { for (const auto &object : m_objects) {
if (auto *ball = dynamic_cast<Ball *>(object.get())) { if (const auto *ball = dynamic_cast<Ball *>(object.get())) {
balls.push_back(ball); balls.push_back(ball);
m_batchRenderer.addBall(*ball); m_batchRenderer.addBall(*ball);
} else { } else {
@@ -200,7 +204,7 @@ void Game::render() {
} }
if (!m_toggleDebug) continue; if (!m_toggleDebug) continue;
if (auto *ball = dynamic_cast<Ball *>(object.get())) { if (const auto *ball = dynamic_cast<Ball *>(object.get())) {
DebugDraw::addDirectionLine(m_debugLines, ball, m_window); DebugDraw::addDirectionLine(m_debugLines, ball, m_window);
DebugDraw::addVelocityLine(m_debugLines, ball); DebugDraw::addVelocityLine(m_debugLines, ball);
} }
@@ -211,8 +215,8 @@ void Game::render() {
if (m_toggleDebug) { if (m_toggleDebug) {
DebugDraw::drawBatchedLines(m_window, m_debugLines); DebugDraw::drawBatchedLines(m_window, m_debugLines);
size_t threadCount = m_threadPool.getThreadCount(); const size_t threadCount = m_threadPool.getThreadCount();
size_t ballsPerThread = m_objects.size() / threadCount; const size_t ballsPerThread = m_objects.size() / threadCount;
m_debugOverlay.update(m_drawCallCount + 2, m_timeScale, m_window, m_debugOverlay.update(m_drawCallCount + 2, m_timeScale, m_window,
threadCount, ballsPerThread); threadCount, ballsPerThread);
m_debugOverlay.draw(m_window); m_debugOverlay.draw(m_window);
@@ -221,26 +225,29 @@ void Game::render() {
m_window.display(); m_window.display();
} }
void Game::run() { void Game::handleEvent(const sf::Event &event) {
while (m_window.isOpen()) { if (event.is<sf::Event::Closed>()) {
while (const std::optional event = m_window.pollEvent()) {
if (event->is<sf::Event::Closed>()) {
m_window.close(); m_window.close();
return; return;
} }
if (const auto *resized = event->getIf<sf::Event::Resized>()) { if (const auto *resized = event.getIf<sf::Event::Resized>()) {
sf::Vector2f position(0.f, 0.f); constexpr sf::Vector2f position(0.f, 0.f);
sf::Vector2f size(static_cast<float>(resized->size.x), const sf::Vector2f size(static_cast<float>(resized->size.x),
static_cast<float>(resized->size.y)); static_cast<float>(resized->size.y));
sf::FloatRect visibleArea(position, size); const sf::FloatRect visibleArea(position, size);
m_window.setView(sf::View(visibleArea)); m_window.setView(sf::View(visibleArea));
m_windowSize = size; m_windowSize = size;
} }
if (auto kP = event->getIf<sf::Event::KeyPressed>()) if (const auto kP = event.getIf<sf::Event::KeyPressed>())
processKeyPressed(*kP); processKeyPressed(*kP);
if (auto mP = event->getIf<sf::Event::MouseButtonPressed>()) if (const auto mP = event.getIf<sf::Event::MouseButtonPressed>())
processMousePressed(*mP); processMousePressed(*mP);
} }
void Game::run() {
while (m_window.isOpen()) {
while (const std::optional event = m_window.pollEvent())
handleEvent(*event);
update(); update();
render(); render();
} }
+23 -13
View File
@@ -1,7 +1,6 @@
#pragma once #pragma once
#include <SFML/Graphics/Font.hpp> #include <SFML/Graphics/Font.hpp>
#include <SFML/Graphics/RenderWindow.hpp> #include <SFML/Graphics/RenderWindow.hpp>
#include <SFML/Graphics/Text.hpp>
#include <SFML/Graphics/VertexArray.hpp> #include <SFML/Graphics/VertexArray.hpp>
#include <SFML/System/Clock.hpp> #include <SFML/System/Clock.hpp>
#include <SFML/System/Vector2.hpp> #include <SFML/System/Vector2.hpp>
@@ -21,11 +20,10 @@ class Ball;
class PhysicalObject; class PhysicalObject;
class Game { class Game {
private:
float m_timeScale = 1.f; float m_timeScale = 1.f;
sf::RenderWindow m_window; sf::RenderWindow m_window;
sf::Vector2f m_windowSize; sf::Vector2f m_windowSize;
std::vector<std::unique_ptr<PhysicalObject>> m_objects; std::vector<std::unique_ptr<PhysicalObject> > m_objects;
sf::Clock m_clock; sf::Clock m_clock;
InputManager m_inputManager; InputManager m_inputManager;
sf::VertexArray m_debugLines; sf::VertexArray m_debugLines;
@@ -36,27 +34,39 @@ class Game {
ThreadPool m_threadPool; ThreadPool m_threadPool;
struct CellHash { struct CellHash {
std::size_t operator()(const std::pair<int, int>& k) const { std::size_t operator()(const std::pair<int, int> &k) const {
return static_cast<std::size_t>(k.first) * 73856093 ^ return static_cast<std::size_t>(k.first) * 73856093 ^
static_cast<std::size_t>(k.second) * 19349663; static_cast<std::size_t>(k.second) * 19349663;
} }
}; };
using Cell = std::pair<int, int>; using Cell = std::pair<int, int>;
using Grid = std::unordered_map<Cell, std::vector<Ball*>, CellHash>; using Grid = std::unordered_map<Cell, std::vector<Ball *>, CellHash>;
void processKeyPressed(const sf::Event::KeyPressed &keyPressed);
void processMousePressed(const sf::Event::MouseButtonPressed &mousePressed);
void handleEvent(const sf::Event &event);
void impulseBalls(const sf::Vector2f &mousePos) const;
void spawnBall(const sf::Vector2f &mousePos);
void processKeyPressed(const sf::Event::KeyPressed& keyPressed);
void processMousePressed(const sf::Event::MouseButtonPressed& mousePressed);
void impulseBalls(const sf::Vector2f& mousePos);
void spawnBall(const sf::Vector2f& mousePos);
void update(); void update();
void render();
void updateBallsParallel(float dt);
void resolveSpatialCollisionsParallel(const Grid& grid);
Grid buildSpatialGrid(); void render();
void updateBallsParallel(float dt);
void resolveSpatialCollisionsParallel(const Grid &grid);
Grid buildSpatialGrid() const;
public: public:
Game(); Game();
void run(); void run();
size_t getThreadCount() const { return m_threadPool.getThreadCount(); } size_t getThreadCount() const { return m_threadPool.getThreadCount(); }
}; };
+4 -3
View File
@@ -5,14 +5,15 @@
InputManager::InputManager(KeyCallback keyCb, MouseCallback mouseCb) InputManager::InputManager(KeyCallback keyCb, MouseCallback mouseCb)
: m_keyCallback(std::move(keyCb)), m_mouseCallback(std::move(mouseCb)) {} : m_keyCallback(std::move(keyCb)), m_mouseCallback(std::move(mouseCb)) {}
void InputManager::processEvents(sf::Window& window) { void InputManager::processEvents(sf::Window& window) const {
while (const std::optional event = window.pollEvent()) { while (const std::optional event = window.pollEvent()) {
if (event->is<sf::Event::Closed>()) { if (event->is<sf::Event::Closed>()) {
window.close(); window.close();
return; return;
} }
if (auto kP = event->getIf<sf::Event::KeyPressed>()) m_keyCallback(*kP); if (const auto kP = event->getIf<sf::Event::KeyPressed>())
if (auto mP = event->getIf<sf::Event::MouseButtonPressed>()) m_keyCallback(*kP);
if (const auto mP = event->getIf<sf::Event::MouseButtonPressed>())
m_mouseCallback(*mP); m_mouseCallback(*mP);
} }
} }
+1 -1
View File
@@ -11,7 +11,7 @@ class InputManager {
InputManager(KeyCallback keyCb, MouseCallback mouseCb); InputManager(KeyCallback keyCb, MouseCallback mouseCb);
void processEvents(sf::Window& window); void processEvents(sf::Window& window) const;
private: private:
KeyCallback m_keyCallback; KeyCallback m_keyCallback;
+6 -3
View File
@@ -5,7 +5,10 @@
class PhysicalObject { class PhysicalObject {
public: public:
virtual ~PhysicalObject() = default; virtual ~PhysicalObject() = default;
virtual void update(float dt, const sf::Vector2f& windowSize) = 0;
virtual void draw(sf::RenderWindow& window) = 0; virtual void update(float dt, const sf::Vector2f &windowSize) = 0;
virtual void applyImpulse(const sf::Vector2f& impulse) = 0;
virtual void draw(sf::RenderWindow &window) = 0;
virtual void applyImpulse(const sf::Vector2f &impulse) = 0;
}; };
+18 -19
View File
@@ -10,34 +10,34 @@
class ThreadPool { class ThreadPool {
public: public:
explicit ThreadPool(size_t numThreads); explicit ThreadPool(size_t numThreads);
~ThreadPool(); ~ThreadPool();
size_t getThreadCount() const { return workers.size(); }
[[nodiscard]] size_t getThreadCount() const { return workers.size(); }
template <class F, class... Args> template <class F, class... Args>
auto enqueue(F&& f, Args&&... args) auto enqueue(F &&f, Args &&...args)
-> std::future<typename std::invoke_result<F, Args...>::type>; -> std::future<std::invoke_result_t<F, Args...> >;
private: private:
std::vector<std::thread> workers; std::vector<std::thread> workers;
std::queue<std::function<void()>> tasks; std::queue<std::function<void()> > tasks;
std::mutex queueMutex; std::mutex queueMutex;
std::condition_variable condition; std::condition_variable condition;
bool stop; bool stop;
}; };
inline ThreadPool::ThreadPool(size_t numThreads) : stop(false) { inline ThreadPool::ThreadPool(const size_t numThreads) : stop(false) {
for (size_t i = 0; i < numThreads; ++i) { for (size_t i = 0; i < numThreads; ++i) {
workers.emplace_back([this] { workers.emplace_back([this] {
while (true) { while (true) {
std::function<void()> task; std::function<void()> task;
{ {
std::unique_lock<std::mutex> lock(this->queueMutex); std::unique_lock lock(this->queueMutex);
this->condition.wait(lock, [this] { this->condition.wait(
return this->stop || !this->tasks.empty(); lock, [this] { return this->stop || !this->tasks.empty(); });
});
if (this->stop && this->tasks.empty()) return; if (this->stop && this->tasks.empty()) return;
@@ -53,29 +53,28 @@ inline ThreadPool::ThreadPool(size_t numThreads) : stop(false) {
inline ThreadPool::~ThreadPool() { inline ThreadPool::~ThreadPool() {
{ {
std::unique_lock<std::mutex> lock(queueMutex); std::unique_lock lock(queueMutex);
stop = true; stop = true;
} }
condition.notify_all(); condition.notify_all();
for (auto& worker : workers) worker.join(); for (auto &worker : workers) worker.join();
} }
template <class F, class... Args> template <class F, class... Args>
inline auto ThreadPool::enqueue(F&& f, Args&&... args) auto ThreadPool::enqueue(F &&f, Args &&...args)
-> std::future<typename std::invoke_result<F, Args...>::type> { -> std::future<std::invoke_result_t<F, Args...> > {
using return_type = typename std::invoke_result<F, Args...>::type; using return_type = std::invoke_result_t<F, Args...>;
auto task = std::make_shared<std::packaged_task<return_type()>>( auto task = std::make_shared<std::packaged_task<return_type()> >(
std::bind(std::forward<F>(f), std::forward<Args>(args)...)); std::bind(std::forward<F>(f), std::forward<Args>(args)...));
std::future<return_type> res = task->get_future(); std::future<return_type> res = task->get_future();
{ {
std::unique_lock<std::mutex> lock(queueMutex); std::unique_lock lock(queueMutex);
if (stop) throw std::runtime_error("enqueue on stopped ThreadPool"); if (stop) throw std::runtime_error("enqueue on stopped ThreadPool");
tasks.emplace([task]() { (*task)(); }); tasks.emplace([task] { (*task)(); });
} }
condition.notify_one(); condition.notify_one();
+8
View File
@@ -0,0 +1,8 @@
#include <thread>
namespace ThreadUtils {
inline size_t calculateSafeWorkerThreads(const size_t reserve = 2) {
const size_t total = std::thread::hardware_concurrency();
return total > reserve ? total - reserve : 1;
}
} // namespace ThreadUtils
+6 -6
View File
@@ -3,15 +3,15 @@
#include <cmath> #include <cmath>
namespace VectorMath { namespace VectorMath {
float length(const sf::Vector2f& vector) { float length(const sf::Vector2f &vector) {
return std::sqrt(vector.x * vector.x + vector.y * vector.y); return std::sqrt(vector.x * vector.x + vector.y * vector.y);
} }
sf::Vector2f normalize(const sf::Vector2f& vector) { sf::Vector2f normalize(const sf::Vector2f &vector) {
float squaredLen = vector.x * vector.x + vector.y * vector.y; if (const float squaredLen = vector.x * vector.x + vector.y * vector.y;
if (squaredLen > 0.0001f) { squaredLen > 0.0001f) {
float invLen = 1.0f / std::sqrt(squaredLen); const float invLen = 1.0f / std::sqrt(squaredLen);
return sf::Vector2f(vector.x * invLen, vector.y * invLen); return {vector.x * invLen, vector.y * invLen};
} }
return vector; return vector;
} }
+3 -2
View File
@@ -2,6 +2,7 @@
#include <SFML/System/Vector2.hpp> #include <SFML/System/Vector2.hpp>
namespace VectorMath { namespace VectorMath {
float length(const sf::Vector2f& vector); float length(const sf::Vector2f &vector);
sf::Vector2f normalize(const sf::Vector2f& vector);
sf::Vector2f normalize(const sf::Vector2f &vector);
} // namespace VectorMath } // namespace VectorMath