diff --git a/Sources/CppBackend/CMakeLists.txt b/Sources/CppBackend/CMakeLists.txt index c0b8282..c118a0f 100644 --- a/Sources/CppBackend/CMakeLists.txt +++ b/Sources/CppBackend/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(CppBackend STATIC backend.h backend.cpp) +add_library(CppBackend STATIC ball.hpp ball.cpp) set_property(TARGET CppBackend PROPERTY Swift_MODULE_NAME "CppBackend") set_property(TARGET CppBackend PROPERTY CXX_STANDARD 20) diff --git a/Sources/CppBackend/backend.cpp b/Sources/CppBackend/backend.cpp deleted file mode 100644 index 1917ed3..0000000 --- a/Sources/CppBackend/backend.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include "backend.h" - -#include - -void backend_init() { - std::cout << "Hello World" << std::endl; -} diff --git a/Sources/CppBackend/backend.h b/Sources/CppBackend/backend.h deleted file mode 100644 index 592be9d..0000000 --- a/Sources/CppBackend/backend.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void backend_init(); diff --git a/Sources/CppBackend/ball.cpp b/Sources/CppBackend/ball.cpp new file mode 100644 index 0000000..6078a2b --- /dev/null +++ b/Sources/CppBackend/ball.cpp @@ -0,0 +1,41 @@ +#include "ball.hpp" +#include + + +Ball::Ball(vec2f pos, float angle, float ballSize) noexcept : + _position(pos), + _velocity(simd::make( + std::cos(angle * M_PI * 2.0f), + -std::sin(angle * M_PI * 2.0f) + )), + _size(ballSize) { +} + +void Ball::update(float deltaTime) noexcept { + _position += _velocity * speed * deltaTime; + if (_position.x < _size) { + _velocity.x = -_velocity.x; + _position.x = _size; + } else if (_position.x > worldSize - _size) { + _velocity.x = -_velocity.x; + _position.x = worldSize - _size; + } + if (_position.y < _size) { + _velocity.y = -_velocity.y; + _position.y = _size; + } else if (_position.y > worldSize - _size) { + _velocity.y = -_velocity.y; + _position.y = worldSize - _size; + } +} + + +void BallWorld::add(Ball::vec2f pos, float angle, float ballSize) noexcept { + balls.emplace_back(Ball{ pos, angle, ballSize }); +} + +void BallWorld::update(float deltaTime) noexcept { + for (auto& ball : balls) { + ball.update(deltaTime); + } +} diff --git a/Sources/CppBackend/ball.hpp b/Sources/CppBackend/ball.hpp new file mode 100644 index 0000000..7faca3d --- /dev/null +++ b/Sources/CppBackend/ball.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +struct Ball { + using vec2f = simd::float2; + +private: + constexpr static float speed = 80.0f; + constexpr static float worldSize = 512.0f; + + vec2f _position, _velocity; + float _size; + +public: + Ball(vec2f pos, float angle, float ballSize) noexcept; + virtual ~Ball() noexcept = default; + + void update(float deltaTime) noexcept; + + [[nodiscard]] constexpr const vec2f& position() const noexcept { + return _position; + } + [[nodiscard]] constexpr const vec2f& velocity() const noexcept { + return _velocity; + } + [[nodiscard]] constexpr const float size() const noexcept { + return _size; + } +}; + + +struct BallWorld { + std::vector balls; + + void add(Ball::vec2f pos, float angle, float ballSize) noexcept; + void update(float deltaTime) noexcept; +}; diff --git a/Sources/CppBackend/module.modulemap b/Sources/CppBackend/module.modulemap index 3b24d92..5361cb5 100644 --- a/Sources/CppBackend/module.modulemap +++ b/Sources/CppBackend/module.modulemap @@ -1,3 +1,3 @@ module CppBackend { - header "backend.h" + header "ball.hpp" } diff --git a/Sources/SwiftFrontend/Application.swift b/Sources/SwiftFrontend/Application.swift index d47be0c..bb4f452 100644 --- a/Sources/SwiftFrontend/Application.swift +++ b/Sources/SwiftFrontend/Application.swift @@ -6,19 +6,26 @@ // import Foundation +import CppBackend import SDL3 class Application { private var window: OpaquePointer? = nil private var renderer: OpaquePointer? = nil + private var balls = BallWorld() + private var lastCounter: UInt64 = 0 + private func initialize() -> ApplicationExecutionState { guard SDL_Init(SDL_INIT_VIDEO) >= 0 else { print("SDL_Init() error: \(String(cString: SDL_GetError()))") return .exitFailure } - window = SDL_CreateWindow("Hello World", 512, 512, 0) + let width: Int32 = 512, height: Int32 = 512 + let sdlWindowResizable: SDL_WindowFlags = 0x0000000000000020 + let sdlWindowHighPixelDensity: SDL_WindowFlags = 0x0000000000002000 + window = SDL_CreateWindow("Hello World", width, height, sdlWindowResizable | sdlWindowHighPixelDensity) guard window != nil else { print("SDL_CreateWindow() error: \(String(cString: SDL_GetError()))") return .exitFailure @@ -29,6 +36,19 @@ class Application { print("SDL_CreateRenderer() error: \(String(cString: SDL_GetError()))") return .exitFailure } + SDL_SetRenderVSync(renderer, 1) + SDL_SetRenderLogicalPresentation(renderer, 512, 512, SDL_LOGICAL_PRESENTATION_LETTERBOX, SDL_SCALEMODE_BEST) + + let ballOrigin = SIMD2(Float(width), Float(height)) * 0.5 + for _ in 0..<10 { + balls.add( + ballOrigin, + Float(arc4random()) / Float(UInt32.max), + Float(arc4random_uniform(32 - 3) + 3) + ) + } + + lastCounter = SDL_GetPerformanceCounter() return .running } @@ -58,16 +78,35 @@ class Application { } } - private func paint() -> ApplicationExecutionState { + private func paint(_ deltaTime: Float) -> ApplicationExecutionState { + balls.update(deltaTime) + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255) SDL_RenderClear(renderer) - var rect = SDL_FRect(x: 0, y: 0, w: 100, h: 100) + SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255) - SDL_RenderFillRect(renderer, &rect) + for ball in balls.balls { + let position = ball.position().pointee, size = ball.size() + var rect = SDL_FRect( + x: position.x - size, + y: position.y - size, + w: size * 2.0, + h: size * 2.0 + ) + SDL_RenderFillRect(renderer, &rect) + } + SDL_RenderPresent(renderer) return .running } + private func deltaTime() -> Double { + let counter = SDL_GetPerformanceCounter() + let divisor = 1.0 / Double(SDL_GetPerformanceFrequency()) + defer { lastCounter = counter } + return Double(counter &- lastCounter) * divisor + } + func run() -> Int32 { var res = initialize() quit: while res == .running { @@ -78,7 +117,7 @@ class Application { break quit } } - res = paint() + res = paint(Float(deltaTime())) } return res == .exitSuccess ? 0 : 1 }