From 4951a7912409dc0a4568fe74c4c7cc549efbd824 Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Wed, 6 May 2026 12:52:41 +0200 Subject: [PATCH] added seperated components, now with a seperate physics engine. todo: add tex notation of physics.cpp --- bounce_cpp/bounce_cpp.vcxproj | 6 +- bounce_cpp/main.cpp | 219 ++++++++-------------------------- bounce_cpp/physics.cpp | 74 ++++++++++++ bounce_cpp/physics.h | 31 +++++ bounce_cpp/sound_handler.cpp | 135 +++++++++++++++++++++ bounce_cpp/sound_handler.h | 41 +++++++ 6 files changed, 333 insertions(+), 173 deletions(-) create mode 100644 bounce_cpp/physics.cpp create mode 100644 bounce_cpp/physics.h create mode 100644 bounce_cpp/sound_handler.cpp create mode 100644 bounce_cpp/sound_handler.h diff --git a/bounce_cpp/bounce_cpp.vcxproj b/bounce_cpp/bounce_cpp.vcxproj index 5b3b94c..fb7c52f 100644 --- a/bounce_cpp/bounce_cpp.vcxproj +++ b/bounce_cpp/bounce_cpp.vcxproj @@ -42,7 +42,7 @@ Application true - v145 + v143 Unicode @@ -129,9 +129,13 @@ + + + + diff --git a/bounce_cpp/main.cpp b/bounce_cpp/main.cpp index 2a338a4..20b13e6 100644 --- a/bounce_cpp/main.cpp +++ b/bounce_cpp/main.cpp @@ -8,6 +8,7 @@ Quick description of the app: - A hyrax picture which bounces around your desktop, like bonzi buddy but not destructive, more of a fun thing which you can open when you get bored. - Run the exe youll find out. + - you can click and it will awawa This is not malware or a virus, its purely for fun and entertainment purpouses. @@ -24,7 +25,10 @@ rattatwinko - 04/05/26 #include #include #include +#include #include "resource.h" +#include "sound_handler.h" +#include "physics.h" #pragma comment(lib, "gdiplus.lib") #pragma comment(lib, "winmm.lib") @@ -32,19 +36,10 @@ rattatwinko - 04/05/26 using namespace Gdiplus; // consts -static constexpr double GRAVITY = 1200.0; -static constexpr double RESTITUTION = 0.6; -static constexpr double AIR_DRAG = 0.999; -static constexpr double GROUND_FRICTION = 0.8; -static constexpr double REST_THRESHOLD = 5.0; static constexpr UINT TIMER_MS = 10; static constexpr int IMAGE_RESOURCE = 111; static constexpr int RESIZE_BORDER = 12; // make wider so easier grab static constexpr int DRAG_THRESHOLD = 5; // pixels before its a drag not a click -static constexpr int WAV_COUNT = 9; -static constexpr int MAX_PEAKS = 64; // max screams per sound -static constexpr double PEAK_HOLD = 0.10; // seconds which we hold the awawa face -static constexpr double PEAK_THRESH = 0.65; // fraction of max amplitude to count as a peak // globals static ULONG_PTR g_gdiplusToken = 0; @@ -54,10 +49,8 @@ static int g_winW = 640; static int g_winH = 480; static LARGE_INTEGER g_freq = {}; -// physics states -static double g_x = 200.0, g_y = 50.0; -static double g_vx = 0.0, g_vy = 0.0; -static double g_lastTime = 0.0; +// physics object (initial position 200,50 with zero velocity) +static physics g_physics(200.0, 50.0, 0.0, 0.0); // resize / drag status static bool g_resizing = false; @@ -68,14 +61,10 @@ static int g_dragOffsetY = 0; static POINT g_lastMouse = {}; static POINT g_clickOrigin = {}; static double g_lastMouseTime = 0.0; +static double g_lastTime = 0.0; -// scream face stuff static bool g_screaming = false; -static double g_screamUntil = 0.0; // timestamp to stop screaming -static double g_soundStart = 0.0; // when playback began -static double g_peakTimes[MAX_PEAKS] = {}; -static int g_peakCount = 0; -static int g_peakIdx = 0; // next peak we havent fired yet +static std::unique_ptr g_sound; // helper definitions static double Now() { @@ -124,131 +113,6 @@ static Image* LoadImageFromResource(HINSTANCE hInst, int resId, LPCWSTR resType) return img; } -// walk RAW fucking PCM data and check for peaks -static void FindWavPeaks(const BYTE* buf, DWORD size) { - g_peakCount = 0; - g_peakIdx = 0; - - if (size < 44) return; - if (memcmp(buf, "RIFF", 4) != 0 || memcmp(buf + 8, "WAVE", 4) != 0) return; - - WORD channels = 0; - DWORD sampleRate = 0; - WORD bitsPerSample = 0; - const BYTE* pcm = nullptr; - DWORD pcmSize = 0; - - // walk the chunks - DWORD pos = 12; - while (pos + 8 <= size) { - DWORD chunkSize = *reinterpret_cast(buf + pos + 4); - - if (memcmp(buf + pos, "fmt ", 4) == 0 && chunkSize >= 16) { - channels = *reinterpret_cast (buf + pos + 10); - sampleRate = *reinterpret_cast(buf + pos + 12); - bitsPerSample = *reinterpret_cast (buf + pos + 22); - } - else if (memcmp(buf + pos, "data", 4) == 0) { - pcm = buf + pos + 8; - pcmSize = chunkSize; - break; - } - - pos += 8 + chunkSize; - if (chunkSize & 1) pos++; // align word cause microsoft likes to fuck my ass - } - - // only 16bit PCM cause everything else is just stoopid - if (!pcm || !sampleRate || bitsPerSample != 16) return; - - DWORD totalSamples = pcmSize / (channels * sizeof(int16_t)); - - // global maximum - int maxAmp = 1; - for (DWORD i = 0; i < pcmSize / sizeof(int16_t); i++) { - int s = abs((int)*reinterpret_cast(pcm + i * sizeof(int16_t))); - if (s > maxAmp) maxAmp = s; - } - - int threshold = static_cast(maxAmp * PEAK_THRESH); - DWORD windowSamples = sampleRate / 50; // 20ms windows - DWORD debounce = sampleRate / 8; // 125ms min gap so we dont spam - DWORD lastPeak = 0xFFFFFFFF; - - // stupid fucking logic. scan for loud moments - for (DWORD s = 0; s + windowSamples <= totalSamples; s += windowSamples / 2) { - int winMax = 0; - for (DWORD j = 0; j < windowSamples * channels; j++) { - DWORD byteOff = (s * channels + j) * sizeof(int16_t); - if (byteOff + sizeof(int16_t) > pcmSize) break; - int amp = abs((int)*reinterpret_cast(pcm + byteOff)); - if (amp > winMax) winMax = amp; - } - - bool gapOk = (lastPeak == 0xFFFFFFFF) || (s - lastPeak >= debounce); - if (winMax >= threshold && gapOk && g_peakCount < MAX_PEAKS) { - g_peakTimes[g_peakCount++] = static_cast(s) / sampleRate; - lastPeak = s; - } - } -} - -// play a random hyrax sound and prep the scream timeline -static void PlayRandomSound(HINSTANCE hInst) { - int id = IDR_WAVE1 + (rand() % WAV_COUNT); - - // grab raw wave data and look for peaks ; possible cause of .rc WAVE type - HRSRC hRes = FindResource(hInst, MAKEINTRESOURCE(id), L"WAVE"); - HGLOBAL hData = hRes ? LoadResource(hInst, hRes) : nullptr; - BYTE* pData = hData ? static_cast(LockResource(hData)) : nullptr; - DWORD sz = hRes ? SizeofResource(hInst, hRes) : 0; - - if (pData && sz) - FindWavPeaks(pData, sz); - - g_soundStart = Now(); - - PlaySoundW(MAKEINTRESOURCEW(id), hInst, SND_RESOURCE | SND_ASYNC | SND_NODEFAULT); -} - -// physics "engine" -static void StepPhysics(double dt) { - const int screenW = GetSystemMetrics(SM_CXSCREEN); - const int screenH = GetSystemMetrics(SM_CYSCREEN); - - g_vy += GRAVITY * dt; - g_vx *= AIR_DRAG; - g_vy *= AIR_DRAG; - - g_x += g_vx * dt; - g_y += g_vy * dt; - - // left / right monitor borders - if (g_x < 0.0) { - g_x = 0.0; - g_vx = -g_vx * RESTITUTION; - } - else if (g_x > screenW - g_winW) { - g_x = static_cast(screenW - g_winW); - g_vx = -g_vx * RESTITUTION; - } - - // top - if (g_y < 0.0) { - g_y = 0.0; - g_vy = -g_vy * RESTITUTION; - } - - // bottom - if (g_y > screenH - g_winH) { - g_y = static_cast(screenH - g_winH); - g_vy = -g_vy * RESTITUTION; - g_vx *= GROUND_FRICTION; - if (fabs(g_vy) < REST_THRESHOLD) - g_vy = 0.0; - } -} - // win api window proc static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { @@ -267,12 +131,26 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara g_imageScream = LoadImageFromResource(hInst, IDB_BITMAP1, L"JPG"); if (g_image) { + /* g_winW = static_cast(g_image->GetWidth()); g_winH = static_cast(g_image->GetHeight()); + */ + g_winW = 480; + g_winH = 480; SetWindowPos(hwnd, nullptr, 0, 0, g_winW, g_winH, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); } + // sound handler + g_sound = std::make_unique( + hInst, + std::initializer_list{ + IDR_WAVE1, IDR_WAVE2, IDR_WAVE3, + IDR_WAVE4, IDR_WAVE5, IDR_WAVE6, + IDR_WAVE7, IDR_WAVE8, IDR_WAVE9 + } + ); + g_lastTime = Now(); g_lastMouseTime = g_lastTime; @@ -286,7 +164,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara g_didDrag = false; g_dragOffsetX = GET_X_LPARAM(lParam); g_dragOffsetY = GET_Y_LPARAM(lParam); - g_vx = g_vy = 0.0; + g_physics.resetVelocity(); // stop all motion GetCursorPos(&g_lastMouse); g_clickOrigin = g_lastMouse; @@ -311,17 +189,17 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara const double dt = now - g_lastMouseTime; if (dt > 0.0) { - g_vx = (p.x - g_lastMouse.x) / dt; - g_vy = (p.y - g_lastMouse.y) / dt; + double vx = (p.x - g_lastMouse.x) / dt; + double vy = (p.y - g_lastMouse.y) / dt; + g_physics.setVelocity(vx, vy); } g_lastMouse = p; g_lastMouseTime = now; - g_x = p.x - g_dragOffsetX; - g_y = p.y - g_dragOffsetY; + g_physics.setPos(p.x - g_dragOffsetX, p.y - g_dragOffsetY); - SetWindowPos(hwnd, nullptr, static_cast(g_x), static_cast(g_y), + SetWindowPos(hwnd, nullptr, static_cast(g_physics.x()), static_cast(g_physics.y()), 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); break; } @@ -329,8 +207,9 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara // drag end or click case WM_LBUTTONUP: { if (!g_didDrag) { - HINSTANCE hInst = reinterpret_cast(GetWindowLongPtrW(hwnd, GWLP_HINSTANCE)); - PlayRandomSound(hInst); + // play random sound + if (g_sound) + g_sound->PlayRandom(); } g_dragging = false; ReleaseCapture(); @@ -344,13 +223,12 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara case WM_EXITSIZEMOVE: { g_resizing = false; - g_vx = g_vy = 0.0; + g_physics.resetVelocity(); // sync gx/y with resized cords RECT r; GetWindowRect(hwnd, &r); - g_x = static_cast(r.left); - g_y = static_cast(r.top); + g_physics.setPos(static_cast(r.left), static_cast(r.top)); g_lastTime = Now(); // after everything is clean then redraw break; @@ -400,27 +278,23 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara g_lastTime = now; if (!g_dragging && !g_resizing) { - StepPhysics(dt); - SetWindowPos(hwnd, nullptr, static_cast(g_x), static_cast(g_y), + int screenW = GetSystemMetrics(SM_CXSCREEN); + int screenH = GetSystemMetrics(SM_CYSCREEN); + g_physics.update(dt, screenW, screenH, g_winW, g_winH); + SetWindowPos(hwnd, nullptr, static_cast(g_physics.x()), static_cast(g_physics.y()), 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); } - // extend scream hold - if (g_peakCount > 0) { - double elapsed = now - g_soundStart; - while (g_peakIdx < g_peakCount && elapsed >= g_peakTimes[g_peakIdx]) { - g_screamUntil = now + PEAK_HOLD; - g_peakIdx++; + // use sound handler instead of doing this in main + if (g_sound) { + g_sound->Update(now); + bool shouldScream = (g_imageScream && g_sound->IsAwAwA(now)); + if (shouldScream != g_screaming) { + g_screaming = shouldScream; + InvalidateRect(hwnd, nullptr, FALSE); } } - // flip face state - bool shouldScream = (g_imageScream && now < g_screamUntil); - if (shouldScream != g_screaming) { - g_screaming = shouldScream; - InvalidateRect(hwnd, nullptr, FALSE); - } - break; } @@ -460,10 +334,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara DestroyWindow(hwnd); break; - + // consuela case WM_DESTROY: KillTimer(hwnd, 1); + g_sound.reset(); // clean up sound handler delete g_image; delete g_imageScream; g_image = g_imageScream = nullptr; @@ -491,7 +366,7 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR, int nCmdShow) { WS_EX_TOPMOST, L"Hyrax", L"", WS_POPUP, - static_cast(g_x), static_cast(g_y), + static_cast(g_physics.x()), static_cast(g_physics.y()), g_winW, g_winH, nullptr, nullptr, hInstance, nullptr ); diff --git a/bounce_cpp/physics.cpp b/bounce_cpp/physics.cpp new file mode 100644 index 0000000..e39f6e9 --- /dev/null +++ b/bounce_cpp/physics.cpp @@ -0,0 +1,74 @@ +/* +Preamble: + These exact physics calculations are documented in a TeX Document soon to be in the source tree of + the project. (as of 06/05/26 not in the tree) + + The physics calculations are done using a Semi-Implicit Euler integrator. (big words) + + | Parameter | Value | Description | Mapped code identifier | + |--------------------|--------|------------------------------------------------|------------------------------| + | `GRAVITY` | 1200.0 | Downward acceleration (px s⁻²) | `GRAVITY` (constant) | + | `RESTITUTION` | 0.6 | Energy retention after bounce | `RESTITUTION` (constant) | + | `AIR_DRAG` | 0.999 | Velocity multiplier per frame | `AIR_DRAG` (constant) | + | `GROUND_FRICTION` | 0.8 | Horizontal speed reduction on ground | `GROUND_FRICTION` (constant) | + | `REST_THRESHOLD` | 5.0 | Minimum `\|v_y\|` to consider at rest | `REST_THRESHOLD` (constant) | + + + 06/05/26 - rattatwinko ; when cleaning the source code +*/ + +#include "physics.h" +#include + +physics::physics() : physics(0.0, 0.0) {} + +physics::physics(double x, double y, double vx, double vy) + : m_x(x), m_y(y), m_vx(vx), m_vy(vy) { +} + +void physics::update(double dt, int screenWidth, int screenHeight, int objWidth, int objHeight) { + m_vy += GRAVITY * dt; + m_vx *= AIR_DRAG; + m_vy *= AIR_DRAG; + + m_x += m_vx * dt; + m_y += m_vy * dt; + + const double rightBound = static_cast(screenWidth - objWidth); + const double bottomBound = static_cast(screenHeight - objHeight); + + if (m_x < 0.0) { + m_x = 0.0; + m_vx = -m_vx * RESTITUTION; + } + else if (m_x > rightBound) { + m_x = rightBound; + m_vx = -m_vx * RESTITUTION; + } + + if (m_y < 0.0) { + m_y = 0.0; + m_vy = -m_vy * RESTITUTION; + } + else if (m_y > bottomBound) { + m_y = bottomBound; + m_vy = -m_vy * RESTITUTION; + m_vx *= GROUND_FRICTION; + if (std::fabs(m_vy) < REST_THRESHOLD) + m_vy = 0.0; + } +} + +void physics::setPos(double x, double y) { + m_x = x; + m_y = y; +} + +void physics::setVelocity(double vx, double vy) { + m_vx = vx; + m_vy = vy; +} + +void physics::resetVelocity() { + m_vx = m_vy = 0.0; +} \ No newline at end of file diff --git a/bounce_cpp/physics.h b/bounce_cpp/physics.h new file mode 100644 index 0000000..3e8187b --- /dev/null +++ b/bounce_cpp/physics.h @@ -0,0 +1,31 @@ +#pragma once + +#define scd static constexpr double + +class physics +{ +public: + scd GRAVITY = 1200.0; + scd RESTITUTION = 0.6; + scd AIR_DRAG = 0.999; + scd GROUND_FRICTION = 0.8; + scd REST_THRESHOLD = 5.0; + + physics(); + physics(double x, double y, double vx = 0.0, double vy = 0.0); + void update(double dt, int screenW, int screenH, int objW, int objH); + + // getters / setters + double x() const { return m_x; } + double y() const { return m_y; } + double vx() const { return m_vx; } + double vy() const { return m_vy; } + void setPos(double x, double y); + void setVelocity(double vx, double vy); + void resetVelocity(); + +private: + double m_x, m_y; + double m_vx, m_vy; +}; + diff --git a/bounce_cpp/sound_handler.cpp b/bounce_cpp/sound_handler.cpp new file mode 100644 index 0000000..ec636a2 --- /dev/null +++ b/bounce_cpp/sound_handler.cpp @@ -0,0 +1,135 @@ +#include "sound_handler.h" +#include +#include + +sound_handler::sound_handler(HINSTANCE hInst, std::initializer_list waveIds) + : m_hInst(hInst), m_wavIds(waveIds) +{ + for (int id : m_wavIds) + m_peaks.push_back(AnalyzeWavResource(id)); +} + +void sound_handler::PlayRandom() +{ + if (m_wavIds.empty()) return; + + int idx = rand() % static_cast(m_wavIds.size()); + m_currentWave = idx; + m_soundStart = Now(); + m_nextPeak = 0; + + int id = m_wavIds[idx]; + PlaySoundW(MAKEINTRESOURCEW(id), m_hInst, + SND_RESOURCE | SND_ASYNC | SND_NODEFAULT); +} + +void sound_handler::Update(double now) +{ + if (m_currentWave < 0 || m_currentWave >= static_cast(m_peaks.size())) + return; + + const auto& peaks = m_peaks[m_currentWave].times; + double elapsed = now - m_soundStart; + + while (m_nextPeak < static_cast(peaks.size()) && elapsed >= peaks[m_nextPeak]) { + m_screamUntil = now + p_hold; + ++m_nextPeak; + } +} + +bool sound_handler::IsAwAwA(double now) const +{ + return now < m_screamUntil; +} + +sound_handler::PeakData sound_handler::AnalyzeWavResource(int resId) +{ + PeakData data; + + HRSRC hRes = FindResource(m_hInst, MAKEINTRESOURCE(resId), L"WAVE"); + if (!hRes) return data; + + HGLOBAL hData = LoadResource(m_hInst, hRes); + if (!hData) return data; + + DWORD size = SizeofResource(m_hInst, hRes); + const BYTE* buf = static_cast(LockResource(hData)); + if (!buf) return data; + + if (size < 44) return data; + if (memcmp(buf, "RIFF", 4) != 0 || memcmp(buf + 8, "WAVE", 4) != 0) + return data; + + WORD channels = 0; + DWORD sampleRate = 0; + WORD bitsPerSample = 0; + const BYTE* pcm = nullptr; + DWORD pcmSize = 0; + + DWORD pos = 12; + while (pos + 8 <= size) { + DWORD chunkSize = *reinterpret_cast(buf + pos + 4); + + if (memcmp(buf + pos, "fmt ", 4) == 0 && chunkSize >= 16) { + channels = *reinterpret_cast(buf + pos + 10); + sampleRate = *reinterpret_cast(buf + pos + 12); + bitsPerSample = *reinterpret_cast(buf + pos + 22); + } + else if (memcmp(buf + pos, "data", 4) == 0) { + pcm = buf + pos + 8; + pcmSize = chunkSize; + break; + } + + pos += 8 + chunkSize; + if (chunkSize & 1) pos++; + } + + if (!pcm || bitsPerSample != 16) return data; + + DWORD totalSamples = pcmSize / (channels * sizeof(int16_t)); + + int maxAmp = 1; + for (DWORD i = 0; i < pcmSize / sizeof(int16_t); i++) { + int s = abs(static_cast( + *reinterpret_cast(pcm + i * sizeof(int16_t)))); + if (s > maxAmp) maxAmp = s; + } + + int threshold = static_cast(maxAmp * p_thresh); + DWORD windowSamples = sampleRate / 50; // 20 ms windows + DWORD debounce = sampleRate / 8; // 125 ms minimum gap + DWORD lastPeak = 0xFFFFFFFF; + + for (DWORD s = 0; s + windowSamples <= totalSamples; s += windowSamples / 2) { + int winMax = 0; + for (DWORD j = 0; j < windowSamples * channels; j++) { + DWORD byteOff = (s * channels + j) * sizeof(int16_t); + if (byteOff + sizeof(int16_t) > pcmSize) break; + int amp = abs(static_cast( + *reinterpret_cast(pcm + byteOff))); + if (amp > winMax) winMax = amp; + } + + bool gapOk = (lastPeak == 0xFFFFFFFF) || (s - lastPeak >= debounce); + if (winMax >= threshold && gapOk && data.times.size() < p_max) { + data.times.push_back(static_cast(s) / sampleRate); + lastPeak = s; + if (data.times.size() >= p_max) break; + } + } + + return data; +} + +double sound_handler::Now() +{ + static LARGE_INTEGER freq = [] { + LARGE_INTEGER f; + QueryPerformanceFrequency(&f); + return f; + }(); + LARGE_INTEGER t; + QueryPerformanceCounter(&t); + return static_cast(t.QuadPart) / static_cast(freq.QuadPart); +} \ No newline at end of file diff --git a/bounce_cpp/sound_handler.h b/bounce_cpp/sound_handler.h new file mode 100644 index 0000000..0b59675 --- /dev/null +++ b/bounce_cpp/sound_handler.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include +#include +#include + +using namespace std; + +class sound_handler +{ +public: + struct PeakData { + vector times; + }; + +private: + HINSTANCE m_hInst; + vector m_wavIds; + vector m_peaks; + + int m_currentWave = -1; + double m_soundStart = 0.0; // time of playsound + int m_nextPeak = 0; + double m_screamUntil = 0.0; + + static constexpr double p_hold = 0.10; + static constexpr double p_thresh = 0.65; + static constexpr int p_max = 64; + +public: + sound_handler(HINSTANCE hInst, initializer_list waveIds); + void PlayRandom(); + void Update(double now); + bool IsAwAwA(double now) const; + +private: + PeakData AnalyzeWavResource(int resId); + static double Now(); +}; \ No newline at end of file