145 BinaryMatrixWindow(
const std::string &path,
const std::string &title,
const int width,
const int height,
const bool fullscreen,
const bool enable_vsync,
const int requested_glyph_size,
const std::string &color) :
mxvk::
VK_Window(title, width, height, fullscreen,
MXVK_VALIDATION, enable_vsync), assetRoot(path.empty() ? std::string(binary_matrix_ASSET_DIR) : path), glyph_size(requested_glyph_size), rng(std::random_device{}()) {
146 if (assetRoot ==
".") {
147 assetRoot = binary_matrix_ASSET_DIR;
150 if (!color.empty()) {
151 const std::optional<SDL_Color> parsedColor = parse_color_spec(color);
153 throw mxvk::Exception(
"Invalid binary_matrix color: " + color +
" (expected #RRGGBB or R,G,B)");
155 digitColor = *parsedColor;
161 throw mxvk::Exception(
"Failed to initialize SDL_ttf: " + std::string(SDL_GetError()));
164 font.reset(TTF_OpenFont((assetRoot +
"/data/NotoSansCJK-Bold.ttc").c_str(), glyph_size));
166 throw mxvk::Exception(
"Failed to load binary matrix font: " + std::string(SDL_GetError()));
168 TTF_SetFontHinting(font.get(), TTF_HINTING_LIGHT);
170 const std::string spriteVertPath = assetRoot +
"/data/sprite.vert.spv";
171 const std::string backgroundFragPath = assetRoot +
"/data/background.frag.spv";
172 backgroundSprite =
createSprite(assetRoot +
"/data/bg.png", spriteVertPath, backgroundFragPath);
173 if (backgroundSprite ==
nullptr) {
174 throw mxvk::Exception(
"Failed to create binary matrix background sprite");
179 lastFrame = Clock::now();
183 digitZeroSprite =
nullptr;
184 digitOneSprite =
nullptr;
185 backgroundSprite =
nullptr;
191 if (e.type == SDL_EVENT_KEY_DOWN) {
192 if (e.key.key == SDLK_ESCAPE) {
194 }
else if (e.key.key == SDLK_SPACE) {
197 }
else if (e.type == SDL_EVENT_MOUSE_MOTION) {
200 }
else if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
204 }
else if (e.type == SDL_EVENT_MOUSE_BUTTON_UP) {
205 mousePressed =
false;
217 if (digitZeroSprite !=
nullptr) {
218 digitZeroSprite->resize(
this);
220 if (digitOneSprite !=
nullptr) {
221 digitOneSprite->resize(
this);
226 if (digitZeroSprite ==
nullptr || digitOneSprite ==
nullptr) {
229 const auto now = Clock::now();
230 float dt = std::chrono::duration<float>(now - lastFrame).count();
232 dt = std::clamp(dt, 0.0f, 1.0f / 15.0f);
235 const float aspect = (extent.height > 0U) ?
static_cast<float>(extent.width) /
static_cast<float>(extent.height) : 1.0f;
237 const float yaw = glm::radians(cameraYaw);
238 const float pitch = glm::radians(cameraPitch);
239 glm::mat4 cameraRotation(1.0f);
240 cameraRotation = glm::rotate(cameraRotation, yaw, glm::vec3(0.0f, 1.0f, 0.0f));
241 cameraRotation = glm::rotate(cameraRotation, pitch, glm::vec3(1.0f, 0.0f, 0.0f));
243 const glm::vec3 cameraPosition = glm::vec3(cameraRotation * glm::vec4(0.0f, 0.0f, cameraDistance, 1.0f));
244 const glm::vec3 cameraUp = glm::normalize(glm::vec3(cameraRotation * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f)));
245 glm::mat4 view = glm::lookAt(cameraPosition, glm::vec3(0.0f), cameraUp);
246 glm::mat4 proj = glm::perspective(glm::radians(50.0f), aspect, 0.1f, 100.0f);
249 updateBinaryRain(dt);
250 backgroundTime += dt;
254 backgroundSprite->clearQueue();
256 digitZeroSprite->updateCamera(imageIndex, view, proj);
257 digitOneSprite->updateCamera(imageIndex, view, proj);
259 for (
int column = 0; column < columns; ++column) {
260 const Stream &stream = streams[column];
261 drawStream(column, stream);
264 digitZeroSprite->render(cmd, imageIndex);
265 digitZeroSprite->clearQueue();
266 digitOneSprite->render(cmd, imageIndex);
267 digitOneSprite->clearQueue();
272 SurfacePtr zero_surface = renderGlyph(font.get(),
"0", digitColor);
273 SurfacePtr one_surface = renderGlyph(font.get(),
"1", digitColor);
275 if (zero_surface ==
nullptr || one_surface ==
nullptr) {
279 digitZeroSprite = createSprite3D(zero_surface.get());
280 digitOneSprite = createSprite3D(one_surface.get());
282 if (digitZeroSprite ==
nullptr || digitOneSprite ==
nullptr) {
286 digitZeroSprite->setDepthTestEnabled(
true);
287 digitZeroSprite->setDepthWriteEnabled(
false);
288 digitZeroSprite->setAlphaDiscardThreshold(0.05f);
290 digitOneSprite->setDepthTestEnabled(
true);
291 digitOneSprite->setDepthWriteEnabled(
false);
292 digitOneSprite->setAlphaDiscardThreshold(0.05f);
295 void rebuildForExtent() {
296 const VkExtent2D extent = getSwapchainExtent();
297 const int width =
static_cast<int>(extent.width);
298 const int height =
static_cast<int>(extent.height);
299 if (width <= 0 || height <= 0 || (extentWidth == width && extentHeight == height)) {
304 extentHeight = height;
306 const int horizontal_spacing = std::max(1,
static_cast<int>(std::round(
static_cast<float>(glyph_size) * 0.6f)));
307 const int vertical_spacing = std::max(1, glyph_size);
308 columns = std::max(1, width / horizontal_spacing);
309 rows = std::max(1, height / vertical_spacing);
311 const float aspect =
static_cast<float>(width) /
static_cast<float>(std::max(1, height));
312 horizontalSpan = 2.15f * aspect;
314 columnStep = (horizontalSpan * 2.0f) /
static_cast<float>(columns);
315 rowStep = (verticalSpan * 2.0f) /
static_cast<float>(rows);
316 baseGlyphSize = std::min(columnStep, rowStep) * 0.82f;
318 streams.assign(
static_cast<std::size_t
>(columns), {});
322 void randomizeStreams() {
323 std::uniform_real_distribution<float> headDist(-
static_cast<float>(rows) * 1.2f, 0.0f);
324 std::uniform_real_distribution<float> speedDist(4.0f, 12.0f);
325 std::uniform_int_distribution<int> lengthDist(34, std::max(48, rows + rows / 2));
326 std::uniform_int_distribution<int> seedDist(0, 4095);
327 std::uniform_real_distribution<float> phaseDist(0.0f, 6.28318530717958647692f);
328 std::uniform_real_distribution<float> amplitudeDist(0.15f, 0.60f);
329 std::uniform_real_distribution<float> biasDist(-0.22f, 0.28f);
331 for (Stream &stream : streams) {
332 stream.head = headDist(rng);
333 stream.speed = speedDist(rng);
334 stream.length = std::min(lengthDist(rng), rows + rows / 2);
335 stream.bitSeed = seedDist(rng);
336 stream.phase = phaseDist(rng);
337 stream.depthPhase = phaseDist(rng);
338 stream.depthAmplitude = amplitudeDist(rng);
339 stream.depthBias = biasDist(rng);
343 void updateBinaryRain(
float dt) {
344 lastScrollPhase += dt * 0.85f;
346 for (Stream &stream : streams) {
347 stream.head += stream.speed * dt;
348 if (stream.head -
static_cast<float>(stream.length) >
static_cast<float>(rows) + 2.0f) {
353 if (std::fmod(lastScrollPhase, 0.22f) < dt * 0.85f) {
354 for (Stream &stream : streams) {
355 stream.bitSeed ^= (frameCounter & 3);
362 void resetStream(Stream &stream) {
363 std::uniform_real_distribution<float> headDist(-
static_cast<float>(rows) * 0.9f, -1.0f);
364 std::uniform_real_distribution<float> speedDist(4.0f, 12.0f);
365 std::uniform_int_distribution<int> lengthDist(34, std::max(48, rows + rows / 2));
366 std::uniform_int_distribution<int> seedDist(0, 4095);
367 std::uniform_real_distribution<float> phaseDist(0.0f, 6.28318530717958647692f);
368 std::uniform_real_distribution<float> amplitudeDist(0.15f, 0.60f);
369 std::uniform_real_distribution<float> biasDist(-0.22f, 0.28f);
371 stream.head = headDist(rng);
372 stream.speed = speedDist(rng);
373 stream.length = std::min(lengthDist(rng), rows + rows / 2);
374 stream.bitSeed = seedDist(rng);
375 stream.phase = phaseDist(rng);
376 stream.depthPhase = phaseDist(rng);
377 stream.depthAmplitude = amplitudeDist(rng);
378 stream.depthBias = biasDist(rng);
381 void drawStream(
int column,
const Stream &stream) {
382 const float x = -horizontalSpan + (
static_cast<float>(column) + 0.5f) * columnStep;
384 const int headRow =
static_cast<int>(std::floor(stream.head));
385 for (
int tail = stream.length; tail >= 0; --tail) {
386 const int row = headRow - tail;
387 if (row < -1 || row >= rows) {
391 const float age =
static_cast<float>(tail) /
static_cast<float>(std::max(1, stream.length));
392 const float rowCenter = verticalSpan - (
static_cast<float>(row) + 0.5f) * rowStep;
393 const float z = 0.0f;
394 const float size = baseGlyphSize;
395 int level = trailLevels -
static_cast<int>(std::round(age *
static_cast<float>(trailLevels)));
398 }
else if (tail <= 2) {
399 level = trailLevels - 1;
401 level = std::clamp(level, 0, trailLevels);
403 const glm::vec4 tint(
static_cast<float>(tintColor.r) / 255.0f,
static_cast<float>(tintColor.g) / 255.0f,
static_cast<float>(tintColor.b) / 255.0f,
static_cast<float>(tintColor.a) / 255.0f);
405 const int cell =
static_cast<int>(std::floor(stream.head)) - tail;
406 const bool drawOne = ((stream.bitSeed + column * 13 + cell * 17 + tail * 3 + frameCounter / 3) & 1) != 0;
407 mxvk::VK_Sprite3D *sprite = drawOne ? digitOneSprite : digitZeroSprite;
408 sprite->
drawSprite(glm::vec3(x, rowCenter, z), glm::vec2(size, size), tint, 0.0f);
412 void drawBackground() {
413 if (backgroundSprite ==
nullptr) {
417 const VkExtent2D extent = getSwapchainExtent();
418 if (extent.width == 0U || extent.height == 0U) {
422 backgroundSprite->setShaderParams(backgroundTime,
static_cast<float>(mouseX),
static_cast<float>(mouseY), mousePressed ? 1.0f : 0.0f);
423 backgroundSprite->drawSpriteRect(0, 0,
static_cast<int>(extent.width),
static_cast<int>(extent.height));
426 void updateCameraInput() {
427 const bool *keyboard = SDL_GetKeyboardState(
nullptr);
428 if (keyboard ==
nullptr) {
432 const auto now = Clock::now();
433 float dt = std::chrono::duration<float>(now - lastCameraInput).count();
434 lastCameraInput = now;
435 dt = std::clamp(dt, 0.0f, 1.0f / 15.0f);
437 const float orbitDelta = cameraOrbitSpeed * dt;
438 const float zoomDelta = cameraZoomSpeed * dt;
440 if (keyboard[SDL_SCANCODE_LEFT]) {
441 cameraYaw -= orbitDelta;
443 if (keyboard[SDL_SCANCODE_RIGHT]) {
444 cameraYaw += orbitDelta;
446 if (keyboard[SDL_SCANCODE_UP]) {
447 cameraPitch += orbitDelta;
449 if (keyboard[SDL_SCANCODE_DOWN]) {
450 cameraPitch -= orbitDelta;
452 if (keyboard[SDL_SCANCODE_PAGEUP]) {
453 cameraDistance = std::max(1.5f, cameraDistance - zoomDelta);
455 if (keyboard[SDL_SCANCODE_PAGEDOWN]) {
456 cameraDistance = std::min(12.0f, cameraDistance + zoomDelta);
459 cameraYaw = std::fmod(cameraYaw, 360.0f);
460 if (cameraYaw < 0.0f) {
464 cameraPitch = std::fmod(cameraPitch, 360.0f);
465 if (cameraPitch < 0.0f) {
466 cameraPitch += 360.0f;
470 static constexpr int trailLevels = 7;
472 std::string assetRoot;
475 mxvk::VK_Sprite *backgroundSprite =
nullptr;
476 mxvk::VK_Sprite3D *digitZeroSprite =
nullptr;
477 mxvk::VK_Sprite3D *digitOneSprite =
nullptr;
479 Clock::time_point lastFrame{Clock::now()};
480 std::vector<Stream> streams;
484 int extentHeight = 0;
485 float horizontalSpan = 0.0f;
486 float verticalSpan = 0.0f;
487 float columnStep = 0.0f;
488 float rowStep = 0.0f;
489 float baseGlyphSize = 0.0f;
490 float lastScrollPhase = 0.0f;
491 float backgroundTime = 0.0f;
494 bool mousePressed =
false;
495 int frameCounter = 0;
496 float cameraYaw = 0.0f;
497 float cameraPitch = 0.0f;
498 float cameraDistance = 4.4f;
499 Clock::time_point lastCameraInput{Clock::now()};
500 static constexpr float cameraOrbitSpeed = 140.0f;
501 static constexpr float cameraZoomSpeed = 2.2f;
502 SDL_Color digitColor{255, 255, 255, 255};