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
.cache
.idea
+48 -46
View File
@@ -4,12 +4,12 @@
#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)
: m_radius(radius),
m_velocity(vel),
: m_velocity(vel),
m_lastPosition(pos),
m_pixelVelocity(vel),
m_radius(radius),
m_baseColor(color) {
m_shape.setRadius(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);
}
void Ball::update(float dt, const sf::Vector2f& windowSize) {
void Ball::update(const float dt, const sf::Vector2f &windowSize) {
if (m_atRest) return;
m_velocity.y += Constants::GRAVITY * 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_lastPosition = currentPosition;
@@ -31,51 +31,53 @@ void Ball::update(float dt, const sf::Vector2f& windowSize) {
}
void Ball::updateColor() {
float currentSpeed =
const float currentSpeed =
std::sqrt(m_velocity.x * m_velocity.x + m_velocity.y * m_velocity.y);
static float lastSpeed = 0.0f;
if (std::abs(currentSpeed - lastSpeed) < 10.0f) {
return;
}
if (std::abs(currentSpeed - lastSpeed) < 10.0f) return;
lastSpeed = currentSpeed;
float speed =
const float speed =
std::sqrt(m_velocity.x * m_velocity.x + m_velocity.y * m_velocity.y);
const float MAX_SPEED = 2000.0f;
float t = std::min(speed / MAX_SPEED, 1.0f);
const float t = std::min(speed / 2000.0f, 1.0f);
sf::Color targetColor;
if (t < 0.33f) { // Red to orange
float scaledT = t * 3.0f;
if (t < 0.33f) {
// Red to orange
const float scaledT = t * 3.0f;
targetColor.r = static_cast<std::uint8_t>(255);
targetColor.g = static_cast<std::uint8_t>(0 + scaledT * 165);
targetColor.b = static_cast<std::uint8_t>(0);
} else if (t < 0.66f) { // Orange to yellow
float scaledT = (t - 0.33f) * 3.0f;
} else if (t < 0.66f) {
// Orange to yellow
const float scaledT = (t - 0.33f) * 3.0f;
targetColor.r = static_cast<std::uint8_t>(255);
targetColor.g = static_cast<std::uint8_t>(165 + scaledT * 90);
targetColor.b = static_cast<std::uint8_t>(0);
} else { // Yellow to white
float scaledT = (t - 0.66f) * 3.0f;
} else {
// Yellow to white
const float scaledT = (t - 0.66f) * 3.0f;
targetColor.r = static_cast<std::uint8_t>(255);
targetColor.g = static_cast<std::uint8_t>(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;
newColor.r = static_cast<std::uint8_t>(
currentColor.r + TRANSITION_SPEED * (targetColor.r - currentColor.r));
newColor.g = static_cast<std::uint8_t>(
currentColor.g + TRANSITION_SPEED * (targetColor.g - currentColor.g));
newColor.b = static_cast<std::uint8_t>(
currentColor.b + TRANSITION_SPEED * (targetColor.b - currentColor.b));
newColor.r = static_cast<std::uint8_t>(static_cast<float>(currentColor.r) +
TRANSITION_SPEED *
(targetColor.r - currentColor.r));
newColor.g = static_cast<std::uint8_t>(static_cast<float>(currentColor.g) +
TRANSITION_SPEED *
(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;
m_shape.setFillColor(newColor);
@@ -97,9 +99,10 @@ bool Ball::isAtRest() const { return m_atRest; }
void Ball::handleWallCollision(const sf::Vector2f &windowSize) {
sf::Vector2f pos = m_shape.getPosition();
auto handleAxis = [&](int axis, float min, float max, float& velocity,
float radius, float restitution) {
float value = (axis == 0) ? pos.x : pos.y;
auto handleAxis = [&](const int axis, const float min, const float max,
float &velocity, const float radius,
const float restitution) {
float value = axis == 0 ? pos.x : pos.y;
if (value - radius < min) {
value = min + radius;
@@ -134,30 +137,29 @@ void Ball::handleWallCollision(const sf::Vector2f& windowSize) {
}
void Ball::resolveCollision(Ball &other) {
std::lock_guard<std::mutex> lockA(m_mutex);
std::lock_guard<std::mutex> lockB(other.m_mutex);
std::lock_guard lockA(m_mutex);
std::lock_guard lockB(other.m_mutex);
sf::Vector2f posA = getPosition();
sf::Vector2f posB = other.getPosition();
sf::Vector2f delta = posB - posA;
float dist = std::sqrt(delta.x * delta.x + delta.y * delta.y);
float minDist = m_radius + other.m_radius;
const sf::Vector2f posA = getPosition();
const sf::Vector2f posB = other.getPosition();
const sf::Vector2f delta = posB - posA;
const float dist = std::sqrt(delta.x * delta.x + delta.y * delta.y);
const float minDist = m_radius + other.m_radius;
if (dist >= minDist || dist < 1e-6f) return;
sf::Vector2f normal = delta / dist;
float overlap = minDist - dist;
const sf::Vector2f normal = delta / dist;
const float overlap = minDist - dist;
m_shape.move(-normal * (overlap / 2.f));
other.m_shape.move(normal * (overlap / 2.f));
sf::Vector2f vA = m_velocity;
sf::Vector2f vB = other.m_velocity;
float vA_n = vA.x * normal.x + vA.y * normal.y;
float vB_n = vB.x * normal.x + vB.y * normal.y;
const sf::Vector2f vA = m_velocity;
const sf::Vector2f vB = other.m_velocity;
const float vA_n = vA.x * normal.x + vA.y * normal.y;
const float vB_n = vB.x * normal.x + vB.y * normal.y;
float restitution = 0.95f;
float vA_n_new = vB_n * restitution;
float vB_n_new = vA_n * restitution;
const float vA_n_new = vB_n * Constants::RESTITUTION;
const float vB_n_new = vA_n * Constants::RESTITUTION;
m_velocity += (vA_n_new - vA_n) * normal;
other.m_velocity += (vB_n_new - vB_n) * normal;
+8 -2
View File
@@ -4,8 +4,7 @@
#include "PhysicalObject.hpp"
class Ball : public PhysicalObject {
private:
class Ball final : public PhysicalObject {
// Position and physics data
sf::Vector2f m_velocity;
sf::Vector2f m_lastPosition;
@@ -21,6 +20,7 @@ class Ball : public PhysicalObject {
mutable std::mutex m_mutex;
void handleWallCollision(const sf::Vector2f &windowSize);
void updateColor();
public:
@@ -28,13 +28,19 @@ class Ball : public PhysicalObject {
const sf::Color &color);
void update(float dt, const sf::Vector2f &windowSize) override;
void draw(sf::RenderWindow &window) override;
void applyImpulse(const sf::Vector2f &impulse) override;
void resolveCollision(Ball &other);
sf::Vector2f getPosition() const;
sf::Vector2f getVelocity() const;
bool isAtRest() const;
sf::Vector2f getPixelVelocity() const { return m_pixelVelocity; }
float getRadius() const { return m_radius; }
sf::Color getColor() const { return m_shape.getFillColor(); }
+17 -14
View File
@@ -1,24 +1,28 @@
#include "BallFactory.hpp"
#include <cstdlib>
#include <ctime>
#include <random>
#include "Constants.hpp"
namespace BallFactory {
std::unique_ptr<Ball> generateRandBall() {
float radius = Constants::BALL_RADIUS;
int diameter = static_cast<int>(2 * radius);
int maxX = Constants::WIDTH - diameter;
int maxY = Constants::HEIGHT - diameter;
float x = static_cast<float>((std::rand() % maxX) + radius);
float y = static_cast<float>((std::rand() % maxY) + radius);
float vel = static_cast<float>((std::rand() % 401) - 200);
static std::random_device rd;
static std::mt19937 rng(rd());
constexpr float x1 = Constants::WIDTH - Constants::BALL_RADIUS * 2;
constexpr float y1 = Constants::HEIGHT - Constants::BALL_RADIUS * 2;
std::uniform_real_distribution posXDist(Constants::BALL_RADIUS, x1);
std::uniform_real_distribution posYDist(Constants::BALL_RADIUS, y1);
std::uniform_real_distribution velDist(-200.0f, 200.0f);
std::uniform_int_distribution colorDist(64, 255);
auto randColorComp = []() {
return static_cast<std::uint8_t>(64 + (std::rand() % (256 - 64)));
};
sf::Color color(randColorComp(), randColorComp(), randColorComp());
float radius = Constants::BALL_RADIUS;
const float x = posXDist(rng);
const float y = posYDist(rng);
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 velocity = sf::Vector2f(vel, vel);
@@ -27,7 +31,6 @@ std::unique_ptr<Ball> generateRandBall() {
std::vector<std::unique_ptr<Ball> > generateBalls() {
std::vector<std::unique_ptr<Ball> > balls;
std::srand(static_cast<unsigned>(std::time(nullptr)));
for (int i = 0; i < Constants::BALL_QUANTITY; ++i) {
auto ball = generateRandBall();
+1
View File
@@ -6,5 +6,6 @@
namespace BallFactory {
std::unique_ptr<Ball> generateRandBall();
std::vector<std::unique_ptr<Ball> > generateBalls();
} // namespace BallFactory
+22 -22
View File
@@ -5,24 +5,24 @@
#include <cmath>
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);
unsigned int radius = size / 2;
sf::Vector2u center(radius, radius);
constexpr unsigned int radius = size / 2;
constexpr sf::Vector2u center(radius, radius);
for (unsigned int y = 0; y < size; ++y) {
for (unsigned int x = 0; x < size; ++x) {
sf::Vector2u pixel(x, y);
int dx = static_cast<int>(center.x) - static_cast<int>(pixel.x);
int dy = static_cast<int>(center.y) - static_cast<int>(pixel.y);
float distance = std::sqrt(dx * dx + dy * dy);
const sf::Vector2u pixel(x, y);
const int dx = static_cast<int>(center.x) - static_cast<int>(pixel.x);
const int dy = static_cast<int>(center.y) - static_cast<int>(pixel.y);
if (distance <= radius) {
if (const auto distance =
static_cast<float>(std::sqrt(dx * dx + dy * dy));
distance <= radius) {
float alpha = 255.0f;
if (distance > radius - 2.0f) {
alpha =
255.0f * (1.0f - (distance - (radius - 2.0f)) / 2.0f);
alpha = 255.0f * (1.0f - (distance - (radius - 2.0f)) / 2.0f);
}
image.setPixel(
sf::Vector2u(x, y),
@@ -39,9 +39,9 @@ BatchRenderer::BatchRenderer() : m_vertices(sf::PrimitiveType::Triangles) {
void BatchRenderer::clear() { m_vertices.clear(); }
void BatchRenderer::addBall(const Ball &ball) {
sf::Vector2f position = ball.getPosition();
float radius = ball.getRadius();
sf::Color color = ball.getColor();
const sf::Vector2f position = ball.getPosition();
const float radius = ball.getRadius();
const sf::Color color = ball.getColor();
sf::Vertex topLeft;
sf::Vertex topRight;
@@ -50,16 +50,16 @@ void BatchRenderer::addBall(const Ball& ball) {
topLeft.position = sf::Vector2f(position.x - radius, position.y - radius);
topRight.position = sf::Vector2f(position.x + radius, position.y - radius);
bottomRight.position =
sf::Vector2f(position.x + radius, position.y + radius);
bottomLeft.position =
sf::Vector2f(position.x - radius, position.y + radius);
bottomRight.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);
topRight.texCoords = sf::Vector2f(m_circleTexture.getSize().x, 0);
bottomRight.texCoords =
sf::Vector2f(m_circleTexture.getSize().x, m_circleTexture.getSize().y);
bottomLeft.texCoords = sf::Vector2f(0, m_circleTexture.getSize().y);
topRight.texCoords = sf::Vector2f(xSize, 0);
bottomRight.texCoords = sf::Vector2f(xSize, ySize);
bottomLeft.texCoords = sf::Vector2f(0, ySize);
topLeft.color = color;
topRight.color = color;
@@ -75,7 +75,7 @@ void BatchRenderer::addBall(const Ball& ball) {
m_vertices.append(bottomLeft);
}
void BatchRenderer::draw(sf::RenderWindow& window) {
void BatchRenderer::draw(sf::RenderWindow &window) const {
if (m_vertices.getVertexCount() == 0) return;
sf::RenderStates states;
+4 -3
View File
@@ -2,18 +2,19 @@
#include <SFML/Graphics/RenderWindow.hpp>
#include <SFML/Graphics/Texture.hpp>
#include <SFML/Graphics/VertexArray.hpp>
#include <vector>
#include "Ball.hpp"
class BatchRenderer {
private:
sf::VertexArray m_vertices;
sf::Texture m_circleTexture;
public:
BatchRenderer();
void clear();
void addBall(const Ball &ball);
void draw(sf::RenderWindow& window);
void draw(sf::RenderWindow &window) const;
};
+2 -2
View File
@@ -1,8 +1,8 @@
#pragma once
namespace Constants {
constexpr unsigned WIDTH = 1920;
constexpr unsigned HEIGHT = 1080;
constexpr int WIDTH = 1920;
constexpr int HEIGHT = 1080;
constexpr float GRAVITY = 781.f;
constexpr float RESTITUTION = 0.8f;
constexpr float FRICTION = 0.9f;
+12 -11
View File
@@ -6,25 +6,26 @@
#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));
}
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) {
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({endPoint, color}));
}
void DebugDraw::addDirectionLine(sf::VertexArray &lines, const Ball *ball,
const sf::RenderWindow &window) {
sf::Vector2f ballCenter = ball->getPosition();
sf::Vector2i mousePixel = sf::Mouse::getPosition(window);
sf::Vector2f mouseWorld(static_cast<float>(mousePixel.x),
const sf::Vector2f ballCenter = ball->getPosition();
const sf::Vector2i mousePixel = sf::Mouse::getPosition(window);
const sf::Vector2f mouseWorld(static_cast<float>(mousePixel.x),
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);
}
@@ -32,10 +33,10 @@ void DebugDraw::addDirectionLine(sf::VertexArray& lines, const Ball* ball,
void DebugDraw::addVelocityLine(sf::VertexArray &lines, const Ball *ball) {
if (ball->isAtRest()) return;
sf::Vector2f ballCenter = ball->getPosition();
sf::Vector2f velocity = ball->getVelocity();
float velLength = VectorMath::length(velocity);
float clampedLength = std::max(0.f, std::min(velLength, 100.f));
const sf::Vector2f ballCenter = ball->getPosition();
const sf::Vector2f velocity = ball->getVelocity();
const float velLength = VectorMath::length(velocity);
const float clampedLength = std::max(0.f, std::min(velLength, 100.f));
addLine(lines, ballCenter, velocity, clampedLength, sf::Color::Red);
}
+3
View File
@@ -10,9 +10,12 @@ class DebugDraw {
static void addLine(sf::VertexArray &lines, const sf::Vector2f &start,
const sf::Vector2f &direction, float length,
const sf::Color &color);
static void addDirectionLine(sf::VertexArray &lines, const Ball *ball,
const sf::RenderWindow &window);
static void addVelocityLine(sf::VertexArray &lines, const Ball *ball);
static void drawBatchedLines(sf::RenderWindow &window,
const sf::VertexArray &lines);
};
+8 -7
View File
@@ -9,13 +9,14 @@ DebugOverlay::DebugOverlay(const std::string& fontPath)
m_text.setPosition(sf::Vector2f(5.f, 5.f));
}
void DebugOverlay::update(int drawCalls, float timeScale,
sf::RenderWindow& window, size_t threadCount,
size_t ballsPerThread) {
float elapsed = m_fpsClock.restart().asSeconds();
if (elapsed > 0.f) m_fps = static_cast<int>(1.f / elapsed);
void DebugOverlay::update(const int drawCalls, const float timeScale,
const sf::RenderWindow &window,
const size_t threadCount,
const size_t ballsPerThread) {
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;
oss << "Draw calls: " << drawCalls << "\n";
@@ -28,4 +29,4 @@ void DebugOverlay::update(int drawCalls, float timeScale,
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 {
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);
void draw(sf::RenderWindow& window);
void draw(sf::RenderWindow &window) const;
private:
sf::Font m_font;
+79 -72
View File
@@ -4,24 +4,25 @@
#include <algorithm>
#include <cmath>
#include <thread>
#include <tuple>
#include <unordered_map>
#include "BallFactory.hpp"
#include "Constants.hpp"
#include "DebugDraw.hpp"
#include "ThreadUtils.hpp"
#include "VectorMath.hpp"
Game::Game()
: m_inputManager(
std::bind(&Game::processKeyPressed, this, std::placeholders::_1),
std::bind(&Game::processMousePressed, this, std::placeholders::_1)),
[this](auto &&PH1) {
processKeyPressed(std::forward<decltype(PH1)>(PH1));
},
[this](auto &&PH1) {
processMousePressed(std::forward<decltype(PH1)>(PH1));
}),
m_debugLines(sf::PrimitiveType::Lines),
m_drawCallCount(0),
m_debugOverlay("assets/consolas.ttf"),
m_threadPool(std::max(1u, std::thread::hardware_concurrency() > 2
? std::thread::hardware_concurrency() - 2
: 1u)) {
m_threadPool(ThreadUtils::calculateSafeWorkerThreads()) {
m_window.create(sf::VideoMode({Constants::WIDTH, Constants::HEIGHT}),
"SFML Playground");
m_window.setVerticalSyncEnabled(true);
@@ -34,37 +35,46 @@ Game::Game()
}
}
void Game::processKeyPressed(const sf::Event::KeyPressed &kP) {
if (kP.code == sf::Keyboard::Key::Equal) {
m_timeScale += 0.25f;
if (m_timeScale > 10.0f) m_timeScale = 10.0f;
} else if (kP.code == sf::Keyboard::Key::Hyphen) {
m_timeScale -= 0.25f;
if (m_timeScale < 0.25f) m_timeScale = 0.25f;
}
if (kP.code == sf::Keyboard::Key::D) {
void Game::processKeyPressed(const sf::Event::KeyPressed &keyPressed) {
switch (keyPressed.code) {
case sf::Keyboard::Key::Equal:
m_timeScale = std::min(10.0f, m_timeScale + 0.25f);
break;
case sf::Keyboard::Key::Hyphen:
m_timeScale = std::max(0.25f, m_timeScale - 0.25f);
break;
case sf::Keyboard::Key::D:
m_toggleDebug = !m_toggleDebug;
break;
default:
break;
}
}
void Game::processMousePressed(const sf::Event::MouseButtonPressed &mP) {
sf::Vector2f mousePos(static_cast<float>(mP.position.x),
static_cast<float>(mP.position.y));
if (mP.button == sf::Mouse::Button::Left) impulseBalls(mousePos);
if (mP.button == sf::Mouse::Button::Right) spawnBall(mousePos);
void Game::processMousePressed(
const sf::Event::MouseButtonPressed &mousePressed) {
const sf::Vector2f mousePos(static_cast<float>(mousePressed.position.x),
static_cast<float>(mousePressed.position.y));
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) {
auto newBallPtr = std::make_unique<Ball>(
Constants::BALL_RADIUS,
mousePos,
sf::Vector2f(0.f, 0.f),
sf::Color::Black);
auto newBallPtr =
std::make_unique<Ball>(Constants::BALL_RADIUS, mousePos,
sf::Vector2f(0.f, 0.f), sf::Color::Black);
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) {
auto *ball = dynamic_cast<Ball *>(object.get());
if (!ball) continue;
@@ -74,20 +84,21 @@ void Game::impulseBalls(const sf::Vector2f &mousePos) {
}
}
Game::Grid Game::buildSpatialGrid() {
const float cellSize = 2 * Constants::BALL_RADIUS;
const float safeCellSize = std::max(cellSize, 0.001f);
Game::Grid Game::buildSpatialGrid() const {
constexpr float safeCellSize = std::max(2 * Constants::BALL_RADIUS, 0.001f);
Grid grid;
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();
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 cellY = static_cast<int>(std::floor(pos.y / safeCellSize));
grid[{cellX, cellY}].push_back(ball);
}
}
}
return grid;
}
@@ -99,23 +110,23 @@ void Game::resolveSpatialCollisionsParallel(const Grid &grid) {
std::mutex collisionsMutex;
for (const auto &[cell, cellBalls] : grid) {
for (const auto &offset : forwardNeighbors) {
Cell neighborCell = {cell.first + offset.first,
cell.second + offset.second};
for (const auto &[firstOffset, secondOffset] : forwardNeighbors) {
Cell neighborCell = {cell.first + firstOffset,
cell.second + secondOffset};
auto neighborIt = grid.find(neighborCell);
if (neighborIt == grid.end()) continue;
if (neighborCell == cell) {
for (size_t i = 0; i < cellBalls.size(); ++i) {
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]);
}
}
} else {
for (Ball *ballA : cellBalls) {
for (Ball *ballB : neighborIt->second) {
std::lock_guard<std::mutex> lock(collisionsMutex);
std::lock_guard lock(collisionsMutex);
collisionPairs.emplace_back(ballA, ballB);
}
}
@@ -123,14 +134,15 @@ void Game::resolveSpatialCollisionsParallel(const Grid &grid) {
}
}
const size_t chunkSize = std::max(
size_t(1), collisionPairs.size() / std::thread::hardware_concurrency());
const size_t chunkSize =
std::max(static_cast<size_t>(1),
collisionPairs.size() / std::thread::hardware_concurrency());
std::vector<std::future<void>> futures;
for (size_t i = 0; i < collisionPairs.size(); i += chunkSize) {
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) {
auto &[ballA, ballB] = collisionPairs[j];
ballA->resolveCollision(*ballB);
@@ -138,9 +150,7 @@ void Game::resolveSpatialCollisionsParallel(const Grid &grid) {
}));
}
for (auto &future : futures) {
future.get();
}
for (auto &future : futures) future.get();
}
void Game::update() {
@@ -148,22 +158,20 @@ void Game::update() {
if (dt > 0.1f) dt = 0.1f;
updateBallsParallel(dt);
auto grid = buildSpatialGrid();
resolveSpatialCollisionsParallel(grid);
resolveSpatialCollisionsParallel(buildSpatialGrid());
}
void Game::updateBallsParallel(float dt) {
const size_t chunkSize = std::max(
size_t(1), m_objects.size() / std::thread::hardware_concurrency());
const size_t chunkSize =
std::max(static_cast<size_t>(1),
m_objects.size() / std::thread::hardware_concurrency());
std::vector<std::future<void>> futures;
for (size_t i = 0; i < m_objects.size(); i += chunkSize) {
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) {
if (auto *ball = dynamic_cast<Ball *>(m_objects[j].get())) {
ball->update(dt, m_windowSize);
@@ -174,24 +182,20 @@ void Game::updateBallsParallel(float dt) {
}));
}
for (auto &future : futures) {
future.get();
}
for (auto &future : futures) future.get();
}
void Game::render() {
m_window.clear(sf::Color::Black);
m_debugLines.clear();
m_drawCallCount = 0;
m_batchRenderer.clear();
std::vector<const Ball *> balls;
balls.reserve(m_objects.size());
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);
m_batchRenderer.addBall(*ball);
} else {
@@ -200,7 +204,7 @@ void Game::render() {
}
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::addVelocityLine(m_debugLines, ball);
}
@@ -211,8 +215,8 @@ void Game::render() {
if (m_toggleDebug) {
DebugDraw::drawBatchedLines(m_window, m_debugLines);
size_t threadCount = m_threadPool.getThreadCount();
size_t ballsPerThread = m_objects.size() / threadCount;
const size_t threadCount = m_threadPool.getThreadCount();
const size_t ballsPerThread = m_objects.size() / threadCount;
m_debugOverlay.update(m_drawCallCount + 2, m_timeScale, m_window,
threadCount, ballsPerThread);
m_debugOverlay.draw(m_window);
@@ -221,26 +225,29 @@ void Game::render() {
m_window.display();
}
void Game::run() {
while (m_window.isOpen()) {
while (const std::optional event = m_window.pollEvent()) {
if (event->is<sf::Event::Closed>()) {
void Game::handleEvent(const sf::Event &event) {
if (event.is<sf::Event::Closed>()) {
m_window.close();
return;
}
if (const auto *resized = event->getIf<sf::Event::Resized>()) {
sf::Vector2f position(0.f, 0.f);
sf::Vector2f size(static_cast<float>(resized->size.x),
if (const auto *resized = event.getIf<sf::Event::Resized>()) {
constexpr sf::Vector2f position(0.f, 0.f);
const sf::Vector2f size(static_cast<float>(resized->size.x),
static_cast<float>(resized->size.y));
sf::FloatRect visibleArea(position, size);
const sf::FloatRect visibleArea(position, size);
m_window.setView(sf::View(visibleArea));
m_windowSize = size;
}
if (auto kP = event->getIf<sf::Event::KeyPressed>())
if (const auto kP = event.getIf<sf::Event::KeyPressed>())
processKeyPressed(*kP);
if (auto mP = event->getIf<sf::Event::MouseButtonPressed>())
if (const auto mP = event.getIf<sf::Event::MouseButtonPressed>())
processMousePressed(*mP);
}
void Game::run() {
while (m_window.isOpen()) {
while (const std::optional event = m_window.pollEvent())
handleEvent(*event);
update();
render();
}
+14 -4
View File
@@ -1,7 +1,6 @@
#pragma once
#include <SFML/Graphics/Font.hpp>
#include <SFML/Graphics/RenderWindow.hpp>
#include <SFML/Graphics/Text.hpp>
#include <SFML/Graphics/VertexArray.hpp>
#include <SFML/System/Clock.hpp>
#include <SFML/System/Vector2.hpp>
@@ -21,7 +20,6 @@ class Ball;
class PhysicalObject;
class Game {
private:
float m_timeScale = 1.f;
sf::RenderWindow m_window;
sf::Vector2f m_windowSize;
@@ -41,22 +39,34 @@ class Game {
static_cast<std::size_t>(k.second) * 19349663;
}
};
using Cell = std::pair<int, int>;
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 impulseBalls(const sf::Vector2f& mousePos);
void handleEvent(const sf::Event &event);
void impulseBalls(const sf::Vector2f &mousePos) const;
void spawnBall(const sf::Vector2f &mousePos);
void update();
void render();
void updateBallsParallel(float dt);
void resolveSpatialCollisionsParallel(const Grid &grid);
Grid buildSpatialGrid();
Grid buildSpatialGrid() const;
public:
Game();
void run();
size_t getThreadCount() const { return m_threadPool.getThreadCount(); }
};
+4 -3
View File
@@ -5,14 +5,15 @@
InputManager::InputManager(KeyCallback keyCb, MouseCallback 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()) {
if (event->is<sf::Event::Closed>()) {
window.close();
return;
}
if (auto kP = event->getIf<sf::Event::KeyPressed>()) m_keyCallback(*kP);
if (auto mP = event->getIf<sf::Event::MouseButtonPressed>())
if (const auto kP = event->getIf<sf::Event::KeyPressed>())
m_keyCallback(*kP);
if (const auto mP = event->getIf<sf::Event::MouseButtonPressed>())
m_mouseCallback(*mP);
}
}
+1 -1
View File
@@ -11,7 +11,7 @@ class InputManager {
InputManager(KeyCallback keyCb, MouseCallback mouseCb);
void processEvents(sf::Window& window);
void processEvents(sf::Window& window) const;
private:
KeyCallback m_keyCallback;
+3
View File
@@ -5,7 +5,10 @@
class PhysicalObject {
public:
virtual ~PhysicalObject() = default;
virtual void update(float dt, const sf::Vector2f &windowSize) = 0;
virtual void draw(sf::RenderWindow &window) = 0;
virtual void applyImpulse(const sf::Vector2f &impulse) = 0;
};
+14 -15
View File
@@ -10,12 +10,14 @@
class ThreadPool {
public:
explicit ThreadPool(size_t numThreads);
~ThreadPool();
size_t getThreadCount() const { return workers.size(); }
[[nodiscard]] size_t getThreadCount() const { return workers.size(); }
template <class F, class... 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:
std::vector<std::thread> workers;
@@ -27,17 +29,15 @@ class ThreadPool {
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) {
workers.emplace_back([this] {
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(this->queueMutex);
this->condition.wait(lock, [this] {
return this->stop || !this->tasks.empty();
});
std::unique_lock lock(this->queueMutex);
this->condition.wait(
lock, [this] { return this->stop || !this->tasks.empty(); });
if (this->stop && this->tasks.empty()) return;
@@ -53,7 +53,7 @@ inline ThreadPool::ThreadPool(size_t numThreads) : stop(false) {
inline ThreadPool::~ThreadPool() {
{
std::unique_lock<std::mutex> lock(queueMutex);
std::unique_lock lock(queueMutex);
stop = true;
}
condition.notify_all();
@@ -62,20 +62,19 @@ inline ThreadPool::~ThreadPool() {
}
template <class F, class... Args>
inline auto ThreadPool::enqueue(F&& f, Args&&... args)
-> std::future<typename std::invoke_result<F, Args...>::type> {
using return_type = typename std::invoke_result<F, Args...>::type;
auto ThreadPool::enqueue(F &&f, Args &&...args)
-> std::future<std::invoke_result_t<F, Args...> > {
using return_type = std::invoke_result_t<F, Args...>;
auto task = std::make_shared<std::packaged_task<return_type()> >(
std::bind(std::forward<F>(f), std::forward<Args>(args)...));
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");
tasks.emplace([task]() { (*task)(); });
tasks.emplace([task] { (*task)(); });
}
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
+4 -4
View File
@@ -8,10 +8,10 @@ float length(const sf::Vector2f& vector) {
}
sf::Vector2f normalize(const sf::Vector2f &vector) {
float squaredLen = vector.x * vector.x + vector.y * vector.y;
if (squaredLen > 0.0001f) {
float invLen = 1.0f / std::sqrt(squaredLen);
return sf::Vector2f(vector.x * invLen, vector.y * invLen);
if (const float squaredLen = vector.x * vector.x + vector.y * vector.y;
squaredLen > 0.0001f) {
const float invLen = 1.0f / std::sqrt(squaredLen);
return {vector.x * invLen, vector.y * invLen};
}
return vector;
}
+1
View File
@@ -3,5 +3,6 @@
namespace VectorMath {
float length(const sf::Vector2f &vector);
sf::Vector2f normalize(const sf::Vector2f &vector);
} // namespace VectorMath