Double "resolution" in the CLI
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -41,7 +41,7 @@ void main() async {
|
||||
final input = CliInput();
|
||||
final cliAudio = CliSilentAudio();
|
||||
|
||||
final rasterizer = AsciiRasterizer();
|
||||
final rasterizer = AsciiRasterizer(isTerminal: true);
|
||||
|
||||
FrameBuffer buffer = FrameBuffer(
|
||||
stdout.terminalColumns,
|
||||
@@ -69,7 +69,6 @@ void main() async {
|
||||
|
||||
if (bytes.contains(9)) {
|
||||
rasterizer.activeTheme = AsciiThemes.nextOf(rasterizer.activeTheme);
|
||||
print("Switched to ${rasterizer.activeTheme.name} theme!");
|
||||
}
|
||||
|
||||
input.handleKey(bytes);
|
||||
|
||||
@@ -46,8 +46,9 @@ abstract class AsciiThemes {
|
||||
class ColoredChar {
|
||||
final String char;
|
||||
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
|
||||
int get r => rawColor & 0xFF;
|
||||
@@ -61,13 +62,16 @@ class ColoredChar {
|
||||
class AsciiRasterizer extends Rasterizer {
|
||||
AsciiRasterizer({
|
||||
this.activeTheme = AsciiThemes.blocks,
|
||||
this.isTerminal = false,
|
||||
this.aspectMultiplier = 1.0,
|
||||
this.verticalStretch = 1.0,
|
||||
});
|
||||
|
||||
AsciiTheme activeTheme = AsciiThemes.blocks;
|
||||
final bool isTerminal;
|
||||
|
||||
late List<List<ColoredChar>> _screen;
|
||||
late List<List<int>> _scenePixels;
|
||||
late WolfEngine _engine;
|
||||
|
||||
@override
|
||||
@@ -75,6 +79,11 @@ class AsciiRasterizer extends Rasterizer {
|
||||
@override
|
||||
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
|
||||
@override
|
||||
dynamic render(WolfEngine engine, FrameBuffer buffer) {
|
||||
@@ -92,7 +101,23 @@ class AsciiRasterizer extends Rasterizer {
|
||||
// Just grab the raw ints!
|
||||
final int ceilingColor = ColorPalette.vga32Bit[25];
|
||||
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 x = 0; x < width; x++) {
|
||||
if (y < viewHeight / 2) {
|
||||
@@ -103,6 +128,7 @@ class AsciiRasterizer extends Rasterizer {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void drawWallColumn(
|
||||
@@ -116,11 +142,10 @@ class AsciiRasterizer extends Rasterizer {
|
||||
int side,
|
||||
) {
|
||||
double brightness = calculateDepthBrightness(perpWallDist);
|
||||
String wallChar = activeTheme.getByBrightness(brightness);
|
||||
|
||||
for (int y = drawStart; y < drawEnd; y++) {
|
||||
double relativeY =
|
||||
(y - (-columnHeight ~/ 2 + viewHeight ~/ 2)) / columnHeight;
|
||||
(y - (-columnHeight ~/ 2 + projectionViewHeight ~/ 2)) / columnHeight;
|
||||
int texY = (relativeY * 64).toInt().clamp(0, 63);
|
||||
|
||||
int colorByte = texture.pixels[texX * 64 + texY];
|
||||
@@ -131,9 +156,14 @@ class AsciiRasterizer extends Rasterizer {
|
||||
pixelColor = shadeColor(pixelColor);
|
||||
}
|
||||
|
||||
if (isTerminal) {
|
||||
_scenePixels[y][x] = _scaleColor(pixelColor, brightness);
|
||||
} else {
|
||||
String wallChar = activeTheme.getByBrightness(brightness);
|
||||
_screen[y][x] = ColoredChar(wallChar, pixelColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void drawSpriteStripe(
|
||||
@@ -149,7 +179,7 @@ class AsciiRasterizer extends Rasterizer {
|
||||
|
||||
for (
|
||||
int y = math.max(0, drawStartY);
|
||||
y < math.min(viewHeight, drawEndY);
|
||||
y < math.min(projectionViewHeight, drawEndY);
|
||||
y++
|
||||
) {
|
||||
double relativeY = (y - drawStartY) / spriteHeight;
|
||||
@@ -170,11 +200,15 @@ class AsciiRasterizer extends Rasterizer {
|
||||
|
||||
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
|
||||
_screen[y][stripeX] = ColoredChar(activeTheme.solid, shadedColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void drawWeapon(WolfEngine engine) {
|
||||
@@ -184,11 +218,13 @@ class AsciiRasterizer extends Rasterizer {
|
||||
Sprite weaponSprite = engine.data.sprites[spriteIndex];
|
||||
|
||||
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 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 dx = 0; dx < weaponWidth; dx++) {
|
||||
@@ -197,10 +233,13 @@ class AsciiRasterizer extends Rasterizer {
|
||||
|
||||
int colorByte = weaponSprite.pixels[texX * 64 + texY];
|
||||
if (colorByte != 255) {
|
||||
int drawX = startX + dx;
|
||||
int sceneX = startX + dx;
|
||||
int drawY = startY + dy;
|
||||
if (drawX >= 0 && drawX < width && drawY >= 0 && drawY < viewHeight) {
|
||||
_screen[drawY][drawX] = ColoredChar(
|
||||
if (sceneX >= 0 && sceneX < width && drawY >= 0) {
|
||||
if (isTerminal && drawY < projectionViewHeight) {
|
||||
_scenePixels[drawY][sceneX] = ColorPalette.vga32Bit[colorByte];
|
||||
} else if (!isTerminal && drawY < viewHeight) {
|
||||
_screen[drawY][sceneX] = ColoredChar(
|
||||
activeTheme.solid,
|
||||
ColorPalette.vga32Bit[colorByte],
|
||||
);
|
||||
@@ -209,15 +248,22 @@ class AsciiRasterizer extends Rasterizer {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- PRIVATE HUD DRAWING HELPER ---
|
||||
|
||||
/// 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++) {
|
||||
int x = startX + i;
|
||||
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);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 4. Panel Drawing Helper
|
||||
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);
|
||||
// Horizontal lines
|
||||
_writeString(startX, startY, "┌${"─" * (w - 2)}┐", white);
|
||||
@@ -262,32 +333,35 @@ class AsciiRasterizer extends Rasterizer {
|
||||
_writeString(startX + w - 1, startY + i, "│", white);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Draw the Panels
|
||||
// FLOOR
|
||||
drawBorderedPanel(offsetX + 4, viewHeight + 2, 12, 5);
|
||||
_writeString(offsetX + 7, viewHeight + 3, "FLOOR", white);
|
||||
_writeString(offsetX + 7, viewHeight + 3, "FLOOR", white, vgaPanelDark);
|
||||
_writeString(
|
||||
offsetX + 9,
|
||||
viewHeight + 5,
|
||||
engine.activeLevel.name.split(' ').last,
|
||||
white,
|
||||
vgaPanelDark,
|
||||
);
|
||||
|
||||
// SCORE
|
||||
drawBorderedPanel(offsetX + 18, viewHeight + 2, 24, 5);
|
||||
_writeString(offsetX + 27, viewHeight + 3, "SCORE", white);
|
||||
_writeString(offsetX + 27, viewHeight + 3, "SCORE", white, vgaPanelDark);
|
||||
_writeString(
|
||||
offsetX + 27,
|
||||
viewHeight + 5,
|
||||
engine.player.score.toString().padLeft(6, '0'),
|
||||
white,
|
||||
vgaPanelDark,
|
||||
);
|
||||
|
||||
// LIVES
|
||||
drawBorderedPanel(offsetX + 44, viewHeight + 2, 12, 5);
|
||||
_writeString(offsetX + 47, viewHeight + 3, "LIVES", white);
|
||||
_writeString(offsetX + 49, viewHeight + 5, "3", white);
|
||||
_writeString(offsetX + 47, viewHeight + 3, "LIVES", white, vgaPanelDark);
|
||||
_writeString(offsetX + 49, viewHeight + 5, "3", white, vgaPanelDark);
|
||||
|
||||
// FACE (With Reactive BJ Logic)
|
||||
drawBorderedPanel(offsetX + 58, viewHeight + 1, 14, 7);
|
||||
@@ -301,30 +375,37 @@ class AsciiRasterizer extends Rasterizer {
|
||||
} else if (engine.player.health <= 60) {
|
||||
face = "ಠ~ಠ";
|
||||
}
|
||||
_writeString(offsetX + 63, viewHeight + 4, face, yellow);
|
||||
_writeString(offsetX + 63, viewHeight + 4, face, yellow, vgaPanelDark);
|
||||
|
||||
// HEALTH
|
||||
int healthColor = engine.player.health > 25 ? white : red;
|
||||
drawBorderedPanel(offsetX + 74, viewHeight + 2, 16, 5);
|
||||
_writeString(offsetX + 78, viewHeight + 3, "HEALTH", white);
|
||||
_writeString(offsetX + 78, viewHeight + 3, "HEALTH", white, vgaPanelDark);
|
||||
_writeString(
|
||||
offsetX + 79,
|
||||
viewHeight + 5,
|
||||
"${engine.player.health}%",
|
||||
healthColor,
|
||||
vgaPanelDark,
|
||||
);
|
||||
|
||||
// AMMO
|
||||
drawBorderedPanel(offsetX + 92, viewHeight + 2, 12, 5);
|
||||
_writeString(offsetX + 95, viewHeight + 3, "AMMO", white);
|
||||
_writeString(offsetX + 97, viewHeight + 5, "${engine.player.ammo}", white);
|
||||
_writeString(offsetX + 95, viewHeight + 3, "AMMO", white, vgaPanelDark);
|
||||
_writeString(
|
||||
offsetX + 97,
|
||||
viewHeight + 5,
|
||||
"${engine.player.ammo}",
|
||||
white,
|
||||
vgaPanelDark,
|
||||
);
|
||||
|
||||
// WEAPON
|
||||
drawBorderedPanel(offsetX + 106, viewHeight + 2, 14, 5);
|
||||
String weapon = engine.player.currentWeapon.type.name.spacePascalCase!
|
||||
.toUpperCase();
|
||||
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) {
|
||||
@@ -427,8 +508,15 @@ class AsciiRasterizer extends Rasterizer {
|
||||
@override
|
||||
dynamic finalizeFrame() {
|
||||
if (_engine.player.damageFlash > 0.0) {
|
||||
if (isTerminal) {
|
||||
_applyDamageFlashToScene();
|
||||
} else {
|
||||
_applyDamageFlash();
|
||||
}
|
||||
}
|
||||
if (isTerminal) {
|
||||
_composeTerminalScene();
|
||||
}
|
||||
return _screen;
|
||||
}
|
||||
|
||||
@@ -437,9 +525,10 @@ class AsciiRasterizer extends Rasterizer {
|
||||
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;
|
||||
|
||||
double scaleX = width / 320.0;
|
||||
double scaleY = height / 200.0;
|
||||
double scaleY = (isTerminal ? _terminalPixelHeight : height) / 200.0;
|
||||
|
||||
int destStartX = (startX_320 * scaleX).toInt();
|
||||
int destStartY = (startY_200 * scaleY).toInt();
|
||||
@@ -451,7 +540,10 @@ class AsciiRasterizer extends Rasterizer {
|
||||
int drawX = destStartX + dx;
|
||||
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 srcY = (dy / scaleY).toInt().clamp(0, image.height - 1);
|
||||
|
||||
@@ -461,6 +553,9 @@ class AsciiRasterizer extends Rasterizer {
|
||||
|
||||
int colorByte = image.pixels[index];
|
||||
if (colorByte != 255) {
|
||||
if (isTerminal) {
|
||||
_scenePixels[drawY][drawX] = ColorPalette.vga32Bit[colorByte];
|
||||
} else {
|
||||
_screen[drawY][drawX] = ColoredChar(
|
||||
activeTheme.solid,
|
||||
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 ---
|
||||
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;
|
||||
int redBoost = (150 * intensity).toInt();
|
||||
double colorDrop = 1.0 - (0.5 * intensity);
|
||||
|
||||
for (int y = 0; y < viewHeight; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
ColoredChar cell = _screen[y][x];
|
||||
|
||||
// Use our safe getters!
|
||||
int r = cell.r;
|
||||
int g = cell.g;
|
||||
int b = cell.b;
|
||||
int r = color & 0xFF;
|
||||
int g = (color >> 8) & 0xFF;
|
||||
int b = (color >> 16) & 0xFF;
|
||||
|
||||
r = (r + redBoost).clamp(0, 255);
|
||||
g = (g * colorDrop).toInt().clamp(0, 255);
|
||||
b = (b * colorDrop).toInt().clamp(0, 255);
|
||||
|
||||
// Pack back into the native AABBGGRR format that ColoredChar expects
|
||||
int newRawColor = (0xFF000000) | (b << 16) | (g << 8) | r;
|
||||
return (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() {
|
||||
StringBuffer buffer = StringBuffer();
|
||||
|
||||
int lastR = -1;
|
||||
int lastG = -1;
|
||||
int lastB = -1;
|
||||
int? lastForeground;
|
||||
int? lastBackground;
|
||||
|
||||
for (int y = 0; y < _screen.length; y++) {
|
||||
List<ColoredChar> row = _screen[y];
|
||||
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');
|
||||
lastR = cell.r;
|
||||
lastG = cell.g;
|
||||
lastB = cell.b;
|
||||
lastForeground = cell.rawColor;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,11 @@ abstract class Rasterizer {
|
||||
/// Defaults to 1.0 (no squish) for standard pixel rendering.
|
||||
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.
|
||||
/// Orchestrates the mathematical rendering pipeline.
|
||||
dynamic render(WolfEngine engine, FrameBuffer buffer) {
|
||||
@@ -103,6 +108,7 @@ abstract class Rasterizer {
|
||||
final Player player = engine.player;
|
||||
final SpriteMap map = engine.currentLevel;
|
||||
final List<Sprite> wallTextures = engine.data.walls;
|
||||
final int sceneHeight = projectionViewHeight;
|
||||
|
||||
final Map<String, double> doorOffsets = engine.doorManager
|
||||
.getOffsetsForRenderer();
|
||||
@@ -283,13 +289,16 @@ abstract class Rasterizer {
|
||||
if (side == 1 && math.sin(player.angle) < 0) texX = 63 - texX;
|
||||
|
||||
// Calculate drawing dimensions
|
||||
int columnHeight = ((viewHeight / perpWallDist) * verticalStretch)
|
||||
int columnHeight = ((sceneHeight / perpWallDist) * verticalStretch)
|
||||
.toInt();
|
||||
int drawStart = (-columnHeight ~/ 2 + viewHeight ~/ 2).clamp(
|
||||
int drawStart = (-columnHeight ~/ 2 + sceneHeight ~/ 2).clamp(
|
||||
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
|
||||
drawWallColumn(
|
||||
@@ -308,6 +317,7 @@ abstract class Rasterizer {
|
||||
void _castSprites(WolfEngine engine) {
|
||||
final Player player = engine.player;
|
||||
final List<Entity> activeSprites = List.from(engine.entities);
|
||||
final int sceneHeight = projectionViewHeight;
|
||||
|
||||
// Sort from furthest to closest (Painter's Algorithm)
|
||||
activeSprites.sort((a, b) {
|
||||
@@ -335,20 +345,23 @@ abstract class Rasterizer {
|
||||
if (transformY > 0) {
|
||||
int spriteScreenX = ((width / 2) * (1 + transformX / transformY))
|
||||
.toInt();
|
||||
int spriteHeight = ((viewHeight / transformY).abs() * verticalStretch)
|
||||
int spriteHeight = ((sceneHeight / transformY).abs() * verticalStretch)
|
||||
.toInt();
|
||||
int displayedSpriteHeight =
|
||||
((viewHeight / transformY).abs() * verticalStretch).toInt();
|
||||
|
||||
// Scale width based on the aspectMultiplier (useful for ASCII)
|
||||
int spriteWidth = (spriteHeight * aspectMultiplier / verticalStretch)
|
||||
int spriteWidth =
|
||||
(displayedSpriteHeight * aspectMultiplier / verticalStretch)
|
||||
.toInt();
|
||||
|
||||
int drawStartY = -spriteHeight ~/ 2 + viewHeight ~/ 2;
|
||||
int drawEndY = spriteHeight ~/ 2 + viewHeight ~/ 2;
|
||||
int drawStartY = -spriteHeight ~/ 2 + sceneHeight ~/ 2;
|
||||
int drawEndY = spriteHeight ~/ 2 + sceneHeight ~/ 2;
|
||||
int drawStartX = -spriteWidth ~/ 2 + spriteScreenX;
|
||||
int drawEndX = spriteWidth ~/ 2 + spriteScreenX;
|
||||
|
||||
int clipStartX = math.max(0, drawStartX);
|
||||
int clipEndX = math.min(width - 1, drawEndX);
|
||||
int clipEndX = math.min(width, drawEndX);
|
||||
|
||||
int safeIndex = entity.spriteIndex.clamp(
|
||||
0,
|
||||
|
||||
Reference in New Issue
Block a user