Refactor ASCII rasterizer to support terminal ANSI mode and improve menu text rendering

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-18 16:06:20 +01:00
parent 839fae700f
commit be03bd45c8
8 changed files with 204 additions and 88 deletions

View File

@@ -61,6 +61,21 @@ class ColoredChar {
// Outputs standard AARRGGBB for Flutter's Color(int) constructor
int get argb => (0xFF000000) | (r << 16) | (g << 8) | b;
// Same AABBGGRR → AARRGGBB conversion for the background channel.
int? get backgroundArgb {
final bg = rawBackgroundColor;
if (bg == null) return null;
final int bgR = bg & 0xFF;
final int bgG = (bg >> 8) & 0xFF;
final int bgB = (bg >> 16) & 0xFF;
return 0xFF000000 | (bgR << 16) | (bgG << 8) | bgB;
}
}
enum AsciiRasterizerMode {
terminalAnsi,
terminalGrid,
}
class AsciiRasterizer extends CliRasterizer<dynamic> {
@@ -71,21 +86,23 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
static const int _simpleHudMinWidth = 84;
static const int _simpleHudMinRows = 7;
static const int _menuSelectedTextPaletteIndex = 19;
static const int _menuUnselectedTextPaletteIndex = 23;
static const int _menuHintKeyPaletteIndex = 12;
static const int _menuHintLabelPaletteIndex = 4;
static const int _menuHintBackgroundPaletteIndex = 0;
AsciiRasterizer({
this.activeTheme = AsciiThemes.blocks,
this.isTerminal = false,
this.mode = AsciiRasterizerMode.terminalGrid,
this.aspectMultiplier = 1.0,
this.verticalStretch = 1.0,
});
AsciiTheme activeTheme = AsciiThemes.blocks;
final bool isTerminal;
final AsciiRasterizerMode mode;
bool get _usesTerminalLayout => true;
bool get _emitAnsi => mode == AsciiRasterizerMode.terminalAnsi;
late List<List<ColoredChar>> _screen;
late List<List<int>> _scenePixels;
@@ -97,7 +114,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
final double verticalStretch;
@override
int get projectionWidth => isTerminal
int get projectionWidth => _usesTerminalLayout
? math.max(
1,
math.min(width, (_terminalPixelHeight * _targetAspectRatio).floor()),
@@ -105,14 +122,16 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
: width;
@override
int get projectionOffsetX => isTerminal ? (width - projectionWidth) ~/ 2 : 0;
int get projectionOffsetX =>
_usesTerminalLayout ? (width - projectionWidth) ~/ 2 : 0;
@override
int get projectionViewHeight => isTerminal ? viewHeight * 2 : viewHeight;
int get projectionViewHeight =>
_usesTerminalLayout ? viewHeight * 2 : viewHeight;
@override
bool isTerminalSizeSupported(int columns, int rows) {
if (!isTerminal) {
if (!_usesTerminalLayout) {
return true;
}
return columns >= _minimumTerminalColumns && rows >= _minimumTerminalRows;
@@ -123,7 +142,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
'ASCII renderer requires a minimum resolution of '
'${_minimumTerminalColumns}x$_minimumTerminalRows.';
int get _terminalPixelHeight => isTerminal ? height * 2 : height;
int get _terminalPixelHeight => _usesTerminalLayout ? height * 2 : height;
int get _viewportRightX => projectionOffsetX + projectionWidth;
@@ -149,7 +168,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
// Just grab the raw ints!
final int ceilingColor = ColorPalette.vga32Bit[25];
final int floorColor = ColorPalette.vga32Bit[29];
final int backdropColor = isTerminal
final int backdropColor = _usesTerminalLayout
? _terminalBackdropColor
: ColorPalette.vga32Bit[0];
@@ -167,7 +186,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
}
}
if (!isTerminal) {
if (!_usesTerminalLayout) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (y < viewHeight / 2) {
@@ -206,7 +225,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
pixelColor = shadeColor(pixelColor);
}
if (isTerminal) {
if (_usesTerminalLayout) {
_scenePixels[y][x] = _scaleColor(pixelColor, brightness);
} else {
final String wallChar = activeTheme.getByBrightness(brightness);
@@ -250,7 +269,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
int shadedColor = (0xFF000000) | (b << 16) | (g << 8) | r;
if (isTerminal) {
if (_usesTerminalLayout) {
_scenePixels[y][stripeX] = shadedColor;
} else {
_screen[y][stripeX] = ColoredChar(activeTheme.solid, shadedColor);
@@ -274,7 +293,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
int startY =
projectionViewHeight -
weaponHeight +
(engine.player.weaponAnimOffset * (isTerminal ? 2 : 1) ~/ 4);
(engine.player.weaponAnimOffset * (_usesTerminalLayout ? 2 : 1) ~/ 4);
for (int dy = 0; dy < weaponHeight; dy++) {
for (int dx = 0; dx < weaponWidth; dx++) {
@@ -288,9 +307,9 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
if (sceneX >= projectionOffsetX &&
sceneX < _viewportRightX &&
drawY >= 0) {
if (isTerminal && drawY < projectionViewHeight) {
if (_usesTerminalLayout && drawY < projectionViewHeight) {
_scenePixels[drawY][sceneX] = ColorPalette.vga32Bit[colorByte];
} else if (!isTerminal && drawY < viewHeight) {
} else if (!_usesTerminalLayout && drawY < viewHeight) {
_screen[drawY][sceneX] = ColoredChar(
activeTheme.solid,
ColorPalette.vga32Bit[colorByte],
@@ -324,7 +343,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
void drawHud(WolfEngine engine) {
// If the terminal is at least 160 columns wide and 50 rows tall,
// there are enough "pixels" to downscale the VGA image clearly.
int hudWidth = isTerminal ? projectionWidth : width;
int hudWidth = _usesTerminalLayout ? projectionWidth : width;
if (hudWidth >= 160 && height >= 50) {
_drawFullVgaHud(engine);
} else {
@@ -338,14 +357,11 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
engine.menuManager.selectedDifficultyIndex;
final int bgColor = _rgbToPaletteColor(engine.menuBackgroundRgb);
final int panelColor = _rgbToPaletteColor(engine.menuPanelRgb);
// Exact title tint requested for menu heading text (#fff700).
final int headingColor = _rgbToRawColor(0xFFF700);
final int selectedTextColor =
ColorPalette.vga32Bit[_menuSelectedTextPaletteIndex];
final int unselectedTextColor =
ColorPalette.vga32Bit[_menuUnselectedTextPaletteIndex];
final int headingColor = WolfMenuPalette.headerTextColor;
final int selectedTextColor = WolfMenuPalette.selectedTextColor;
final int unselectedTextColor = WolfMenuPalette.unselectedTextColor;
if (isTerminal) {
if (_usesTerminalLayout) {
_fillTerminalRect(0, 0, width, _terminalPixelHeight, bgColor);
} else {
_fillRect(0, 0, width, height, activeTheme.solid, bgColor);
@@ -464,7 +480,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
}
int get _fullMenuHeadingScale {
if (!isTerminal) {
if (!_usesTerminalLayout) {
return 2;
}
return projectionWidth < 140 ? 1 : 2;
@@ -473,7 +489,8 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
bool get _useMinimalMenuText => _menuGlyphHeightInRows(scale: 1) <= 4;
int _menuGlyphHeightInRows({required int scale}) {
final double scaleY = (isTerminal ? _terminalPixelHeight : height) / 200.0;
final double scaleY =
(_usesTerminalLayout ? _terminalPixelHeight : height) / 200.0;
// Use pre-compose pixel scale so terminal mode can graduate back to
// bitmap menu text at practical window sizes.
final double rasterHeight = 7.0 * scale * scaleY;
@@ -481,15 +498,17 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
}
int _menuY200ToRow(int y200) {
final double scaleY = (isTerminal ? _terminalPixelHeight : height) / 200.0;
final double scaleY =
(_usesTerminalLayout ? _terminalPixelHeight : height) / 200.0;
final int pixelY = (y200 * scaleY).toInt();
final int row = isTerminal ? (pixelY / 2).round() : pixelY;
final int row = _usesTerminalLayout ? (pixelY / 2).round() : pixelY;
return row.clamp(0, height - 1);
}
int _menuX320ToColumn(int x320) {
final double scaleX = (isTerminal ? projectionWidth : width) / 320.0;
final int offsetX = isTerminal ? projectionOffsetX : 0;
final double scaleX =
(_usesTerminalLayout ? projectionWidth : width) / 320.0;
final int offsetX = _usesTerminalLayout ? projectionOffsetX : 0;
final int pixelX = offsetX + (x320 * scaleX).toInt();
return pixelX.clamp(0, width - 1);
}
@@ -517,10 +536,8 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
const int rowStep = 15;
for (int i = 0; i < Difficulty.values.length; i++) {
final bool isSelected = i == selectedDifficultyIndex;
final int baseRowY = _menuY200ToRow(rowYStart + (i * rowStep));
final int rowY = i == Difficulty.values.length - 1
? (baseRowY + 1).clamp(0, height - 1)
: baseRowY;
final int rowY = _menuY200ToRow(rowYStart + (i * rowStep));
final String rowText = Difficulty.values[i].title;
_writeLeftClipped(
rowY,
@@ -534,6 +551,11 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
}
void _drawCenteredMenuFooter() {
if (_usesTerminalLayout && !_emitAnsi) {
_drawFlutterGridMenuFooter();
return;
}
final int hintKeyColor = ColorPalette.vga32Bit[_menuHintKeyPaletteIndex];
final int hintLabelColor =
ColorPalette.vga32Bit[_menuHintLabelPaletteIndex];
@@ -558,7 +580,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
final int boxX = ((width - boxWidth) ~/ 2).clamp(0, width - boxWidth);
final int boxY = math.max(0, height - boxHeight - 1);
if (isTerminal) {
if (_usesTerminalLayout) {
_fillTerminalRect(
boxX,
boxY * 2,
@@ -587,6 +609,41 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
}
}
void _drawFlutterGridMenuFooter() {
final int hintKeyColor = ColorPalette.vga32Bit[_menuHintKeyPaletteIndex];
final int hintLabelColor =
ColorPalette.vga32Bit[_menuHintLabelPaletteIndex];
final int hintBackground =
ColorPalette.vga32Bit[_menuHintBackgroundPaletteIndex];
final List<(String text, int color)> segments = <(String, int)>[
('UP/DN', hintKeyColor),
(' MOVE ', hintLabelColor),
('RET', hintKeyColor),
(' SELECT ', hintLabelColor),
('ESC', hintKeyColor),
(' BACK', hintLabelColor),
];
int textWidth = 0;
for (final (text, _) in segments) {
textWidth += WolfMenuFont.measureTextWidth(text, 1);
}
final int panelWidth = (textWidth + 12).clamp(1, 320);
final int panelHeight = 12;
final int panelX = ((320 - panelWidth) ~/ 2).clamp(0, 319);
const int panelY = 184;
_fillRect320(panelX, panelY, panelWidth, panelHeight, hintBackground);
int cursorX = panelX + 6;
const int textY = panelY + 2;
for (final (text, color) in segments) {
_drawMenuText(text, cursorX, textY, color, scale: 1);
cursorX += WolfMenuFont.measureTextWidth(text, 1);
}
}
void _writeLeftClipped(
int y,
String text,
@@ -613,15 +670,17 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
}
void _plotMenuPixel320(int x320, int y200, int color) {
final double scaleX = (isTerminal ? projectionWidth : width) / 320.0;
final double scaleY = (isTerminal ? _terminalPixelHeight : height) / 200.0;
final double scaleX =
(_usesTerminalLayout ? projectionWidth : width) / 320.0;
final double scaleY =
(_usesTerminalLayout ? _terminalPixelHeight : height) / 200.0;
final int offsetX = isTerminal ? projectionOffsetX : 0;
final int offsetX = _usesTerminalLayout ? projectionOffsetX : 0;
final int startX = offsetX + (x320 * scaleX).toInt();
final int startY = (y200 * scaleY).toInt();
final int pixelW = math.max(1, scaleX.ceil());
final int pixelH = math.max(1, scaleY.ceil());
if (isTerminal) {
if (_usesTerminalLayout) {
for (int dy = 0; dy < pixelH; dy++) {
final int y = startY + dy;
if (y < 0 || y >= _terminalPixelHeight) {
@@ -654,7 +713,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
}
void _drawSimpleHud(WolfEngine engine) {
final int hudWidth = isTerminal ? projectionWidth : width;
final int hudWidth = _usesTerminalLayout ? projectionWidth : width;
final int hudRows = height - viewHeight;
if (hudWidth < _simpleHudMinWidth || hudRows < _simpleHudMinRows) {
_drawMinimalHud(engine);
@@ -693,7 +752,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
final int baseY = viewHeight + 1;
// 3. Clear HUD Base
if (isTerminal) {
if (_usesTerminalLayout) {
_fillTerminalRect(
projectionOffsetX,
viewHeight * 2,
@@ -722,7 +781,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
// 4. Panel Drawing Helper
void drawBorderedPanel(int startX, int startY, int w, int h) {
if (isTerminal) {
if (_usesTerminalLayout) {
_fillTerminalRect(startX, startY * 2, w, h * 2, vgaPanelDark);
_fillTerminalRect(startX, startY * 2, w, 1, white);
_fillTerminalRect(startX, (startY + h) * 2 - 1, w, 1, white);
@@ -821,7 +880,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
final int red = ColorPalette.vga32Bit[4];
final int hudRows = height - viewHeight;
if (isTerminal) {
if (_usesTerminalLayout) {
_fillTerminalRect(
projectionOffsetX,
viewHeight * 2,
@@ -853,8 +912,8 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
final int lineY = viewHeight + 1;
if (lineY >= height) return;
final int drawStartX = isTerminal ? projectionOffsetX : 0;
final int drawWidth = isTerminal ? projectionWidth : width;
final int drawStartX = _usesTerminalLayout ? projectionOffsetX : 0;
final int drawWidth = _usesTerminalLayout ? projectionWidth : width;
final int maxTextLen = math.max(0, drawWidth - 2);
String clipped = hudText;
if (clipped.length > maxTextLen) {
@@ -965,15 +1024,15 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
@override
dynamic finalizeFrame() {
if (_engine.difficulty != null && _engine.player.damageFlash > 0.0) {
if (isTerminal) {
if (_usesTerminalLayout) {
_applyDamageFlashToScene();
} else {
_applyDamageFlash();
}
}
if (isTerminal) {
if (_usesTerminalLayout) {
_composeTerminalScene();
return toAnsiString();
return _emitAnsi ? toAnsiString() : _screen;
}
return _screen;
}
@@ -983,14 +1042,16 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
void _blitVgaImageAscii(VgaImage image, int startX_320, int startY_200) {
int planeWidth = image.width ~/ 4;
int planeSize = planeWidth * image.height;
int maxDrawHeight = isTerminal ? _terminalPixelHeight : height;
int maxDrawWidth = isTerminal ? _viewportRightX : width;
int maxDrawHeight = _usesTerminalLayout ? _terminalPixelHeight : height;
int maxDrawWidth = _usesTerminalLayout ? _viewportRightX : width;
double scaleX = (isTerminal ? projectionWidth : width) / 320.0;
double scaleY = (isTerminal ? _terminalPixelHeight : height) / 200.0;
double scaleX = (_usesTerminalLayout ? projectionWidth : width) / 320.0;
double scaleY =
(_usesTerminalLayout ? _terminalPixelHeight : height) / 200.0;
int destStartX =
(isTerminal ? projectionOffsetX : 0) + (startX_320 * scaleX).toInt();
(_usesTerminalLayout ? projectionOffsetX : 0) +
(startX_320 * scaleX).toInt();
int destStartY = (startY_200 * scaleY).toInt();
int destWidth = (image.width * scaleX).toInt();
int destHeight = (image.height * scaleY).toInt();
@@ -1013,7 +1074,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
int colorByte = image.pixels[index];
if (colorByte != 255) {
if (isTerminal) {
if (_usesTerminalLayout) {
_scenePixels[drawY][drawX] = ColorPalette.vga32Bit[colorByte];
} else {
_screen[drawY][drawX] = ColoredChar(
@@ -1046,16 +1107,19 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
int h200,
int color,
) {
final double scaleX = (isTerminal ? projectionWidth : width) / 320.0;
final double scaleY = (isTerminal ? _terminalPixelHeight : height) / 200.0;
final double scaleX =
(_usesTerminalLayout ? projectionWidth : width) / 320.0;
final double scaleY =
(_usesTerminalLayout ? _terminalPixelHeight : height) / 200.0;
final int startX =
(isTerminal ? projectionOffsetX : 0) + (startX320 * scaleX).toInt();
(_usesTerminalLayout ? projectionOffsetX : 0) +
(startX320 * scaleX).toInt();
final int startY = (startY200 * scaleY).toInt();
final int w = math.max(1, (w320 * scaleX).toInt());
final int h = math.max(1, (h200 * scaleY).toInt());
if (isTerminal) {
if (_usesTerminalLayout) {
_fillTerminalRect(startX, startY, w, h, color);
} else {
_fillRect(startX, startY, w, h, activeTheme.solid, color);
@@ -1119,7 +1183,11 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
ColoredChar overlay = _screen[y][x];
if (overlay.char != ' ') {
if (overlay.rawBackgroundColor == null) {
// In ANSI terminal mode, inject the bottom scene pixel as background
// so half-block rows pack two pixel rows into one cell. In Flutter
// grid mode any injected background renders as a colored rectangle
// behind the character, so we leave the cell as-is.
if (_emitAnsi && overlay.rawBackgroundColor == null) {
_screen[y][x] = ColoredChar(
overlay.char,
overlay.rawColor,
@@ -1129,6 +1197,9 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
continue;
}
// Pack two scene rows into one cell for both terminal and Flutter grid
// modes. Overlay characters above keep a null background in Flutter
// mode, so this does not introduce text background artifacts.
_screen[y][x] = topColor == bottomColor
? ColoredChar('', topColor)
: ColoredChar('', topColor, bottomColor);
@@ -1185,13 +1256,6 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
return ColorPalette.vga32Bit[_rgbToPaletteIndex(rgb)];
}
int _rgbToRawColor(int rgb) {
final int r = (rgb >> 16) & 0xFF;
final int g = (rgb >> 8) & 0xFF;
final int b = rgb & 0xFF;
return (0xFF000000) | (b << 16) | (g << 8) | r;
}
int _rgbToPaletteIndex(int rgb) {
final int targetR = (rgb >> 16) & 0xFF;
final int targetG = (rgb >> 8) & 0xFF;

View File

@@ -1,6 +1,8 @@
class WolfMenuFont {
const WolfMenuFont._();
static const int _letterSpacing = 2;
static const Map<String, List<String>> glyphs = {
'A': ['01110', '10001', '10001', '11111', '10001', '10001', '10001'],
'B': ['11110', '10001', '10001', '11110', '10001', '10001', '11110'],
@@ -62,11 +64,11 @@ class WolfMenuFont {
case ',':
case "'":
case ':':
return 4 * scale;
return (4 + _letterSpacing) * scale;
case ' ':
return 5 * scale;
default:
return 6 * scale;
return (6 + _letterSpacing) * scale;
}
}

View File

@@ -336,9 +336,9 @@ class SixelRasterizer extends CliRasterizer<String> {
engine.menuManager.selectedDifficultyIndex;
final int bgColor = _rgbToPaletteIndex(engine.menuBackgroundRgb);
final int panelColor = _rgbToPaletteIndex(engine.menuPanelRgb);
const int headingIndex = 119;
const int selectedTextIndex = 19;
const int unselectedTextIndex = 23;
final int headingIndex = WolfMenuPalette.headerTextIndex;
final int selectedTextIndex = WolfMenuPalette.selectedTextIndex;
final int unselectedTextIndex = WolfMenuPalette.unselectedTextIndex;
for (int i = 0; i < _screen.length; i++) {
_screen[i] = bgColor;
@@ -419,7 +419,9 @@ class SixelRasterizer extends CliRasterizer<String> {
prefix + Difficulty.values[i].title,
42,
rowYStart + (i * rowStep),
isSelected ? 19 : 23,
isSelected
? WolfMenuPalette.selectedTextIndex
: WolfMenuPalette.unselectedTextIndex,
scale: 1,
);
}

View File

@@ -182,9 +182,9 @@ class SoftwareRasterizer extends Rasterizer<FrameBuffer> {
engine.menuManager.selectedDifficultyIndex;
final int bgColor = _rgbToFrameColor(engine.menuBackgroundRgb);
final int panelColor = _rgbToFrameColor(engine.menuPanelRgb);
final int headingColor = ColorPalette.vga32Bit[119];
final int selectedTextColor = ColorPalette.vga32Bit[19];
final int unselectedTextColor = ColorPalette.vga32Bit[23];
final int headingColor = WolfMenuPalette.headerTextColor;
final int selectedTextColor = WolfMenuPalette.selectedTextColor;
final int unselectedTextColor = WolfMenuPalette.unselectedTextColor;
for (int i = 0; i < _buffer.pixels.length; i++) {
_buffer.pixels[i] = bgColor;

View File

@@ -43,6 +43,55 @@ abstract class WolfMenuPic {
];
}
/// Shared menu text colors resolved from the VGA palette.
///
/// Keep menu color choices centralized so renderers don't duplicate
/// hard-coded palette slots or RGB conversion logic.
abstract class WolfMenuPalette {
static const int selectedTextIndex = 19;
static const int unselectedTextIndex = 23;
static const int _headerTargetRgb = 0xFFF700;
static int? _cachedHeaderTextIndex;
static int get headerTextIndex =>
_cachedHeaderTextIndex ??= _nearestPaletteIndex(_headerTargetRgb);
static int get selectedTextColor => ColorPalette.vga32Bit[selectedTextIndex];
static int get unselectedTextColor =>
ColorPalette.vga32Bit[unselectedTextIndex];
static int get headerTextColor => ColorPalette.vga32Bit[headerTextIndex];
static int _nearestPaletteIndex(int rgb) {
final int targetR = (rgb >> 16) & 0xFF;
final int targetG = (rgb >> 8) & 0xFF;
final int targetB = rgb & 0xFF;
int bestIndex = 0;
int bestDistance = 1 << 30;
for (int i = 0; i < 256; i++) {
final int color = ColorPalette.vga32Bit[i];
final int r = color & 0xFF;
final int g = (color >> 8) & 0xFF;
final int b = (color >> 16) & 0xFF;
final int dr = targetR - r;
final int dg = targetG - g;
final int db = targetB - b;
final int distance = (dr * dr) + (dg * dg) + (db * db);
if (distance < bestDistance) {
bestDistance = distance;
bestIndex = i;
}
}
return bestIndex;
}
}
/// Structured accessors for classic Wolf3D menu art.
class WolfClassicMenuArt {
final WolfensteinData data;

View File

@@ -1,6 +1,7 @@
library;
export 'src/rasterizer/ascii_rasterizer.dart' show AsciiRasterizer, ColoredChar;
export 'src/rasterizer/ascii_rasterizer.dart'
show AsciiRasterizer, AsciiRasterizerMode, ColoredChar;
export 'src/rasterizer/cli_rasterizer.dart';
export 'src/rasterizer/rasterizer.dart';
export 'src/rasterizer/sixel_rasterizer.dart';