Double "resolution" in the CLI

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-17 23:41:05 +01:00
parent 72ed1ce968
commit 458c0a5d14
3 changed files with 260 additions and 81 deletions

View File

@@ -41,7 +41,7 @@ void main() async {
final input = CliInput(); final input = CliInput();
final cliAudio = CliSilentAudio(); final cliAudio = CliSilentAudio();
final rasterizer = AsciiRasterizer(); final rasterizer = AsciiRasterizer(isTerminal: true);
FrameBuffer buffer = FrameBuffer( FrameBuffer buffer = FrameBuffer(
stdout.terminalColumns, stdout.terminalColumns,
@@ -69,7 +69,6 @@ void main() async {
if (bytes.contains(9)) { if (bytes.contains(9)) {
rasterizer.activeTheme = AsciiThemes.nextOf(rasterizer.activeTheme); rasterizer.activeTheme = AsciiThemes.nextOf(rasterizer.activeTheme);
print("Switched to ${rasterizer.activeTheme.name} theme!");
} }
input.handleKey(bytes); input.handleKey(bytes);

View File

@@ -46,8 +46,9 @@ abstract class AsciiThemes {
class ColoredChar { class ColoredChar {
final String char; final String char;
final int rawColor; // Stores the AABBGGRR integer from the palette final int rawColor; // Stores the AABBGGRR integer from the palette
final int? rawBackgroundColor;
ColoredChar(this.char, this.rawColor); ColoredChar(this.char, this.rawColor, [this.rawBackgroundColor]);
// Safely extract the exact RGB channels regardless of framework // Safely extract the exact RGB channels regardless of framework
int get r => rawColor & 0xFF; int get r => rawColor & 0xFF;
@@ -61,13 +62,16 @@ class ColoredChar {
class AsciiRasterizer extends Rasterizer { class AsciiRasterizer extends Rasterizer {
AsciiRasterizer({ AsciiRasterizer({
this.activeTheme = AsciiThemes.blocks, this.activeTheme = AsciiThemes.blocks,
this.isTerminal = false,
this.aspectMultiplier = 1.0, this.aspectMultiplier = 1.0,
this.verticalStretch = 1.0, this.verticalStretch = 1.0,
}); });
AsciiTheme activeTheme = AsciiThemes.blocks; AsciiTheme activeTheme = AsciiThemes.blocks;
final bool isTerminal;
late List<List<ColoredChar>> _screen; late List<List<ColoredChar>> _screen;
late List<List<int>> _scenePixels;
late WolfEngine _engine; late WolfEngine _engine;
@override @override
@@ -75,6 +79,11 @@ class AsciiRasterizer extends Rasterizer {
@override @override
final double verticalStretch; final double verticalStretch;
@override
int get projectionViewHeight => isTerminal ? viewHeight * 2 : viewHeight;
int get _terminalPixelHeight => isTerminal ? height * 2 : height;
// Intercept the base render call to initialize our text grid // Intercept the base render call to initialize our text grid
@override @override
dynamic render(WolfEngine engine, FrameBuffer buffer) { dynamic render(WolfEngine engine, FrameBuffer buffer) {
@@ -92,7 +101,23 @@ class AsciiRasterizer extends Rasterizer {
// Just grab the raw ints! // Just grab the raw ints!
final int ceilingColor = ColorPalette.vga32Bit[25]; final int ceilingColor = ColorPalette.vga32Bit[25];
final int floorColor = ColorPalette.vga32Bit[29]; final int floorColor = ColorPalette.vga32Bit[29];
final int black = ColorPalette.vga32Bit[0];
_scenePixels = List.generate(
_terminalPixelHeight,
(_) => List.filled(width, black),
);
for (int y = 0; y < projectionViewHeight; y++) {
final int color = y < projectionViewHeight / 2
? ceilingColor
: floorColor;
for (int x = 0; x < width; x++) {
_scenePixels[y][x] = color;
}
}
if (!isTerminal) {
for (int y = 0; y < height; y++) { for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) { for (int x = 0; x < width; x++) {
if (y < viewHeight / 2) { if (y < viewHeight / 2) {
@@ -103,6 +128,7 @@ class AsciiRasterizer extends Rasterizer {
} }
} }
} }
}
@override @override
void drawWallColumn( void drawWallColumn(
@@ -116,11 +142,10 @@ class AsciiRasterizer extends Rasterizer {
int side, int side,
) { ) {
double brightness = calculateDepthBrightness(perpWallDist); double brightness = calculateDepthBrightness(perpWallDist);
String wallChar = activeTheme.getByBrightness(brightness);
for (int y = drawStart; y < drawEnd; y++) { for (int y = drawStart; y < drawEnd; y++) {
double relativeY = double relativeY =
(y - (-columnHeight ~/ 2 + viewHeight ~/ 2)) / columnHeight; (y - (-columnHeight ~/ 2 + projectionViewHeight ~/ 2)) / columnHeight;
int texY = (relativeY * 64).toInt().clamp(0, 63); int texY = (relativeY * 64).toInt().clamp(0, 63);
int colorByte = texture.pixels[texX * 64 + texY]; int colorByte = texture.pixels[texX * 64 + texY];
@@ -131,9 +156,14 @@ class AsciiRasterizer extends Rasterizer {
pixelColor = shadeColor(pixelColor); pixelColor = shadeColor(pixelColor);
} }
if (isTerminal) {
_scenePixels[y][x] = _scaleColor(pixelColor, brightness);
} else {
String wallChar = activeTheme.getByBrightness(brightness);
_screen[y][x] = ColoredChar(wallChar, pixelColor); _screen[y][x] = ColoredChar(wallChar, pixelColor);
} }
} }
}
@override @override
void drawSpriteStripe( void drawSpriteStripe(
@@ -149,7 +179,7 @@ class AsciiRasterizer extends Rasterizer {
for ( for (
int y = math.max(0, drawStartY); int y = math.max(0, drawStartY);
y < math.min(viewHeight, drawEndY); y < math.min(projectionViewHeight, drawEndY);
y++ y++
) { ) {
double relativeY = (y - drawStartY) / spriteHeight; double relativeY = (y - drawStartY) / spriteHeight;
@@ -170,11 +200,15 @@ class AsciiRasterizer extends Rasterizer {
int shadedColor = (0xFF000000) | (b << 16) | (g << 8) | r; int shadedColor = (0xFF000000) | (b << 16) | (g << 8) | r;
if (isTerminal) {
_scenePixels[y][stripeX] = shadedColor;
} else {
// Force sprites to be SOLID so they don't vanish into the terminal background // Force sprites to be SOLID so they don't vanish into the terminal background
_screen[y][stripeX] = ColoredChar(activeTheme.solid, shadedColor); _screen[y][stripeX] = ColoredChar(activeTheme.solid, shadedColor);
} }
} }
} }
}
@override @override
void drawWeapon(WolfEngine engine) { void drawWeapon(WolfEngine engine) {
@@ -184,11 +218,13 @@ class AsciiRasterizer extends Rasterizer {
Sprite weaponSprite = engine.data.sprites[spriteIndex]; Sprite weaponSprite = engine.data.sprites[spriteIndex];
int weaponWidth = (width * 0.5).toInt(); int weaponWidth = (width * 0.5).toInt();
int weaponHeight = (viewHeight * 0.8).toInt(); int weaponHeight = ((projectionViewHeight * 0.8)).toInt();
int startX = (width ~/ 2) - (weaponWidth ~/ 2); int startX = (width ~/ 2) - (weaponWidth ~/ 2);
int startY = int startY =
viewHeight - weaponHeight + (engine.player.weaponAnimOffset ~/ 4); projectionViewHeight -
weaponHeight +
(engine.player.weaponAnimOffset * (isTerminal ? 2 : 1) ~/ 4);
for (int dy = 0; dy < weaponHeight; dy++) { for (int dy = 0; dy < weaponHeight; dy++) {
for (int dx = 0; dx < weaponWidth; dx++) { for (int dx = 0; dx < weaponWidth; dx++) {
@@ -197,10 +233,13 @@ class AsciiRasterizer extends Rasterizer {
int colorByte = weaponSprite.pixels[texX * 64 + texY]; int colorByte = weaponSprite.pixels[texX * 64 + texY];
if (colorByte != 255) { if (colorByte != 255) {
int drawX = startX + dx; int sceneX = startX + dx;
int drawY = startY + dy; int drawY = startY + dy;
if (drawX >= 0 && drawX < width && drawY >= 0 && drawY < viewHeight) { if (sceneX >= 0 && sceneX < width && drawY >= 0) {
_screen[drawY][drawX] = ColoredChar( if (isTerminal && drawY < projectionViewHeight) {
_scenePixels[drawY][sceneX] = ColorPalette.vga32Bit[colorByte];
} else if (!isTerminal && drawY < viewHeight) {
_screen[drawY][sceneX] = ColoredChar(
activeTheme.solid, activeTheme.solid,
ColorPalette.vga32Bit[colorByte], ColorPalette.vga32Bit[colorByte],
); );
@@ -209,15 +248,22 @@ class AsciiRasterizer extends Rasterizer {
} }
} }
} }
}
// --- PRIVATE HUD DRAWING HELPER --- // --- PRIVATE HUD DRAWING HELPER ---
/// Injects a pure text string directly into the rasterizer grid /// Injects a pure text string directly into the rasterizer grid
void _writeString(int startX, int y, String text, int color) { void _writeString(
int startX,
int y,
String text,
int color, [
int? backgroundColor,
]) {
for (int i = 0; i < text.length; i++) { for (int i = 0; i < text.length; i++) {
int x = startX + i; int x = startX + i;
if (x >= 0 && x < width && y >= 0 && y < height) { if (x >= 0 && x < width && y >= 0 && y < height) {
_screen[y][x] = ColoredChar(text[i], color); _screen[y][x] = ColoredChar(text[i], color, backgroundColor);
} }
} }
} }
@@ -247,11 +293,36 @@ class AsciiRasterizer extends Rasterizer {
final int offsetX = ((width - hudContentWidth) ~/ 2).clamp(0, width); final int offsetX = ((width - hudContentWidth) ~/ 2).clamp(0, width);
// 3. Clear HUD Base // 3. Clear HUD Base
_fillRect(0, viewHeight, width, height - viewHeight, ' ', vgaStatusBarBlue); if (isTerminal) {
_fillTerminalRect(
0,
viewHeight * 2,
width,
(height - viewHeight) * 2,
vgaStatusBarBlue,
);
_fillTerminalRect(0, viewHeight * 2, width, 1, white);
} else {
_fillRect(
0,
viewHeight,
width,
height - viewHeight,
' ',
vgaStatusBarBlue,
);
_writeString(0, viewHeight, "" * width, white); _writeString(0, viewHeight, "" * width, white);
}
// 4. Panel Drawing Helper // 4. Panel Drawing Helper
void drawBorderedPanel(int startX, int startY, int w, int h) { void drawBorderedPanel(int startX, int startY, int w, int h) {
if (isTerminal) {
_fillTerminalRect(startX, startY * 2, w, h * 2, vgaPanelDark);
_fillTerminalRect(startX, startY * 2, w, 1, white);
_fillTerminalRect(startX, (startY + h) * 2 - 1, w, 1, white);
_fillTerminalRect(startX, startY * 2, 1, h * 2, white);
_fillTerminalRect(startX + w - 1, startY * 2, 1, h * 2, white);
} else {
_fillRect(startX, startY, w, h, ' ', vgaPanelDark); _fillRect(startX, startY, w, h, ' ', vgaPanelDark);
// Horizontal lines // Horizontal lines
_writeString(startX, startY, "${"" * (w - 2)}", white); _writeString(startX, startY, "${"" * (w - 2)}", white);
@@ -262,32 +333,35 @@ class AsciiRasterizer extends Rasterizer {
_writeString(startX + w - 1, startY + i, "", white); _writeString(startX + w - 1, startY + i, "", white);
} }
} }
}
// 5. Draw the Panels // 5. Draw the Panels
// FLOOR // FLOOR
drawBorderedPanel(offsetX + 4, viewHeight + 2, 12, 5); drawBorderedPanel(offsetX + 4, viewHeight + 2, 12, 5);
_writeString(offsetX + 7, viewHeight + 3, "FLOOR", white); _writeString(offsetX + 7, viewHeight + 3, "FLOOR", white, vgaPanelDark);
_writeString( _writeString(
offsetX + 9, offsetX + 9,
viewHeight + 5, viewHeight + 5,
engine.activeLevel.name.split(' ').last, engine.activeLevel.name.split(' ').last,
white, white,
vgaPanelDark,
); );
// SCORE // SCORE
drawBorderedPanel(offsetX + 18, viewHeight + 2, 24, 5); drawBorderedPanel(offsetX + 18, viewHeight + 2, 24, 5);
_writeString(offsetX + 27, viewHeight + 3, "SCORE", white); _writeString(offsetX + 27, viewHeight + 3, "SCORE", white, vgaPanelDark);
_writeString( _writeString(
offsetX + 27, offsetX + 27,
viewHeight + 5, viewHeight + 5,
engine.player.score.toString().padLeft(6, '0'), engine.player.score.toString().padLeft(6, '0'),
white, white,
vgaPanelDark,
); );
// LIVES // LIVES
drawBorderedPanel(offsetX + 44, viewHeight + 2, 12, 5); drawBorderedPanel(offsetX + 44, viewHeight + 2, 12, 5);
_writeString(offsetX + 47, viewHeight + 3, "LIVES", white); _writeString(offsetX + 47, viewHeight + 3, "LIVES", white, vgaPanelDark);
_writeString(offsetX + 49, viewHeight + 5, "3", white); _writeString(offsetX + 49, viewHeight + 5, "3", white, vgaPanelDark);
// FACE (With Reactive BJ Logic) // FACE (With Reactive BJ Logic)
drawBorderedPanel(offsetX + 58, viewHeight + 1, 14, 7); drawBorderedPanel(offsetX + 58, viewHeight + 1, 14, 7);
@@ -301,30 +375,37 @@ class AsciiRasterizer extends Rasterizer {
} else if (engine.player.health <= 60) { } else if (engine.player.health <= 60) {
face = "ಠ~ಠ"; face = "ಠ~ಠ";
} }
_writeString(offsetX + 63, viewHeight + 4, face, yellow); _writeString(offsetX + 63, viewHeight + 4, face, yellow, vgaPanelDark);
// HEALTH // HEALTH
int healthColor = engine.player.health > 25 ? white : red; int healthColor = engine.player.health > 25 ? white : red;
drawBorderedPanel(offsetX + 74, viewHeight + 2, 16, 5); drawBorderedPanel(offsetX + 74, viewHeight + 2, 16, 5);
_writeString(offsetX + 78, viewHeight + 3, "HEALTH", white); _writeString(offsetX + 78, viewHeight + 3, "HEALTH", white, vgaPanelDark);
_writeString( _writeString(
offsetX + 79, offsetX + 79,
viewHeight + 5, viewHeight + 5,
"${engine.player.health}%", "${engine.player.health}%",
healthColor, healthColor,
vgaPanelDark,
); );
// AMMO // AMMO
drawBorderedPanel(offsetX + 92, viewHeight + 2, 12, 5); drawBorderedPanel(offsetX + 92, viewHeight + 2, 12, 5);
_writeString(offsetX + 95, viewHeight + 3, "AMMO", white); _writeString(offsetX + 95, viewHeight + 3, "AMMO", white, vgaPanelDark);
_writeString(offsetX + 97, viewHeight + 5, "${engine.player.ammo}", white); _writeString(
offsetX + 97,
viewHeight + 5,
"${engine.player.ammo}",
white,
vgaPanelDark,
);
// WEAPON // WEAPON
drawBorderedPanel(offsetX + 106, viewHeight + 2, 14, 5); drawBorderedPanel(offsetX + 106, viewHeight + 2, 14, 5);
String weapon = engine.player.currentWeapon.type.name.spacePascalCase! String weapon = engine.player.currentWeapon.type.name.spacePascalCase!
.toUpperCase(); .toUpperCase();
if (weapon.length > 12) weapon = weapon.substring(0, 12); if (weapon.length > 12) weapon = weapon.substring(0, 12);
_writeString(offsetX + 107, viewHeight + 4, weapon, white); _writeString(offsetX + 107, viewHeight + 4, weapon, white, vgaPanelDark);
} }
void _drawFullVgaHud(WolfEngine engine) { void _drawFullVgaHud(WolfEngine engine) {
@@ -427,8 +508,15 @@ class AsciiRasterizer extends Rasterizer {
@override @override
dynamic finalizeFrame() { dynamic finalizeFrame() {
if (_engine.player.damageFlash > 0.0) { if (_engine.player.damageFlash > 0.0) {
if (isTerminal) {
_applyDamageFlashToScene();
} else {
_applyDamageFlash(); _applyDamageFlash();
} }
}
if (isTerminal) {
_composeTerminalScene();
}
return _screen; return _screen;
} }
@@ -437,9 +525,10 @@ class AsciiRasterizer extends Rasterizer {
void _blitVgaImageAscii(VgaImage image, int startX_320, int startY_200) { void _blitVgaImageAscii(VgaImage image, int startX_320, int startY_200) {
int planeWidth = image.width ~/ 4; int planeWidth = image.width ~/ 4;
int planeSize = planeWidth * image.height; int planeSize = planeWidth * image.height;
int maxDrawHeight = isTerminal ? _terminalPixelHeight : height;
double scaleX = width / 320.0; double scaleX = width / 320.0;
double scaleY = height / 200.0; double scaleY = (isTerminal ? _terminalPixelHeight : height) / 200.0;
int destStartX = (startX_320 * scaleX).toInt(); int destStartX = (startX_320 * scaleX).toInt();
int destStartY = (startY_200 * scaleY).toInt(); int destStartY = (startY_200 * scaleY).toInt();
@@ -451,7 +540,10 @@ class AsciiRasterizer extends Rasterizer {
int drawX = destStartX + dx; int drawX = destStartX + dx;
int drawY = destStartY + dy; int drawY = destStartY + dy;
if (drawX >= 0 && drawX < width && drawY >= 0 && drawY < height) { if (drawX >= 0 &&
drawX < width &&
drawY >= 0 &&
drawY < maxDrawHeight) {
int srcX = (dx / scaleX).toInt().clamp(0, image.width - 1); int srcX = (dx / scaleX).toInt().clamp(0, image.width - 1);
int srcY = (dy / scaleY).toInt().clamp(0, image.height - 1); int srcY = (dy / scaleY).toInt().clamp(0, image.height - 1);
@@ -461,6 +553,9 @@ class AsciiRasterizer extends Rasterizer {
int colorByte = image.pixels[index]; int colorByte = image.pixels[index];
if (colorByte != 255) { if (colorByte != 255) {
if (isTerminal) {
_scenePixels[drawY][drawX] = ColorPalette.vga32Bit[colorByte];
} else {
_screen[drawY][drawX] = ColoredChar( _screen[drawY][drawX] = ColoredChar(
activeTheme.solid, activeTheme.solid,
ColorPalette.vga32Bit[colorByte], ColorPalette.vga32Bit[colorByte],
@@ -470,30 +565,90 @@ class AsciiRasterizer extends Rasterizer {
} }
} }
} }
}
void _fillTerminalRect(int startX, int startY, int w, int h, int color) {
for (int dy = 0; dy < h; dy++) {
for (int dx = 0; dx < w; dx++) {
int x = startX + dx;
int y = startY + dy;
if (x >= 0 && x < width && y >= 0 && y < _terminalPixelHeight) {
_scenePixels[y][x] = color;
}
}
}
}
// --- DAMAGE FLASH --- // --- DAMAGE FLASH ---
void _applyDamageFlash() { void _applyDamageFlash() {
for (int y = 0; y < viewHeight; y++) {
for (int x = 0; x < width; x++) {
ColoredChar cell = _screen[y][x];
_screen[y][x] = ColoredChar(
cell.char,
_applyDamageFlashToColor(cell.rawColor),
cell.rawBackgroundColor == null
? null
: _applyDamageFlashToColor(cell.rawBackgroundColor!),
);
}
}
}
void _applyDamageFlashToScene() {
for (int y = 0; y < _terminalPixelHeight; y++) {
for (int x = 0; x < width; x++) {
_scenePixels[y][x] = _applyDamageFlashToColor(_scenePixels[y][x]);
}
}
}
int _applyDamageFlashToColor(int color) {
double intensity = _engine.player.damageFlash; double intensity = _engine.player.damageFlash;
int redBoost = (150 * intensity).toInt(); int redBoost = (150 * intensity).toInt();
double colorDrop = 1.0 - (0.5 * intensity); double colorDrop = 1.0 - (0.5 * intensity);
for (int y = 0; y < viewHeight; y++) { int r = color & 0xFF;
for (int x = 0; x < width; x++) { int g = (color >> 8) & 0xFF;
ColoredChar cell = _screen[y][x]; int b = (color >> 16) & 0xFF;
// Use our safe getters!
int r = cell.r;
int g = cell.g;
int b = cell.b;
r = (r + redBoost).clamp(0, 255); r = (r + redBoost).clamp(0, 255);
g = (g * colorDrop).toInt().clamp(0, 255); g = (g * colorDrop).toInt().clamp(0, 255);
b = (b * colorDrop).toInt().clamp(0, 255); b = (b * colorDrop).toInt().clamp(0, 255);
// Pack back into the native AABBGGRR format that ColoredChar expects return (0xFF000000) | (b << 16) | (g << 8) | r;
int newRawColor = (0xFF000000) | (b << 16) | (g << 8) | r; }
_screen[y][x] = ColoredChar(cell.char, newRawColor); int _scaleColor(int color, double brightness) {
int r = ((color & 0xFF) * brightness).toInt().clamp(0, 255);
int g = (((color >> 8) & 0xFF) * brightness).toInt().clamp(0, 255);
int b = (((color >> 16) & 0xFF) * brightness).toInt().clamp(0, 255);
return (0xFF000000) | (b << 16) | (g << 8) | r;
}
void _composeTerminalScene() {
for (int y = 0; y < height; y++) {
int topY = y * 2;
int bottomY = math.min(topY + 1, _terminalPixelHeight - 1);
for (int x = 0; x < width; x++) {
int topColor = _scenePixels[topY][x];
int bottomColor = _scenePixels[bottomY][x];
ColoredChar overlay = _screen[y][x];
if (overlay.char != ' ') {
if (overlay.rawBackgroundColor == null) {
_screen[y][x] = ColoredChar(
overlay.char,
overlay.rawColor,
bottomColor,
);
}
continue;
}
_screen[y][x] = topColor == bottomColor
? ColoredChar('', topColor)
: ColoredChar('', topColor, bottomColor);
} }
} }
} }
@@ -502,18 +657,30 @@ class AsciiRasterizer extends Rasterizer {
String toAnsiString() { String toAnsiString() {
StringBuffer buffer = StringBuffer(); StringBuffer buffer = StringBuffer();
int lastR = -1; int? lastForeground;
int lastG = -1; int? lastBackground;
int lastB = -1;
for (int y = 0; y < _screen.length; y++) { for (int y = 0; y < _screen.length; y++) {
List<ColoredChar> row = _screen[y]; List<ColoredChar> row = _screen[y];
for (ColoredChar cell in row) { for (ColoredChar cell in row) {
if (cell.r != lastR || cell.g != lastG || cell.b != lastB) { if (cell.rawColor != lastForeground) {
buffer.write('\x1b[38;2;${cell.r};${cell.g};${cell.b}m'); buffer.write('\x1b[38;2;${cell.r};${cell.g};${cell.b}m');
lastR = cell.r; lastForeground = cell.rawColor;
lastG = cell.g; }
lastB = cell.b; if (cell.rawBackgroundColor != lastBackground) {
if (cell.rawBackgroundColor == null) {
buffer.write('\x1b[49m');
} else {
int background = cell.rawBackgroundColor!;
int bgR = background & 0xFF;
int bgG = (background >> 8) & 0xFF;
int bgB = (background >> 16) & 0xFF;
buffer.write(
'\x1b[48;2;$bgR;$bgG;$bgB'
'm',
);
}
lastBackground = cell.rawBackgroundColor;
} }
buffer.write(cell.char); buffer.write(cell.char);
} }

View File

@@ -19,6 +19,11 @@ abstract class Rasterizer {
/// Defaults to 1.0 (no squish) for standard pixel rendering. /// Defaults to 1.0 (no squish) for standard pixel rendering.
double get verticalStretch => 1.0; double get verticalStretch => 1.0;
/// The logical height of the 3D projection before a renderer maps rows to output pixels.
/// Most renderers use the visible view height. Terminal ASCII can override this to render
/// more vertical detail and collapse it into half-block glyphs.
int get projectionViewHeight => viewHeight;
/// The main entry point called by the game loop. /// The main entry point called by the game loop.
/// Orchestrates the mathematical rendering pipeline. /// Orchestrates the mathematical rendering pipeline.
dynamic render(WolfEngine engine, FrameBuffer buffer) { dynamic render(WolfEngine engine, FrameBuffer buffer) {
@@ -103,6 +108,7 @@ abstract class Rasterizer {
final Player player = engine.player; final Player player = engine.player;
final SpriteMap map = engine.currentLevel; final SpriteMap map = engine.currentLevel;
final List<Sprite> wallTextures = engine.data.walls; final List<Sprite> wallTextures = engine.data.walls;
final int sceneHeight = projectionViewHeight;
final Map<String, double> doorOffsets = engine.doorManager final Map<String, double> doorOffsets = engine.doorManager
.getOffsetsForRenderer(); .getOffsetsForRenderer();
@@ -283,13 +289,16 @@ abstract class Rasterizer {
if (side == 1 && math.sin(player.angle) < 0) texX = 63 - texX; if (side == 1 && math.sin(player.angle) < 0) texX = 63 - texX;
// Calculate drawing dimensions // Calculate drawing dimensions
int columnHeight = ((viewHeight / perpWallDist) * verticalStretch) int columnHeight = ((sceneHeight / perpWallDist) * verticalStretch)
.toInt(); .toInt();
int drawStart = (-columnHeight ~/ 2 + viewHeight ~/ 2).clamp( int drawStart = (-columnHeight ~/ 2 + sceneHeight ~/ 2).clamp(
0, 0,
viewHeight, sceneHeight,
);
int drawEnd = (columnHeight ~/ 2 + sceneHeight ~/ 2).clamp(
0,
sceneHeight,
); );
int drawEnd = (columnHeight ~/ 2 + viewHeight ~/ 2).clamp(0, viewHeight);
// Tell the implementation to draw this column // Tell the implementation to draw this column
drawWallColumn( drawWallColumn(
@@ -308,6 +317,7 @@ abstract class Rasterizer {
void _castSprites(WolfEngine engine) { void _castSprites(WolfEngine engine) {
final Player player = engine.player; final Player player = engine.player;
final List<Entity> activeSprites = List.from(engine.entities); final List<Entity> activeSprites = List.from(engine.entities);
final int sceneHeight = projectionViewHeight;
// Sort from furthest to closest (Painter's Algorithm) // Sort from furthest to closest (Painter's Algorithm)
activeSprites.sort((a, b) { activeSprites.sort((a, b) {
@@ -335,20 +345,23 @@ abstract class Rasterizer {
if (transformY > 0) { if (transformY > 0) {
int spriteScreenX = ((width / 2) * (1 + transformX / transformY)) int spriteScreenX = ((width / 2) * (1 + transformX / transformY))
.toInt(); .toInt();
int spriteHeight = ((viewHeight / transformY).abs() * verticalStretch) int spriteHeight = ((sceneHeight / transformY).abs() * verticalStretch)
.toInt(); .toInt();
int displayedSpriteHeight =
((viewHeight / transformY).abs() * verticalStretch).toInt();
// Scale width based on the aspectMultiplier (useful for ASCII) // Scale width based on the aspectMultiplier (useful for ASCII)
int spriteWidth = (spriteHeight * aspectMultiplier / verticalStretch) int spriteWidth =
(displayedSpriteHeight * aspectMultiplier / verticalStretch)
.toInt(); .toInt();
int drawStartY = -spriteHeight ~/ 2 + viewHeight ~/ 2; int drawStartY = -spriteHeight ~/ 2 + sceneHeight ~/ 2;
int drawEndY = spriteHeight ~/ 2 + viewHeight ~/ 2; int drawEndY = spriteHeight ~/ 2 + sceneHeight ~/ 2;
int drawStartX = -spriteWidth ~/ 2 + spriteScreenX; int drawStartX = -spriteWidth ~/ 2 + spriteScreenX;
int drawEndX = spriteWidth ~/ 2 + spriteScreenX; int drawEndX = spriteWidth ~/ 2 + spriteScreenX;
int clipStartX = math.max(0, drawStartX); int clipStartX = math.max(0, drawStartX);
int clipEndX = math.min(width - 1, drawEndX); int clipEndX = math.min(width, drawEndX);
int safeIndex = entity.spriteIndex.clamp( int safeIndex = entity.spriteIndex.clamp(
0, 0,