308 lines
6.8 KiB
C
308 lines
6.8 KiB
C
#include "stdafx.h"
|
|
#include "Starfield.h"
|
|
#include "Random.h"
|
|
|
|
#include <math.h>
|
|
#include <time.h>
|
|
|
|
#define WMU_FRAMERATE_NAME _T("SetFramerate_{6CFC2304-CE89-40c4-881C-988D36FBB978}")
|
|
#define DENSITY 3
|
|
#define LAYERS 5
|
|
|
|
const COLORREF crTransColors[] = {
|
|
RGB(0x5b, 0xce, 0xfa),
|
|
RGB(0xf5, 0xa9, 0xb8),
|
|
RGB(0xff, 0xff, 0xff),
|
|
};
|
|
|
|
typedef struct _StarLayer {
|
|
HBITMAP hbmFrame;
|
|
DOUBLE x;
|
|
BYTE alpha;
|
|
} StarLayer;
|
|
|
|
typedef struct _StarState {
|
|
StarLayer layers[LAYERS];
|
|
HBITMAP hbmColors[3]; // for each stripe
|
|
HDC hMemDC; // blits to screen
|
|
HDC hBlitDC; // blits to hMemDC
|
|
} StarState;
|
|
|
|
static void
|
|
DeleteState(StarState *state)
|
|
{
|
|
if (state) {
|
|
int i;
|
|
|
|
if (state->hBlitDC) DeleteDC(state->hBlitDC);
|
|
if (state->hMemDC) DeleteDC(state->hMemDC);
|
|
|
|
for (i = 0; i < LAYERS; i++) {
|
|
if (state->layers[i].hbmFrame) {
|
|
DeleteObject(state->layers[i].hbmFrame);
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
if (state->hbmColors[i]) DeleteObject(state->hbmColors[i]);
|
|
}
|
|
|
|
// The state is usually stored in the stack, so zero out any
|
|
// values that might cause issues in case of reuse
|
|
ZeroMemory(state, sizeof(StarState));
|
|
}
|
|
}
|
|
|
|
// Initializes the StarState based on the given client area RECT
|
|
static void
|
|
InitState(HDC hDC,
|
|
StarState *state,
|
|
LPRECT rcArea)
|
|
{
|
|
UINT i, // iterators
|
|
iStripeSplit; // height per stripe
|
|
HBITMAP hbmColors[3];
|
|
DWORD dwStarCount = rcArea->bottom * DENSITY;
|
|
|
|
iStripeSplit = rcArea->bottom / 5;
|
|
|
|
// init DCs
|
|
state->hMemDC = CreateCompatibleDC(hDC);
|
|
state->hBlitDC = CreateCompatibleDC(hDC);
|
|
|
|
// init layer bitmaps
|
|
for (i = 0; i < LAYERS; i++) {
|
|
HBITMAP hbmOrig;
|
|
state->layers[i].hbmFrame = CreateCompatibleBitmap(hDC,
|
|
rcArea->right,
|
|
rcArea->bottom);
|
|
state->layers[i].x = 0;
|
|
state->layers[i].alpha = 159 + (i * 24);
|
|
|
|
// Before stars, fill the frame with the void of space
|
|
hbmOrig = SelectObject(
|
|
state->hBlitDC,
|
|
state->layers[i].hbmFrame);
|
|
BitBlt(state->hBlitDC,
|
|
0, 0,
|
|
rcArea->right, rcArea->bottom,
|
|
NULL,
|
|
0, 0,
|
|
BLACKNESS);
|
|
SelectObject(
|
|
state->hBlitDC,
|
|
hbmOrig);
|
|
}
|
|
|
|
// we only need to init the stripes, thanks to AlphaBlend
|
|
for (i = 0; i < 3; i++) {
|
|
HBITMAP hbmOrig;
|
|
|
|
hbmColors[i] = CreateCompatibleBitmap(hDC, 1, 1);
|
|
hbmOrig = SelectObject(state->hBlitDC,
|
|
hbmColors[i]);
|
|
SetPixel(state->hBlitDC,
|
|
0, 0, crTransColors[i]);
|
|
SelectObject(state->hBlitDC, hbmOrig);
|
|
}
|
|
|
|
// arm the RNG
|
|
xorsrand((unsigned)time(NULL));
|
|
|
|
// create each star, AlphaBlending as we go for ideal effect
|
|
for (i = 0; i < dwStarCount; i++) {
|
|
int layer = (WORD)(xorrand() >> 48) % LAYERS,
|
|
x = (int)((WORD)xorrand()) % (rcArea->right - 4),
|
|
y = i / DENSITY,
|
|
stripe = y / iStripeSplit,
|
|
width = max((int)(((float)layer + 3.0f) / 2.0f), 1);
|
|
|
|
HBITMAP hbmColor = hbmColors[(stripe < 3) ? stripe :
|
|
(stripe == 3) ? 1 : 0],
|
|
hbmOrig,
|
|
hbmBlitOrig;
|
|
|
|
BLENDFUNCTION bf = {
|
|
AC_SRC_OVER,
|
|
0,
|
|
state->layers[layer].alpha,
|
|
0
|
|
};
|
|
|
|
hbmOrig = SelectObject(state->hMemDC, state->layers[layer].hbmFrame);
|
|
hbmBlitOrig = SelectObject(state->hBlitDC, hbmColor);
|
|
AlphaBlend(state->hMemDC,
|
|
x - (width / 2), y - (width / 2),
|
|
width, width,
|
|
state->hBlitDC,
|
|
0, 0,
|
|
1, 1,
|
|
bf);
|
|
|
|
if (x + width > rcArea->right) {
|
|
// loop around to make the square look complete
|
|
AlphaBlend(state->hMemDC,
|
|
0, y,
|
|
(x + width) - rcArea->right, width,
|
|
state->hBlitDC,
|
|
0, 0,
|
|
1, 1,
|
|
bf);
|
|
}
|
|
|
|
SelectObject(state->hBlitDC, hbmBlitOrig);
|
|
SelectObject(state->hMemDC, hbmOrig);
|
|
}
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
DeleteObject(hbmColors[i]);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void
|
|
DrawStars(HDC hDC,
|
|
StarState *state,
|
|
LPRECT rcArea)
|
|
{
|
|
// Logic taken from https://github.com/lunasorcery/keygender/blob/main/script.js
|
|
UINT i;
|
|
HBITMAP hbmOrig,
|
|
hbmFrame = CreateCompatibleBitmap(hDC,
|
|
rcArea->right,
|
|
rcArea->bottom);
|
|
static DWORD dwLastRun = 0;
|
|
DOUBLE dSpeedMult = 1.0;
|
|
|
|
hbmOrig = SelectObject(state->hMemDC, hbmFrame);
|
|
// Without this, Win98 starts blitting VRAM to our control
|
|
BitBlt(state->hMemDC,
|
|
0, 0,
|
|
rcArea->right, rcArea->bottom,
|
|
NULL,
|
|
0, 0,
|
|
BLACKNESS);
|
|
|
|
// try to keep a consistent speed across frames
|
|
if (dwLastRun != 0) {
|
|
DWORD dwElapsed = GetTickCount() - dwLastRun;
|
|
dSpeedMult = max(1.0, (dwElapsed / 17.0));
|
|
}
|
|
dwLastRun = GetTickCount();
|
|
|
|
for (i = 0; i < LAYERS; i++) {
|
|
HBITMAP hbmBlitOrig = NULL;
|
|
|
|
// split each layer into two pieces, and stitch them together
|
|
int widths[2];
|
|
widths[0] = (int)state->layers[i].x + rcArea->right;
|
|
widths[1] = rcArea->right - widths[0];
|
|
|
|
hbmBlitOrig = SelectObject(state->hBlitDC, state->layers[i].hbmFrame);
|
|
// Left side of the split
|
|
TransparentBlt(state->hMemDC,
|
|
0, 0,
|
|
widths[0], rcArea->bottom,
|
|
state->hBlitDC,
|
|
widths[1], 0,
|
|
widths[0], rcArea->bottom,
|
|
0);
|
|
// Right side of the split
|
|
TransparentBlt(state->hMemDC,
|
|
widths[0], 0,
|
|
widths[1], rcArea->bottom,
|
|
state->hBlitDC,
|
|
0, 0,
|
|
widths[1], rcArea->bottom,
|
|
0);
|
|
SelectObject(state->hBlitDC, hbmBlitOrig);
|
|
|
|
// Update frame location
|
|
state->layers[i].x -= ((DOUBLE)i + 1.5) * dSpeedMult;
|
|
if (state->layers[i].x + rcArea->right < 0) {
|
|
state->layers[i].x += rcArea->right;
|
|
}
|
|
}
|
|
|
|
BitBlt(hDC,
|
|
rcArea->left,
|
|
rcArea->top,
|
|
rcArea->right,
|
|
rcArea->bottom,
|
|
state->hMemDC,
|
|
0, 0,
|
|
SRCCOPY);
|
|
|
|
SelectObject(state->hMemDC, hbmOrig);
|
|
DeleteObject(hbmFrame);
|
|
|
|
return;
|
|
}
|
|
|
|
static LRESULT CALLBACK
|
|
StarfieldWndProc(HWND hWnd,
|
|
UINT uMsg,
|
|
WPARAM wParam,
|
|
LPARAM lParam)
|
|
{
|
|
static RECT rcClient = {0};
|
|
static StarState state = {0};
|
|
static HBITMAP hbmColors[3] = {0};
|
|
static HDC hDC = NULL;
|
|
// Default to 60fps
|
|
static DOUBLE fFrameRate = (1.0 / 60) *
|
|
1000.0;
|
|
|
|
switch (uMsg) {
|
|
case WM_CREATE:
|
|
hDC = GetDC(hWnd);
|
|
|
|
GetClientRect(hWnd, &rcClient);
|
|
InitState(hDC, &state, &rcClient);
|
|
/* PASSTHRU */
|
|
case WM_TIMER:
|
|
// Frame tick!
|
|
DrawStars(hDC, &state, &rcClient);
|
|
SetTimer(hWnd, 0x69, fFrameRate, NULL);
|
|
break;
|
|
|
|
case WM_DESTROY:
|
|
DeleteState(&state);
|
|
DeleteDC(hDC);
|
|
hDC = NULL;
|
|
break;
|
|
|
|
default:
|
|
if (uMsg == WMU_FRAMERATE) {
|
|
// Update our frame rate
|
|
fFrameRate = (1.0 / (DOUBLE)(LONG)wParam)
|
|
* 1000.0;
|
|
return 0;
|
|
} else {
|
|
return DefWindowProc(hWnd, uMsg, wParam, lParam);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
InitStarfieldControl(HINSTANCE hInst)
|
|
{
|
|
WNDCLASSEX wcex = {0};
|
|
wcex.cbSize = sizeof(wcex);
|
|
wcex.lpfnWndProc = StarfieldWndProc;
|
|
wcex.hInstance = hInst;
|
|
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
wcex.lpszClassName = STARFIELD_CLASS;
|
|
RegisterClassEx(&wcex);
|
|
|
|
WMU_FRAMERATE = RegisterWindowMessage(WMU_FRAMERATE_NAME);
|
|
}
|
|
|
|
void
|
|
DeinitStarfieldControl(HINSTANCE hInst)
|
|
{
|
|
UnregisterClass(STARFIELD_CLASS, hInst);
|
|
} |