146 const std::string &title,
149 const bool fullscreen,
150 const bool enable_vsync,
151 const int requested_glyph_size,
152 const std::string &color)
154 assetRoot(path.empty() ? std::string(binary_matrix_ASSET_DIR) : path),
155 glyph_size(requested_glyph_size),
156 rng(std::random_device{}()) {
157 if (assetRoot ==
".") {
158 assetRoot = binary_matrix_ASSET_DIR;
161 if (!color.empty()) {
162 const std::optional<SDL_Color> parsedColor = parse_color_spec(color);
164 throw mxvk::Exception(
"Invalid binary_matrix color: " + color +
" (expected #RRGGBB or R,G,B)");
166 digitColor = *parsedColor;
172 throw mxvk::Exception(
"Failed to initialize SDL_ttf: " + std::string(SDL_GetError()));
175 font.reset(TTF_OpenFont((assetRoot +
"/data/NotoSansCJK-Bold.ttc").c_str(), glyph_size));
177 throw mxvk::Exception(
"Failed to load binary matrix font: " + std::string(SDL_GetError()));
179 TTF_SetFontHinting(font.get(), TTF_HINTING_LIGHT);
181 const std::string spriteVertPath = assetRoot +
"/data/sprite.vert.spv";
182 const std::string backgroundFragPath = assetRoot +
"/data/background.frag.spv";
183 backgroundSprite =
createSprite(assetRoot +
"/data/bg.png", spriteVertPath, backgroundFragPath);
184 if (backgroundSprite ==
nullptr) {
185 throw mxvk::Exception(
"Failed to create binary matrix background sprite");
190 lastFrame = Clock::now();
194 digitZeroSprite =
nullptr;
195 digitOneSprite =
nullptr;
196 backgroundSprite =
nullptr;
202 if (e.type == SDL_EVENT_KEY_DOWN) {
203 if (e.key.key == SDLK_ESCAPE) {
205 }
else if (e.key.key == SDLK_SPACE) {
208 }
else if (e.type == SDL_EVENT_MOUSE_MOTION) {
211 }
else if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
215 }
else if (e.type == SDL_EVENT_MOUSE_BUTTON_UP) {
216 mousePressed =
false;
228 if (digitZeroSprite !=
nullptr) {
229 digitZeroSprite->resize(
this);
231 if (digitOneSprite !=
nullptr) {
232 digitOneSprite->resize(
this);
237 if (digitZeroSprite ==
nullptr || digitOneSprite ==
nullptr) {
240 const auto now = Clock::now();
241 float dt = std::chrono::duration<float>(now - lastFrame).count();
243 dt = std::clamp(dt, 0.0f, 1.0f / 15.0f);
246 const float aspect = (extent.height > 0U)
247 ?
static_cast<float>(extent.width) /
static_cast<float>(extent.height)
250 const float yaw = glm::radians(cameraYaw);
251 const float pitch = glm::radians(cameraPitch);
252 glm::mat4 cameraRotation(1.0f);
253 cameraRotation = glm::rotate(cameraRotation, yaw, glm::vec3(0.0f, 1.0f, 0.0f));
254 cameraRotation = glm::rotate(cameraRotation, pitch, glm::vec3(1.0f, 0.0f, 0.0f));
256 const glm::vec3 cameraPosition = glm::vec3(cameraRotation * glm::vec4(0.0f, 0.0f, cameraDistance, 1.0f));
257 const glm::vec3 cameraUp = glm::normalize(glm::vec3(cameraRotation * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f)));
258 glm::mat4 view = glm::lookAt(cameraPosition, glm::vec3(0.0f), cameraUp);
259 glm::mat4 proj = glm::perspective(glm::radians(50.0f), aspect, 0.1f, 100.0f);
262 updateBinaryRain(dt);
263 backgroundTime += dt;
267 backgroundSprite->clearQueue();
269 digitZeroSprite->updateCamera(imageIndex, view, proj);
270 digitOneSprite->updateCamera(imageIndex, view, proj);
272 for (
int column = 0; column < columns; ++column) {
273 const Stream &stream = streams[column];
274 drawStream(column, stream);
277 digitZeroSprite->render(cmd, imageIndex);
278 digitZeroSprite->clearQueue();
279 digitOneSprite->render(cmd, imageIndex);
280 digitOneSprite->clearQueue();
285 SurfacePtr zero_surface = renderGlyph(font.get(),
"0", digitColor);
286 SurfacePtr one_surface = renderGlyph(font.get(),
"1", digitColor);
288 if (zero_surface ==
nullptr || one_surface ==
nullptr) {
292 digitZeroSprite = createSprite3D(zero_surface.get());
293 digitOneSprite = createSprite3D(one_surface.get());
295 if (digitZeroSprite ==
nullptr || digitOneSprite ==
nullptr) {
299 digitZeroSprite->setDepthTestEnabled(
true);
300 digitZeroSprite->setDepthWriteEnabled(
false);
301 digitZeroSprite->setAlphaDiscardThreshold(0.05f);
303 digitOneSprite->setDepthTestEnabled(
true);
304 digitOneSprite->setDepthWriteEnabled(
false);
305 digitOneSprite->setAlphaDiscardThreshold(0.05f);
308 void rebuildForExtent() {
309 const VkExtent2D extent = getSwapchainExtent();
310 const int width =
static_cast<int>(extent.width);
311 const int height =
static_cast<int>(extent.height);
312 if (width <= 0 || height <= 0 || (extentWidth == width && extentHeight == height)) {
317 extentHeight = height;
319 const int horizontal_spacing = std::max(1,
static_cast<int>(std::round(
static_cast<float>(glyph_size) * 0.6f)));
320 const int vertical_spacing = std::max(1, glyph_size);
321 columns = std::max(1, width / horizontal_spacing);
322 rows = std::max(1, height / vertical_spacing);
324 const float aspect =
static_cast<float>(width) /
static_cast<float>(std::max(1, height));
325 horizontalSpan = 2.15f * aspect;
327 columnStep = (horizontalSpan * 2.0f) /
static_cast<float>(columns);
328 rowStep = (verticalSpan * 2.0f) /
static_cast<float>(rows);
329 baseGlyphSize = std::min(columnStep, rowStep) * 0.82f;
331 streams.assign(
static_cast<std::size_t
>(columns), {});
335 void randomizeStreams() {
336 std::uniform_real_distribution<float> headDist(-
static_cast<float>(rows) * 1.2f, 0.0f);
337 std::uniform_real_distribution<float> speedDist(4.0f, 12.0f);
338 std::uniform_int_distribution<int> lengthDist(34, std::max(48, rows + rows / 2));
339 std::uniform_int_distribution<int> seedDist(0, 4095);
340 std::uniform_real_distribution<float> phaseDist(0.0f, 6.28318530717958647692f);
341 std::uniform_real_distribution<float> amplitudeDist(0.15f, 0.60f);
342 std::uniform_real_distribution<float> biasDist(-0.22f, 0.28f);
344 for (Stream &stream : streams) {
345 stream.head = headDist(rng);
346 stream.speed = speedDist(rng);
347 stream.length = std::min(lengthDist(rng), rows + rows / 2);
348 stream.bitSeed = seedDist(rng);
349 stream.phase = phaseDist(rng);
350 stream.depthPhase = phaseDist(rng);
351 stream.depthAmplitude = amplitudeDist(rng);
352 stream.depthBias = biasDist(rng);
356 void updateBinaryRain(
float dt) {
357 lastScrollPhase += dt * 0.85f;
359 for (Stream &stream : streams) {
360 stream.head += stream.speed * dt;
361 if (stream.head -
static_cast<float>(stream.length) >
static_cast<float>(rows) + 2.0f) {
366 if (std::fmod(lastScrollPhase, 0.22f) < dt * 0.85f) {
367 for (Stream &stream : streams) {
368 stream.bitSeed ^= (frameCounter & 3);
375 void resetStream(Stream &stream) {
376 std::uniform_real_distribution<float> headDist(-
static_cast<float>(rows) * 0.9f, -1.0f);
377 std::uniform_real_distribution<float> speedDist(4.0f, 12.0f);
378 std::uniform_int_distribution<int> lengthDist(34, std::max(48, rows + rows / 2));
379 std::uniform_int_distribution<int> seedDist(0, 4095);
380 std::uniform_real_distribution<float> phaseDist(0.0f, 6.28318530717958647692f);
381 std::uniform_real_distribution<float> amplitudeDist(0.15f, 0.60f);
382 std::uniform_real_distribution<float> biasDist(-0.22f, 0.28f);
384 stream.head = headDist(rng);
385 stream.speed = speedDist(rng);
386 stream.length = std::min(lengthDist(rng), rows + rows / 2);
387 stream.bitSeed = seedDist(rng);
388 stream.phase = phaseDist(rng);
389 stream.depthPhase = phaseDist(rng);
390 stream.depthAmplitude = amplitudeDist(rng);
391 stream.depthBias = biasDist(rng);
394 void drawStream(
int column,
const Stream &stream) {
395 const float x = -horizontalSpan + (
static_cast<float>(column) + 0.5f) * columnStep;
397 const int headRow =
static_cast<int>(std::floor(stream.head));
398 for (
int tail = stream.length; tail >= 0; --tail) {
399 const int row = headRow - tail;
400 if (row < -1 || row >= rows) {
404 const float age =
static_cast<float>(tail) /
static_cast<float>(std::max(1, stream.length));
405 const float rowCenter = verticalSpan - (
static_cast<float>(row) + 0.5f) * rowStep;
406 const float z = 0.0f;
407 const float size = baseGlyphSize;
408 int level = trailLevels -
static_cast<int>(std::round(age *
static_cast<float>(trailLevels)));
411 }
else if (tail <= 2) {
412 level = trailLevels - 1;
414 level = std::clamp(level, 0, trailLevels);
416 const glm::vec4 tint(
static_cast<float>(tintColor.r) / 255.0f,
417 static_cast<float>(tintColor.g) / 255.0f,
418 static_cast<float>(tintColor.b) / 255.0f,
419 static_cast<float>(tintColor.a) / 255.0f);
421 const int cell =
static_cast<int>(std::floor(stream.head)) - tail;
422 const bool drawOne = ((stream.bitSeed + column * 13 + cell * 17 + tail * 3 + frameCounter / 3) & 1) != 0;
423 mxvk::VK_Sprite3D *sprite = drawOne ? digitOneSprite : digitZeroSprite;
424 sprite->
drawSprite(glm::vec3(x, rowCenter, z), glm::vec2(size, size), tint, 0.0f);
428 void drawBackground() {
429 if (backgroundSprite ==
nullptr) {
433 const VkExtent2D extent = getSwapchainExtent();
434 if (extent.width == 0U || extent.height == 0U) {
438 backgroundSprite->setShaderParams(backgroundTime,
439 static_cast<float>(mouseX),
440 static_cast<float>(mouseY),
441 mousePressed ? 1.0f : 0.0f);
442 backgroundSprite->drawSpriteRect(0, 0,
static_cast<int>(extent.width),
static_cast<int>(extent.height));
445 void updateCameraInput() {
446 const bool *keyboard = SDL_GetKeyboardState(
nullptr);
447 if (keyboard ==
nullptr) {
451 const auto now = Clock::now();
452 float dt = std::chrono::duration<float>(now - lastCameraInput).count();
453 lastCameraInput = now;
454 dt = std::clamp(dt, 0.0f, 1.0f / 15.0f);
456 const float orbitDelta = cameraOrbitSpeed * dt;
457 const float zoomDelta = cameraZoomSpeed * dt;
459 if (keyboard[SDL_SCANCODE_LEFT]) {
460 cameraYaw -= orbitDelta;
462 if (keyboard[SDL_SCANCODE_RIGHT]) {
463 cameraYaw += orbitDelta;
465 if (keyboard[SDL_SCANCODE_UP]) {
466 cameraPitch += orbitDelta;
468 if (keyboard[SDL_SCANCODE_DOWN]) {
469 cameraPitch -= orbitDelta;
471 if (keyboard[SDL_SCANCODE_PAGEUP]) {
472 cameraDistance = std::max(1.5f, cameraDistance - zoomDelta);
474 if (keyboard[SDL_SCANCODE_PAGEDOWN]) {
475 cameraDistance = std::min(12.0f, cameraDistance + zoomDelta);
478 cameraYaw = std::fmod(cameraYaw, 360.0f);
479 if (cameraYaw < 0.0f) {
483 cameraPitch = std::fmod(cameraPitch, 360.0f);
484 if (cameraPitch < 0.0f) {
485 cameraPitch += 360.0f;
489 static constexpr int trailLevels = 7;
491 std::string assetRoot;
494 mxvk::VK_Sprite *backgroundSprite =
nullptr;
495 mxvk::VK_Sprite3D *digitZeroSprite =
nullptr;
496 mxvk::VK_Sprite3D *digitOneSprite =
nullptr;
498 Clock::time_point lastFrame{Clock::now()};
499 std::vector<Stream> streams;
503 int extentHeight = 0;
504 float horizontalSpan = 0.0f;
505 float verticalSpan = 0.0f;
506 float columnStep = 0.0f;
507 float rowStep = 0.0f;
508 float baseGlyphSize = 0.0f;
509 float lastScrollPhase = 0.0f;
510 float backgroundTime = 0.0f;
513 bool mousePressed =
false;
514 int frameCounter = 0;
515 float cameraYaw = 0.0f;
516 float cameraPitch = 0.0f;
517 float cameraDistance = 4.4f;
518 Clock::time_point lastCameraInput{Clock::now()};
519 static constexpr float cameraOrbitSpeed = 140.0f;
520 static constexpr float cameraZoomSpeed = 2.2f;
521 SDL_Color digitColor{255, 255, 255, 255};