diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..9969025 --- /dev/null +++ b/.clang-format @@ -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: '^' + 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 +... + diff --git a/.gitignore b/.gitignore index 6af96b6..e6b383c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ -build +build/ +cmake-build-debug/ out .cache .idea diff --git a/src/Ball.cpp b/src/Ball.cpp index 783c28b..897cc67 100644 --- a/src/Ball.cpp +++ b/src/Ball.cpp @@ -4,88 +4,90 @@ #include "Constants.hpp" -Ball::Ball(float radius, const sf::Vector2f& pos, const sf::Vector2f& vel, - const sf::Color& color) - : m_radius(radius), - m_velocity(vel), +Ball::Ball(const float radius, const sf::Vector2f &pos, const sf::Vector2f &vel, + const sf::Color &color) + : 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)); - m_shape.setPosition(pos); - m_shape.setFillColor(color); + m_shape.setRadius(radius); + m_shape.setOrigin(sf::Vector2f(radius, radius)); + m_shape.setPosition(pos); + m_shape.setFillColor(color); } -void Ball::update(float dt, const sf::Vector2f& windowSize) { - if (m_atRest) return; - m_velocity.y += Constants::GRAVITY * dt; - m_shape.move(m_velocity * dt); +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(); - m_pixelVelocity = (currentPosition - m_lastPosition) / dt; - m_lastPosition = currentPosition; + const sf::Vector2f currentPosition = m_shape.getPosition(); + m_pixelVelocity = (currentPosition - m_lastPosition) / dt; + m_lastPosition = currentPosition; - updateColor(); - handleWallCollision(windowSize); + updateColor(); + handleWallCollision(windowSize); } void Ball::updateColor() { - float currentSpeed = - std::sqrt(m_velocity.x * m_velocity.x + m_velocity.y * m_velocity.y); + 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; - } - lastSpeed = currentSpeed; + static float lastSpeed = 0.0f; + if (std::abs(currentSpeed - lastSpeed) < 10.0f) return; + lastSpeed = currentSpeed; - float speed = - std::sqrt(m_velocity.x * m_velocity.x + m_velocity.y * m_velocity.y); + const float speed = + 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 + const float scaledT = t * 3.0f; + targetColor.r = static_cast(255); + targetColor.g = static_cast(0 + scaledT * 165); + targetColor.b = static_cast(0); + } else if (t < 0.66f) { + // Orange to yellow + const float scaledT = (t - 0.33f) * 3.0f; + targetColor.r = static_cast(255); + targetColor.g = static_cast(165 + scaledT * 90); + targetColor.b = static_cast(0); + } else { + // Yellow to white + const float scaledT = (t - 0.66f) * 3.0f; + targetColor.r = static_cast(255); + targetColor.g = static_cast(255); + targetColor.b = static_cast(0 + scaledT * 255); + } - if (t < 0.33f) { // Red to orange - float scaledT = t * 3.0f; - targetColor.r = static_cast(255); - targetColor.g = static_cast(0 + scaledT * 165); - targetColor.b = static_cast(0); - } else if (t < 0.66f) { // Orange to yellow - float scaledT = (t - 0.33f) * 3.0f; - targetColor.r = static_cast(255); - targetColor.g = static_cast(165 + scaledT * 90); - targetColor.b = static_cast(0); - } else { // Yellow to white - float scaledT = (t - 0.66f) * 3.0f; - targetColor.r = static_cast(255); - targetColor.g = static_cast(255); - targetColor.b = static_cast(0 + scaledT * 255); - } + const sf::Color currentColor = m_shape.getFillColor(); - sf::Color currentColor = m_shape.getFillColor(); + constexpr float TRANSITION_SPEED = 0.05f; - const float TRANSITION_SPEED = 0.05f; + sf::Color newColor; + newColor.r = static_cast(static_cast(currentColor.r) + + TRANSITION_SPEED * + (targetColor.r - currentColor.r)); + newColor.g = static_cast(static_cast(currentColor.g) + + TRANSITION_SPEED * + (targetColor.g - currentColor.g)); + newColor.b = static_cast(static_cast(currentColor.b) + + TRANSITION_SPEED * + (targetColor.b - currentColor.b)); + newColor.a = 255; - sf::Color newColor; - newColor.r = static_cast( - currentColor.r + TRANSITION_SPEED * (targetColor.r - currentColor.r)); - newColor.g = static_cast( - currentColor.g + TRANSITION_SPEED * (targetColor.g - currentColor.g)); - newColor.b = static_cast( - currentColor.b + TRANSITION_SPEED * (targetColor.b - currentColor.b)); - 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) { - m_velocity += impulse; - m_atRest = false; +void Ball::applyImpulse(const sf::Vector2f &impulse) { + m_velocity += impulse; + m_atRest = false; } sf::Vector2f Ball::getPosition() const { return m_shape.getPosition(); } @@ -94,74 +96,74 @@ sf::Vector2f Ball::getVelocity() const { return m_velocity; } bool Ball::isAtRest() const { return m_atRest; } -void Ball::handleWallCollision(const sf::Vector2f& windowSize) { - sf::Vector2f pos = m_shape.getPosition(); +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; - velocity = -velocity * restitution; - } else if (value + radius > max) { - value = max - radius; - velocity = -velocity * restitution; - if (axis == 1 && std::abs(velocity) < 10.f) velocity = 0.f; - } - - (axis == 0 ? pos.x : pos.y) = value; - }; - - handleAxis(0, 0.f, windowSize.x, m_velocity.x, m_radius, - Constants::RESTITUTION); - handleAxis(1, 0.f, windowSize.y, m_velocity.y, m_radius, - Constants::RESTITUTION); - - if (pos.y + m_radius >= windowSize.y - 1.0f) { - m_velocity.x *= Constants::FRICTION; - if (std::abs(m_velocity.x) < 5.f) m_velocity.x = 0.f; - if (std::abs(m_pixelVelocity.x) < Constants::REST_PIXEL_VELOCITY && - std::abs(m_pixelVelocity.y) < Constants::REST_PIXEL_VELOCITY) { - m_velocity = {0.f, 0.f}; - m_atRest = true; - } - } else { - m_atRest = false; + if (value - radius < min) { + value = min + radius; + velocity = -velocity * restitution; + } else if (value + radius > max) { + value = max - radius; + velocity = -velocity * restitution; + if (axis == 1 && std::abs(velocity) < 10.f) velocity = 0.f; } - m_shape.setPosition(pos); -} + (axis == 0 ? pos.x : pos.y) = value; + }; -void Ball::resolveCollision(Ball& other) { - 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; - - if (dist >= minDist || dist < 1e-6f) return; - - sf::Vector2f normal = delta / dist; - 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; - - float restitution = 0.95f; - float vA_n_new = vB_n * restitution; - float vB_n_new = vA_n * restitution; - - m_velocity += (vA_n_new - vA_n) * normal; - other.m_velocity += (vB_n_new - vB_n) * normal; + handleAxis(0, 0.f, windowSize.x, m_velocity.x, m_radius, + Constants::RESTITUTION); + handleAxis(1, 0.f, windowSize.y, m_velocity.y, m_radius, + Constants::RESTITUTION); + if (pos.y + m_radius >= windowSize.y - 1.0f) { + m_velocity.x *= Constants::FRICTION; + if (std::abs(m_velocity.x) < 5.f) m_velocity.x = 0.f; + if (std::abs(m_pixelVelocity.x) < Constants::REST_PIXEL_VELOCITY && + std::abs(m_pixelVelocity.y) < Constants::REST_PIXEL_VELOCITY) { + m_velocity = {0.f, 0.f}; + m_atRest = true; + } + } else { m_atRest = false; - other.m_atRest = false; + } + + m_shape.setPosition(pos); +} + +void Ball::resolveCollision(Ball &other) { + std::lock_guard lockA(m_mutex); + std::lock_guard lockB(other.m_mutex); + + 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; + + 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)); + + 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; + + 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; + + m_atRest = false; + other.m_atRest = false; } diff --git a/src/Ball.hpp b/src/Ball.hpp index 85de294..513b59b 100644 --- a/src/Ball.hpp +++ b/src/Ball.hpp @@ -4,38 +4,44 @@ #include "PhysicalObject.hpp" -class Ball : public PhysicalObject { - private: - // Position and physics data - sf::Vector2f m_velocity; - sf::Vector2f m_lastPosition; - sf::Vector2f m_pixelVelocity; - float m_radius; +class Ball final : public PhysicalObject { + // Position and physics data + sf::Vector2f m_velocity; + sf::Vector2f m_lastPosition; + sf::Vector2f m_pixelVelocity; + float m_radius; - // Display data - sf::CircleShape m_shape; - sf::Color m_baseColor; + // Display data + sf::CircleShape m_shape; + sf::Color m_baseColor; - // State flags - bool m_atRest = false; - mutable std::mutex m_mutex; + // State flags + bool m_atRest = false; + mutable std::mutex m_mutex; - void handleWallCollision(const sf::Vector2f& windowSize); - void updateColor(); + void handleWallCollision(const sf::Vector2f &windowSize); - public: - Ball(float radius, const sf::Vector2f& pos, const sf::Vector2f& vel, - const sf::Color& color); + void updateColor(); - 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); + public: + Ball(float radius, const sf::Vector2f &pos, const sf::Vector2f &vel, + const sf::Color &color); - 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(); } + 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(); } }; diff --git a/src/BallFactory.cpp b/src/BallFactory.cpp index 0fcd093..7784b24 100644 --- a/src/BallFactory.cpp +++ b/src/BallFactory.cpp @@ -1,38 +1,41 @@ #include "BallFactory.hpp" -#include -#include +#include #include "Constants.hpp" namespace BallFactory { std::unique_ptr generateRandBall() { - float radius = Constants::BALL_RADIUS; - int diameter = static_cast(2 * radius); - int maxX = Constants::WIDTH - diameter; - int maxY = Constants::HEIGHT - diameter; - float x = static_cast((std::rand() % maxX) + radius); - float y = static_cast((std::rand() % maxY) + radius); - float vel = static_cast((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(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); - auto position = sf::Vector2f(x, y); - auto velocity = sf::Vector2f(vel, vel); - return std::make_unique(radius, position, velocity, color); + sf::Color color(static_cast(colorDist(rng)), + static_cast(colorDist(rng)), + static_cast(colorDist(rng))); + + auto position = sf::Vector2f(x, y); + auto velocity = sf::Vector2f(vel, vel); + return std::make_unique(radius, position, velocity, color); } -std::vector> generateBalls() { - std::vector> balls; - std::srand(static_cast(std::time(nullptr))); +std::vector > generateBalls() { + std::vector > balls; - for (int i = 0; i < Constants::BALL_QUANTITY; ++i) { - auto ball = generateRandBall(); - balls.push_back(std::move(ball)); - } - return balls; + for (int i = 0; i < Constants::BALL_QUANTITY; ++i) { + auto ball = generateRandBall(); + balls.push_back(std::move(ball)); + } + return balls; } -} // namespace BallFactory \ No newline at end of file +} // namespace BallFactory diff --git a/src/BallFactory.hpp b/src/BallFactory.hpp index f547822..ffab569 100644 --- a/src/BallFactory.hpp +++ b/src/BallFactory.hpp @@ -6,5 +6,6 @@ namespace BallFactory { std::unique_ptr generateRandBall(); -std::vector> generateBalls(); -} // namespace BallFactory \ No newline at end of file + +std::vector > generateBalls(); +} // namespace BallFactory diff --git a/src/BatchRenderer.cpp b/src/BatchRenderer.cpp index f7fea99..59265fd 100644 --- a/src/BatchRenderer.cpp +++ b/src/BatchRenderer.cpp @@ -5,81 +5,81 @@ #include BatchRenderer::BatchRenderer() : m_vertices(sf::PrimitiveType::Triangles) { - const unsigned int size = 64; - sf::Image image(sf::Vector2u(size, size), sf::Color::Transparent); + 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(center.x) - static_cast(pixel.x); - int dy = static_cast(center.y) - static_cast(pixel.y); - float distance = std::sqrt(dx * dx + dy * dy); + for (unsigned int y = 0; y < size; ++y) { + for (unsigned int x = 0; x < size; ++x) { + const sf::Vector2u pixel(x, y); + const int dx = static_cast(center.x) - static_cast(pixel.x); + const int dy = static_cast(center.y) - static_cast(pixel.y); - if (distance <= radius) { - float alpha = 255.0f; - if (distance > radius - 2.0f) { - alpha = - 255.0f * (1.0f - (distance - (radius - 2.0f)) / 2.0f); - } - image.setPixel( - sf::Vector2u(x, y), - sf::Color(255, 255, 255, static_cast(alpha))); - } + if (const auto distance = + static_cast(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); } + image.setPixel( + sf::Vector2u(x, y), + sf::Color(255, 255, 255, static_cast(alpha))); + } } + } - if (!m_circleTexture.loadFromImage(image)) { - throw std::runtime_error("Failed to load circle texture from image"); - } + if (!m_circleTexture.loadFromImage(image)) { + throw std::runtime_error("Failed to load circle texture from image"); + } } 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(); +void BatchRenderer::addBall(const Ball &ball) { + const sf::Vector2f position = ball.getPosition(); + const float radius = ball.getRadius(); + const sf::Color color = ball.getColor(); - sf::Vertex topLeft; - sf::Vertex topRight; - sf::Vertex bottomRight; - sf::Vertex bottomLeft; + sf::Vertex topLeft; + sf::Vertex topRight; + sf::Vertex bottomRight; + sf::Vertex bottomLeft; - 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); + 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); - 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); + const float xSize = static_cast(m_circleTexture.getSize().x); + const float ySize = static_cast(m_circleTexture.getSize().y); - topLeft.color = color; - topRight.color = color; - bottomRight.color = color; - bottomLeft.color = color; + topLeft.texCoords = sf::Vector2f(0, 0); + topRight.texCoords = sf::Vector2f(xSize, 0); + bottomRight.texCoords = sf::Vector2f(xSize, ySize); + bottomLeft.texCoords = sf::Vector2f(0, ySize); - m_vertices.append(topLeft); - m_vertices.append(topRight); - m_vertices.append(bottomRight); + topLeft.color = color; + topRight.color = color; + bottomRight.color = color; + bottomLeft.color = color; - m_vertices.append(topLeft); - m_vertices.append(bottomRight); - m_vertices.append(bottomLeft); + m_vertices.append(topLeft); + m_vertices.append(topRight); + m_vertices.append(bottomRight); + + m_vertices.append(topLeft); + m_vertices.append(bottomRight); + m_vertices.append(bottomLeft); } -void BatchRenderer::draw(sf::RenderWindow& window) { - if (m_vertices.getVertexCount() == 0) return; +void BatchRenderer::draw(sf::RenderWindow &window) const { + if (m_vertices.getVertexCount() == 0) return; - sf::RenderStates states; - states.texture = &m_circleTexture; + sf::RenderStates states; + states.texture = &m_circleTexture; - window.draw(m_vertices, states); + window.draw(m_vertices, states); } diff --git a/src/BatchRenderer.hpp b/src/BatchRenderer.hpp index 188f413..e43d424 100644 --- a/src/BatchRenderer.hpp +++ b/src/BatchRenderer.hpp @@ -2,18 +2,19 @@ #include #include #include -#include #include "Ball.hpp" class BatchRenderer { - private: - sf::VertexArray m_vertices; - sf::Texture m_circleTexture; + sf::VertexArray m_vertices; + sf::Texture m_circleTexture; - public: - BatchRenderer(); - void clear(); - void addBall(const Ball& ball); - void draw(sf::RenderWindow& window); + public: + BatchRenderer(); + + void clear(); + + void addBall(const Ball &ball); + + void draw(sf::RenderWindow &window) const; }; diff --git a/src/Constants.hpp b/src/Constants.hpp index 5f8181f..50e471f 100644 --- a/src/Constants.hpp +++ b/src/Constants.hpp @@ -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; diff --git a/src/DebugDraw.cpp b/src/DebugDraw.cpp index ced5f4c..135f5df 100644 --- a/src/DebugDraw.cpp +++ b/src/DebugDraw.cpp @@ -6,41 +6,42 @@ #include "VectorMath.hpp" -float clamp(float value, float minVal, float maxVal) { - return std::max(minVal, std::min(value, 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::Color& color) { - sf::Vector2f endPoint = start + VectorMath::normalize(direction) * length; - lines.append(sf::Vertex({start, color})); - lines.append(sf::Vertex({endPoint, color})); +void DebugDraw::addLine(sf::VertexArray &lines, const sf::Vector2f &start, + const sf::Vector2f &direction, const float length, + const sf::Color &color) { + 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(mousePixel.x), - static_cast(mousePixel.y)); - sf::Vector2f dir = mouseWorld - ballCenter; +void DebugDraw::addDirectionLine(sf::VertexArray &lines, const Ball *ball, + const sf::RenderWindow &window) { + const sf::Vector2f ballCenter = ball->getPosition(); + const sf::Vector2i mousePixel = sf::Mouse::getPosition(window); + const sf::Vector2f mouseWorld(static_cast(mousePixel.x), + static_cast(mousePixel.y)); + 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) { - if (ball->isAtRest()) return; +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); + addLine(lines, ballCenter, velocity, clampedLength, sf::Color::Red); } -void DebugDraw::drawBatchedLines(sf::RenderWindow& window, - const sf::VertexArray& lines) { - if (lines.getVertexCount() > 0) window.draw(lines); +void DebugDraw::drawBatchedLines(sf::RenderWindow &window, + const sf::VertexArray &lines) { + if (lines.getVertexCount() > 0) window.draw(lines); } diff --git a/src/DebugDraw.hpp b/src/DebugDraw.hpp index 4c313c1..7ff73a9 100644 --- a/src/DebugDraw.hpp +++ b/src/DebugDraw.hpp @@ -6,13 +6,16 @@ #include "Ball.hpp" class DebugDraw { - public: - 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); + public: + 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); }; diff --git a/src/DebugOverlay.cpp b/src/DebugOverlay.cpp index 0f63d54..e577c70 100644 --- a/src/DebugOverlay.cpp +++ b/src/DebugOverlay.cpp @@ -2,30 +2,31 @@ #include -DebugOverlay::DebugOverlay(const std::string& fontPath) +DebugOverlay::DebugOverlay(const std::string &fontPath) : m_font(fontPath), m_text(m_font) { - m_text.setCharacterSize(18); - m_text.setFillColor(sf::Color::White); - m_text.setPosition(sf::Vector2f(5.f, 5.f)); + m_text.setCharacterSize(18); + m_text.setFillColor(sf::Color::White); + 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(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"; - oss << "Framerate: " << m_fps << " FPS\n"; - oss << "Frametime: " << (elapsed * 1000.f) << " ms\n"; - oss << "Mouse: " << mousePos.x << ", " << mousePos.y << "\n"; - oss << "Time scale: " << timeScale << "\n"; - oss << "\nThreads: " << threadCount; - oss << "\nBalls per thread: " << ballsPerThread; - m_text.setString(oss.str()); + std::ostringstream oss; + oss << "Draw calls: " << drawCalls << "\n"; + oss << "Framerate: " << m_fps << " FPS\n"; + oss << "Frame time: " << (elapsed * 1000.f) << " ms\n"; + oss << "Mouse: " << mousePos.x << ", " << mousePos.y << "\n"; + oss << "Time scale: " << timeScale << "\n"; + oss << "\nThreads: " << threadCount; + oss << "\nBalls per thread: " << ballsPerThread; + 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); } diff --git a/src/DebugOverlay.hpp b/src/DebugOverlay.hpp index ea5f24d..73e1050 100644 --- a/src/DebugOverlay.hpp +++ b/src/DebugOverlay.hpp @@ -6,16 +6,17 @@ #include class DebugOverlay { - public: - DebugOverlay(const std::string& fontPath); + public: + explicit DebugOverlay(const std::string &fontPath); - void update(int drawCalls, float timeScale, sf::RenderWindow& window, - size_t threadCount, size_t ballsPerThread); - void draw(sf::RenderWindow& window); + void update(int drawCalls, float timeScale, const sf::RenderWindow &window, + size_t threadCount, size_t ballsPerThread); - private: - sf::Font m_font; - sf::Text m_text; - sf::Clock m_fpsClock; - float m_fps = 0.f; -}; \ No newline at end of file + void draw(sf::RenderWindow &window) const; + + private: + sf::Font m_font; + sf::Text m_text; + sf::Clock m_fpsClock; + float m_fps = 0.f; +}; diff --git a/src/Game.cpp b/src/Game.cpp index 15950c6..70a442a 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -4,244 +4,251 @@ #include #include #include -#include #include #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(PH1)); + }, + [this](auto &&PH1) { + processMousePressed(std::forward(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_window.create(sf::VideoMode({Constants::WIDTH, Constants::HEIGHT}), - "SFML Playground"); - m_window.setVerticalSyncEnabled(true); - m_windowSize = sf::Vector2f(Constants::WIDTH, Constants::HEIGHT); - m_objects.clear(); + m_threadPool(ThreadUtils::calculateSafeWorkerThreads()) { + m_window.create(sf::VideoMode({Constants::WIDTH, Constants::HEIGHT}), + "SFML Playground"); + m_window.setVerticalSyncEnabled(true); + m_windowSize = sf::Vector2f(Constants::WIDTH, Constants::HEIGHT); + m_objects.clear(); - auto balls = BallFactory::generateBalls(); - for (auto &ball : balls) { - m_objects.push_back(std::move(ball)); - } + auto balls = BallFactory::generateBalls(); + for (auto &ball : balls) { + m_objects.push_back(std::move(ball)); + } } -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) { - m_toggleDebug = !m_toggleDebug; - } +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(mP.position.x), - static_cast(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(mousePressed.position.x), + static_cast(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( - Constants::BALL_RADIUS, - mousePos, - sf::Vector2f(0.f, 0.f), - sf::Color::Black); - m_objects.push_back(std::move(newBallPtr)); + auto newBallPtr = + std::make_unique(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) { - for (auto &object : m_objects) { - auto *ball = dynamic_cast(object.get()); - if (!ball) continue; +void Game::impulseBalls(const sf::Vector2f &mousePos) const { + for (auto &object : m_objects) { + auto *ball = dynamic_cast(object.get()); + if (!ball) continue; - sf::Vector2f dir = mousePos - ball->getPosition(); - ball->applyImpulse(Constants::IMPULSE * VectorMath::normalize(dir)); - } + sf::Vector2f dir = mousePos - ball->getPosition(); + ball->applyImpulse(Constants::IMPULSE * VectorMath::normalize(dir)); + } } -Game::Grid Game::buildSpatialGrid() { - const float cellSize = 2 * Constants::BALL_RADIUS; - const float safeCellSize = std::max(cellSize, 0.001f); - Grid grid; - for (auto &object : m_objects) { - if (Ball *ball = dynamic_cast(object.get())) { - const sf::Vector2f &pos = ball->getPosition(); - if (std::isfinite(pos.x) && std::isfinite(pos.y)) { - int cellX = static_cast(std::floor(pos.x / safeCellSize)); - int cellY = static_cast(std::floor(pos.y / safeCellSize)); - grid[{cellX, cellY}].push_back(ball); - } - } - } - return grid; +Game::Grid Game::buildSpatialGrid() const { + constexpr float safeCellSize = std::max(2 * Constants::BALL_RADIUS, 0.001f); + Grid grid; + + for (auto &object : m_objects) { + auto ball = dynamic_cast(object.get()); + if (!ball) continue; + + const sf::Vector2f &pos = ball->getPosition(); + if (!std::isfinite(pos.x) || !std::isfinite(pos.y)) continue; + + int cellX = static_cast(std::floor(pos.x / safeCellSize)); + int cellY = static_cast(std::floor(pos.y / safeCellSize)); + grid[{cellX, cellY}].push_back(ball); + } + return grid; } void Game::resolveSpatialCollisionsParallel(const Grid &grid) { - static const Cell forwardNeighbors[] = { - {0, 0}, {1, 0}, {1, 1}, {0, 1}, {-1, 1}}; + static const Cell forwardNeighbors[] = { + {0, 0}, {1, 0}, {1, 1}, {0, 1}, {-1, 1}}; - std::vector> collisionPairs; - std::mutex collisionsMutex; + std::vector> collisionPairs; + std::mutex collisionsMutex; - for (const auto &[cell, cellBalls] : grid) { - for (const auto &offset : forwardNeighbors) { - Cell neighborCell = {cell.first + offset.first, - cell.second + offset.second}; - auto neighborIt = grid.find(neighborCell); - if (neighborIt == grid.end()) continue; + for (const auto &[cell, cellBalls] : grid) { + 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 lock(collisionsMutex); - collisionPairs.emplace_back(cellBalls[i], cellBalls[j]); - } - } - } else { - for (Ball *ballA : cellBalls) { - for (Ball *ballB : neighborIt->second) { - std::lock_guard lock(collisionsMutex); - collisionPairs.emplace_back(ballA, ballB); - } - } - } + 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 lock(collisionsMutex); + collisionPairs.emplace_back(cellBalls[i], cellBalls[j]); + } } + } else { + for (Ball *ballA : cellBalls) { + for (Ball *ballB : neighborIt->second) { + std::lock_guard lock(collisionsMutex); + collisionPairs.emplace_back(ballA, ballB); + } + } + } } + } - const size_t chunkSize = std::max( - size_t(1), collisionPairs.size() / std::thread::hardware_concurrency()); - std::vector> futures; + const size_t chunkSize = + std::max(static_cast(1), + collisionPairs.size() / std::thread::hardware_concurrency()); + std::vector> futures; - for (size_t i = 0; i < collisionPairs.size(); i += chunkSize) { - size_t end = std::min(i + chunkSize, collisionPairs.size()); + 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]() { - for (size_t j = i; j < end; ++j) { - auto &[ballA, ballB] = collisionPairs[j]; - ballA->resolveCollision(*ballB); - } - })); - } + 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); + } + })); + } - for (auto &future : futures) { - future.get(); - } + for (auto &future : futures) future.get(); } void Game::update() { - float dt = m_clock.restart().asSeconds() * m_timeScale; - if (dt > 0.1f) dt = 0.1f; + float dt = m_clock.restart().asSeconds() * m_timeScale; + if (dt > 0.1f) dt = 0.1f; - updateBallsParallel(dt); - - auto grid = buildSpatialGrid(); - - resolveSpatialCollisionsParallel(grid); + updateBallsParallel(dt); + 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(1), + m_objects.size() / std::thread::hardware_concurrency()); - std::vector> futures; + std::vector> futures; - for (size_t i = 0; i < m_objects.size(); i += chunkSize) { - size_t end = std::min(i + chunkSize, m_objects.size()); + 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]() { - for (size_t j = i; j < end; ++j) { - if (auto *ball = dynamic_cast(m_objects[j].get())) { - ball->update(dt, m_windowSize); - } else { - m_objects[j]->update(dt, m_windowSize); - } - } - })); - } + futures.push_back(m_threadPool.enqueue([&, i, end, dt] { + for (size_t j = i; j < end; ++j) { + if (auto *ball = dynamic_cast(m_objects[j].get())) { + ball->update(dt, m_windowSize); + } else { + m_objects[j]->update(dt, m_windowSize); + } + } + })); + } - for (auto &future : futures) { - future.get(); - } + for (auto &future : futures) future.get(); } void Game::render() { - m_window.clear(sf::Color::Black); + m_window.clear(sf::Color::Black); + m_debugLines.clear(); + m_drawCallCount = 0; + m_batchRenderer.clear(); - m_debugLines.clear(); - m_drawCallCount = 0; + std::vector balls; + balls.reserve(m_objects.size()); - m_batchRenderer.clear(); - - std::vector balls; - balls.reserve(m_objects.size()); - - for (const auto &object : m_objects) { - if (auto *ball = dynamic_cast(object.get())) { - balls.push_back(ball); - m_batchRenderer.addBall(*ball); - } else { - object->draw(m_window); - ++m_drawCallCount; - } - - if (!m_toggleDebug) continue; - if (auto *ball = dynamic_cast(object.get())) { - DebugDraw::addDirectionLine(m_debugLines, ball, m_window); - DebugDraw::addVelocityLine(m_debugLines, ball); - } + for (const auto &object : m_objects) { + if (const auto *ball = dynamic_cast(object.get())) { + balls.push_back(ball); + m_batchRenderer.addBall(*ball); + } else { + object->draw(m_window); + ++m_drawCallCount; } - m_batchRenderer.draw(m_window); - ++m_drawCallCount; - - if (m_toggleDebug) { - DebugDraw::drawBatchedLines(m_window, m_debugLines); - size_t threadCount = m_threadPool.getThreadCount(); - size_t ballsPerThread = m_objects.size() / threadCount; - m_debugOverlay.update(m_drawCallCount + 2, m_timeScale, m_window, - threadCount, ballsPerThread); - m_debugOverlay.draw(m_window); + if (!m_toggleDebug) continue; + if (const auto *ball = dynamic_cast(object.get())) { + DebugDraw::addDirectionLine(m_debugLines, ball, m_window); + DebugDraw::addVelocityLine(m_debugLines, ball); } + } - m_window.display(); + m_batchRenderer.draw(m_window); + ++m_drawCallCount; + + if (m_toggleDebug) { + DebugDraw::drawBatchedLines(m_window, m_debugLines); + 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); + } + + m_window.display(); +} + +void Game::handleEvent(const sf::Event &event) { + if (event.is()) { + m_window.close(); + return; + } + if (const auto *resized = event.getIf()) { + constexpr sf::Vector2f position(0.f, 0.f); + const sf::Vector2f size(static_cast(resized->size.x), + static_cast(resized->size.y)); + const sf::FloatRect visibleArea(position, size); + m_window.setView(sf::View(visibleArea)); + m_windowSize = size; + } + if (const auto kP = event.getIf()) + processKeyPressed(*kP); + if (const auto mP = event.getIf()) + processMousePressed(*mP); } void Game::run() { - while (m_window.isOpen()) { - while (const std::optional event = m_window.pollEvent()) { - if (event->is()) { - m_window.close(); - return; - } - if (const auto *resized = event->getIf()) { - sf::Vector2f position(0.f, 0.f); - sf::Vector2f size(static_cast(resized->size.x), - static_cast(resized->size.y)); - sf::FloatRect visibleArea(position, size); - m_window.setView(sf::View(visibleArea)); - m_windowSize = size; - } - if (auto kP = event->getIf()) - processKeyPressed(*kP); - if (auto mP = event->getIf()) - processMousePressed(*mP); - } - update(); - render(); - } + while (m_window.isOpen()) { + while (const std::optional event = m_window.pollEvent()) + handleEvent(*event); + update(); + render(); + } } diff --git a/src/Game.hpp b/src/Game.hpp index ff058f8..a20e089 100644 --- a/src/Game.hpp +++ b/src/Game.hpp @@ -1,7 +1,6 @@ #pragma once #include #include -#include #include #include #include @@ -21,42 +20,53 @@ class Ball; class PhysicalObject; class Game { - private: - float m_timeScale = 1.f; - sf::RenderWindow m_window; - sf::Vector2f m_windowSize; - std::vector> m_objects; - sf::Clock m_clock; - InputManager m_inputManager; - sf::VertexArray m_debugLines; - int m_drawCallCount = 0; - DebugOverlay m_debugOverlay; - bool m_toggleDebug = true; - BatchRenderer m_batchRenderer; - ThreadPool m_threadPool; + float m_timeScale = 1.f; + sf::RenderWindow m_window; + sf::Vector2f m_windowSize; + std::vector > m_objects; + sf::Clock m_clock; + InputManager m_inputManager; + sf::VertexArray m_debugLines; + int m_drawCallCount = 0; + DebugOverlay m_debugOverlay; + bool m_toggleDebug = true; + BatchRenderer m_batchRenderer; + ThreadPool m_threadPool; - struct CellHash { - std::size_t operator()(const std::pair& k) const { - return static_cast(k.first) * 73856093 ^ - static_cast(k.second) * 19349663; - } - }; - using Cell = std::pair; - using Grid = std::unordered_map, CellHash>; + struct CellHash { + std::size_t operator()(const std::pair &k) const { + return static_cast(k.first) * 73856093 ^ + static_cast(k.second) * 19349663; + } + }; - 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 render(); - void updateBallsParallel(float dt); - void resolveSpatialCollisionsParallel(const Grid& grid); + using Cell = std::pair; + using Grid = std::unordered_map, CellHash>; - Grid buildSpatialGrid(); + void processKeyPressed(const sf::Event::KeyPressed &keyPressed); - public: - Game(); - void run(); - size_t getThreadCount() const { return m_threadPool.getThreadCount(); } + 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 update(); + + void render(); + + void updateBallsParallel(float dt); + + void resolveSpatialCollisionsParallel(const Grid &grid); + + Grid buildSpatialGrid() const; + + public: + Game(); + + void run(); + + size_t getThreadCount() const { return m_threadPool.getThreadCount(); } }; diff --git a/src/InputManager.cpp b/src/InputManager.cpp index d717db1..fb571f1 100644 --- a/src/InputManager.cpp +++ b/src/InputManager.cpp @@ -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) { - while (const std::optional event = window.pollEvent()) { - if (event->is()) { - window.close(); - return; - } - if (auto kP = event->getIf()) m_keyCallback(*kP); - if (auto mP = event->getIf()) - m_mouseCallback(*mP); +void InputManager::processEvents(sf::Window& window) const { + while (const std::optional event = window.pollEvent()) { + if (event->is()) { + window.close(); + return; } + if (const auto kP = event->getIf()) + m_keyCallback(*kP); + if (const auto mP = event->getIf()) + m_mouseCallback(*mP); + } } diff --git a/src/InputManager.hpp b/src/InputManager.hpp index 405e51b..e9bec4b 100644 --- a/src/InputManager.hpp +++ b/src/InputManager.hpp @@ -4,16 +4,16 @@ #include class InputManager { - public: - using KeyCallback = std::function; - using MouseCallback = - std::function; + public: + using KeyCallback = std::function; + using MouseCallback = + std::function; - InputManager(KeyCallback keyCb, MouseCallback mouseCb); + InputManager(KeyCallback keyCb, MouseCallback mouseCb); - void processEvents(sf::Window& window); + void processEvents(sf::Window& window) const; - private: - KeyCallback m_keyCallback; - MouseCallback m_mouseCallback; + private: + KeyCallback m_keyCallback; + MouseCallback m_mouseCallback; }; diff --git a/src/PhysicalObject.hpp b/src/PhysicalObject.hpp index bfef246..f256d39 100644 --- a/src/PhysicalObject.hpp +++ b/src/PhysicalObject.hpp @@ -3,9 +3,12 @@ #include 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; + 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; }; diff --git a/src/ThreadPool.hpp b/src/ThreadPool.hpp index d1db76c..0d62eb6 100644 --- a/src/ThreadPool.hpp +++ b/src/ThreadPool.hpp @@ -8,76 +8,75 @@ #include class ThreadPool { - public: - explicit ThreadPool(size_t numThreads); - ~ThreadPool(); - size_t getThreadCount() const { return workers.size(); } + public: + explicit ThreadPool(size_t numThreads); - template - auto enqueue(F&& f, Args&&... args) - -> std::future::type>; + ~ThreadPool(); - private: - std::vector workers; + [[nodiscard]] size_t getThreadCount() const { return workers.size(); } - std::queue> tasks; + template + auto enqueue(F &&f, Args &&...args) + -> std::future >; - std::mutex queueMutex; - std::condition_variable condition; - bool stop; + private: + std::vector workers; + + std::queue > tasks; + + std::mutex queueMutex; + std::condition_variable condition; + bool stop; }; -inline ThreadPool::ThreadPool(size_t numThreads) : stop(false) { - for (size_t i = 0; i < numThreads; ++i) { - workers.emplace_back([this] { - while (true) { - std::function task; +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 task; + { + std::unique_lock 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; - if (this->stop && this->tasks.empty()) return; + task = std::move(this->tasks.front()); + this->tasks.pop(); + } - task = std::move(this->tasks.front()); - this->tasks.pop(); - } - - task(); - } - }); - } + task(); + } + }); + } } inline ThreadPool::~ThreadPool() { - { - std::unique_lock lock(queueMutex); - stop = true; - } - condition.notify_all(); + { + std::unique_lock lock(queueMutex); + stop = true; + } + condition.notify_all(); - for (auto& worker : workers) worker.join(); + for (auto &worker : workers) worker.join(); } template -inline auto ThreadPool::enqueue(F&& f, Args&&... args) - -> std::future::type> { - using return_type = typename std::invoke_result::type; +auto ThreadPool::enqueue(F &&f, Args &&...args) + -> std::future > { + using return_type = std::invoke_result_t; - auto task = std::make_shared>( - std::bind(std::forward(f), std::forward(args)...)); + auto task = std::make_shared >( + std::bind(std::forward(f), std::forward(args)...)); - std::future res = task->get_future(); + std::future res = task->get_future(); + { + std::unique_lock lock(queueMutex); + if (stop) throw std::runtime_error("enqueue on stopped ThreadPool"); - { - 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(); - return res; -} \ No newline at end of file + condition.notify_one(); + return res; +} diff --git a/src/ThreadUtils.hpp b/src/ThreadUtils.hpp new file mode 100644 index 0000000..cc1722e --- /dev/null +++ b/src/ThreadUtils.hpp @@ -0,0 +1,8 @@ +#include + +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 diff --git a/src/VectorMath.cpp b/src/VectorMath.cpp index 9e08c08..665c87e 100644 --- a/src/VectorMath.cpp +++ b/src/VectorMath.cpp @@ -3,16 +3,16 @@ #include namespace VectorMath { -float length(const sf::Vector2f& vector) { - return std::sqrt(vector.x * vector.x + vector.y * vector.y); +float length(const sf::Vector2f &vector) { + return std::sqrt(vector.x * vector.x + vector.y * vector.y); } -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); - } - return vector; +sf::Vector2f normalize(const sf::Vector2f &vector) { + 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; } } // namespace VectorMath diff --git a/src/VectorMath.hpp b/src/VectorMath.hpp index 5be16fb..1ba7623 100644 --- a/src/VectorMath.hpp +++ b/src/VectorMath.hpp @@ -2,6 +2,7 @@ #include namespace VectorMath { -float length(const sf::Vector2f& vector); -sf::Vector2f normalize(const sf::Vector2f& vector); +float length(const sf::Vector2f &vector); + +sf::Vector2f normalize(const sf::Vector2f &vector); } // namespace VectorMath diff --git a/src/main.cpp b/src/main.cpp index 015c122..ff60ccf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,7 @@ #include "Game.hpp" int main() { - Game game; - game.run(); - return 0; + Game game; + game.run(); + return 0; }