Here's a completely unoptimized version I made in C++ for Windows:
Code:
// BubbleUni.cpp : Defines the entry point for the application.
//
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
// Windows Header Files
#include <windows.h>
// C RunTime Header Files
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>
#include "BubbleUni.h"
#define _USE_MATH_DEFINES
#include "math.h"
void DrawDIB();
char *g_dib = nullptr;
size_t g_dibSize = 0;
// Resolution of our DIB
const int WIDTH = 1280; // 640 * 2
const int HEIGHT = 960; // 480 * 2
// State for actual bubble universe program
const int N = 200;
const int SCALE = 90;
const double ASPECT = (double)WIDTH / HEIGHT;
const double TAU = M_PI / 2;
const double r = TAU / 256;
double t = 0;
void DrawFrame()
{
if (!g_dib)
return; // Don't compute if we don't have a DIB to draw to!
double x = 0;
double u = 0;
double v = 0;
int hw = WIDTH / 2;
int hh = HEIGHT / 2;
// Fill with black
memset(g_dib, 0, g_dibSize);
for (int i = 0; i <= N; ++i)
{
for (int j = 0; j <= N; ++j)
{
u = sin(i + v) + sin(r * i + x);
v = cos(i + v) + cos(r * i + x);
x = u + t;
int dx = (int)(u * SCALE) + hw;
int dy = (int)(v * SCALE * ASPECT) + hh;
int off = (dx + dy * WIDTH) * 3;
g_dib[off + 0] = i;
g_dib[off + 1] = j;
g_dib[off + 2] = 99;
}
}
t += 1.0 / N;
DrawDIB();
}
/*
* Below is the bulk of the Win32 startup/shutdown code.
*/
// Global Variables:
HINSTANCE g_instance; // current instance
// Save the drawing context globally.
HDC g_dc;
BITMAPINFO bmap;
int g_width;
int g_height;
#define MAIN_CLS _T("BUBBLE_UNI_MAIN_WIN__")
#define TITLE _T("Bubble Universe Test")
// Forward declarations of functions included in this code module:
ATOM MyRegisterClass();
BOOL InitInstance(int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
(void)(hPrevInstance);
(void)(lpCmdLine);
// TODO: Place code here.
// Initialize global strings
g_instance = hInstance; // Store instance handle in our global variable
// Perform application initialization:
if (!InitInstance(nCmdShow))
return FALSE;
MSG msg;
// Main message loop:
for (;;)
{
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
DrawFrame();
}
delete[] g_dib;
return (int)msg.wParam;
}
//
// FUNCTION: MyRegisterClass()
//
// PURPOSE: Registers the window class.
//
ATOM MyRegisterClass()
{
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = g_instance;
wcex.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = nullptr; // We'll take care of painting everything
wcex.lpszMenuName = nullptr; // No menu
wcex.lpszClassName = MAIN_CLS;
wcex.hIconSm = LoadIcon(nullptr, IDI_APPLICATION);
return RegisterClassExW(&wcex);
}
//
// FUNCTION: InitInstance(HINSTANCE, int)
//
// PURPOSE: Saves instance handle and creates main window
//
// COMMENTS:
//
// In this function, we save the instance handle in a global variable and
// create and display the main program window.
//
BOOL InitInstance(int nCmdShow)
{
MyRegisterClass();
HWND hWnd = CreateWindowW(
MAIN_CLS,
TITLE,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0,
CW_USEDEFAULT, 0,
nullptr,
nullptr,
g_instance,
nullptr);
if (!hWnd)
return FALSE;
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
void RebuildDIB()
{
delete[] g_dib;
g_dibSize = WIDTH * HEIGHT * 3; // 24-bpp
g_dib = new char[g_dibSize];
memset(&bmap, 0, sizeof(bmap));
bmap.bmiHeader.biSize = sizeof(bmap.bmiHeader);
bmap.bmiHeader.biWidth = WIDTH;
bmap.bmiHeader.biHeight = HEIGHT;
bmap.bmiHeader.biPlanes = 1;
bmap.bmiHeader.biBitCount = 24;
bmap.bmiHeader.biCompression = BI_RGB;
bmap.bmiHeader.biSizeImage = 0;
bmap.bmiHeader.biXPelsPerMeter = 0;
bmap.bmiHeader.biYPelsPerMeter = 0;
bmap.bmiHeader.biClrUsed = 0;
bmap.bmiHeader.biClrImportant = 0;
}
void DrawDIB()
{
StretchDIBits(g_dc, 0, 0, g_width, g_height, 0, 0, WIDTH, HEIGHT, g_dib, &bmap, 0, SRCCOPY);
}
//
// FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// PURPOSE: Processes messages for the main window.
//
// WM_COMMAND - process the application menu
// WM_PAINT - Paint the main window
// WM_DESTROY - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
LRESULT rval = 0;
switch (message)
{
case WM_CREATE:
g_dc = GetDC(hWnd);
break;
case WM_SIZE:
if (wParam == SIZE_MINIMIZED)
break; // Don't adjust if window is minimized.
g_width = (int)LOWORD(lParam);
g_height = (int)HIWORD(lParam);
RebuildDIB();
break;
case WM_DESTROY:
ReleaseDC(hWnd, g_dc);
PostQuitMessage(0);
break;
default:
rval = DefWindowProc(hWnd, message, wParam, lParam);
break;
}
return rval;
}
You may need to tweak for your specific system. I have a 4090 that I run at 3840x2160
The bitmap it draws to is a 1280x960 and then stretched to whatever size the Window is via the GDI. So this is pretty painfully slow really.
It might be possible to write this as GLSL shader and run almost completely on a video card these days. ^^;
Enjoy!
Edit:
Screen shot:
Attachment:
Screenshot 2024-05-30 003743.png [ 101.85 KiB | Viewed 1184 times ]