@@ -60,6 +60,11 @@ class ColoredChar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class AsciiRasterizer extends Rasterizer {
|
class AsciiRasterizer extends Rasterizer {
|
||||||
|
static const double _targetAspectRatio = 4 / 3;
|
||||||
|
static const int _terminalBackdropArgb = 0xFF009688;
|
||||||
|
static const int _simpleHudMinWidth = 84;
|
||||||
|
static const int _simpleHudMinRows = 7;
|
||||||
|
|
||||||
AsciiRasterizer({
|
AsciiRasterizer({
|
||||||
this.activeTheme = AsciiThemes.blocks,
|
this.activeTheme = AsciiThemes.blocks,
|
||||||
this.isTerminal = false,
|
this.isTerminal = false,
|
||||||
@@ -79,11 +84,26 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
@override
|
@override
|
||||||
final double verticalStretch;
|
final double verticalStretch;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get projectionWidth => isTerminal
|
||||||
|
? math.max(
|
||||||
|
1,
|
||||||
|
math.min(width, (_terminalPixelHeight * _targetAspectRatio).floor()),
|
||||||
|
)
|
||||||
|
: width;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get projectionOffsetX => isTerminal ? (width - projectionWidth) ~/ 2 : 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get projectionViewHeight => isTerminal ? viewHeight * 2 : viewHeight;
|
int get projectionViewHeight => isTerminal ? viewHeight * 2 : viewHeight;
|
||||||
|
|
||||||
int get _terminalPixelHeight => isTerminal ? height * 2 : height;
|
int get _terminalPixelHeight => isTerminal ? height * 2 : height;
|
||||||
|
|
||||||
|
int get _viewportRightX => projectionOffsetX + projectionWidth;
|
||||||
|
|
||||||
|
int get _terminalBackdropColor => _argbToRawColor(_terminalBackdropArgb);
|
||||||
|
|
||||||
// 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) {
|
||||||
@@ -101,18 +121,20 @@ 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];
|
final int backdropColor = isTerminal
|
||||||
|
? _terminalBackdropColor
|
||||||
|
: ColorPalette.vga32Bit[0];
|
||||||
|
|
||||||
_scenePixels = List.generate(
|
_scenePixels = List.generate(
|
||||||
_terminalPixelHeight,
|
_terminalPixelHeight,
|
||||||
(_) => List.filled(width, black),
|
(_) => List.filled(width, backdropColor),
|
||||||
);
|
);
|
||||||
|
|
||||||
for (int y = 0; y < projectionViewHeight; y++) {
|
for (int y = 0; y < projectionViewHeight; y++) {
|
||||||
final int color = y < projectionViewHeight / 2
|
final int color = y < projectionViewHeight / 2
|
||||||
? ceilingColor
|
? ceilingColor
|
||||||
: floorColor;
|
: floorColor;
|
||||||
for (int x = 0; x < width; x++) {
|
for (int x = projectionOffsetX; x < _viewportRightX; x++) {
|
||||||
_scenePixels[y][x] = color;
|
_scenePixels[y][x] = color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -217,10 +239,11 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
);
|
);
|
||||||
Sprite weaponSprite = engine.data.sprites[spriteIndex];
|
Sprite weaponSprite = engine.data.sprites[spriteIndex];
|
||||||
|
|
||||||
int weaponWidth = (width * 0.5).toInt();
|
int weaponWidth = (projectionWidth * 0.5).toInt();
|
||||||
int weaponHeight = ((projectionViewHeight * 0.8)).toInt();
|
int weaponHeight = ((projectionViewHeight * 0.8)).toInt();
|
||||||
|
|
||||||
int startX = (width ~/ 2) - (weaponWidth ~/ 2);
|
int startX =
|
||||||
|
projectionOffsetX + (projectionWidth ~/ 2) - (weaponWidth ~/ 2);
|
||||||
int startY =
|
int startY =
|
||||||
projectionViewHeight -
|
projectionViewHeight -
|
||||||
weaponHeight +
|
weaponHeight +
|
||||||
@@ -235,7 +258,9 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
if (colorByte != 255) {
|
if (colorByte != 255) {
|
||||||
int sceneX = startX + dx;
|
int sceneX = startX + dx;
|
||||||
int drawY = startY + dy;
|
int drawY = startY + dy;
|
||||||
if (sceneX >= 0 && sceneX < width && drawY >= 0) {
|
if (sceneX >= projectionOffsetX &&
|
||||||
|
sceneX < _viewportRightX &&
|
||||||
|
drawY >= 0) {
|
||||||
if (isTerminal && drawY < projectionViewHeight) {
|
if (isTerminal && drawY < projectionViewHeight) {
|
||||||
_scenePixels[drawY][sceneX] = ColorPalette.vga32Bit[colorByte];
|
_scenePixels[drawY][sceneX] = ColorPalette.vga32Bit[colorByte];
|
||||||
} else if (!isTerminal && drawY < viewHeight) {
|
} else if (!isTerminal && drawY < viewHeight) {
|
||||||
@@ -272,7 +297,8 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
void drawHud(WolfEngine engine) {
|
void drawHud(WolfEngine engine) {
|
||||||
// If the terminal is at least 160 columns wide and 50 rows tall,
|
// If the terminal is at least 160 columns wide and 50 rows tall,
|
||||||
// there are enough "pixels" to downscale the VGA image clearly.
|
// there are enough "pixels" to downscale the VGA image clearly.
|
||||||
if (width >= 160 && height >= 50) {
|
int hudWidth = isTerminal ? projectionWidth : width;
|
||||||
|
if (hudWidth >= 160 && height >= 50) {
|
||||||
_drawFullVgaHud(engine);
|
_drawFullVgaHud(engine);
|
||||||
} else {
|
} else {
|
||||||
_drawSimpleHud(engine);
|
_drawSimpleHud(engine);
|
||||||
@@ -280,6 +306,13 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _drawSimpleHud(WolfEngine engine) {
|
void _drawSimpleHud(WolfEngine engine) {
|
||||||
|
final int hudWidth = isTerminal ? projectionWidth : width;
|
||||||
|
final int hudRows = height - viewHeight;
|
||||||
|
if (hudWidth < _simpleHudMinWidth || hudRows < _simpleHudMinRows) {
|
||||||
|
_drawMinimalHud(engine);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Pull Retro Colors
|
// 1. Pull Retro Colors
|
||||||
final int vgaStatusBarBlue = ColorPalette.vga32Bit[153];
|
final int vgaStatusBarBlue = ColorPalette.vga32Bit[153];
|
||||||
final int vgaPanelDark = ColorPalette.vga32Bit[0];
|
final int vgaPanelDark = ColorPalette.vga32Bit[0];
|
||||||
@@ -287,21 +320,46 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
final int yellow = ColorPalette.vga32Bit[11];
|
final int yellow = ColorPalette.vga32Bit[11];
|
||||||
final int red = ColorPalette.vga32Bit[4];
|
final int red = ColorPalette.vga32Bit[4];
|
||||||
|
|
||||||
// 2. Setup Centered Layout
|
// Compact full simple HUD layout.
|
||||||
// The total width of our standard HUD elements is roughly 120 chars
|
const int floorW = 10;
|
||||||
const int hudContentWidth = 120;
|
const int scoreW = 14;
|
||||||
final int offsetX = ((width - hudContentWidth) ~/ 2).clamp(0, width);
|
const int livesW = 9;
|
||||||
|
const int faceW = 10;
|
||||||
|
const int healthW = 12;
|
||||||
|
const int ammoW = 10;
|
||||||
|
const int weaponW = 13;
|
||||||
|
const int gap = 1;
|
||||||
|
const int hudContentWidth =
|
||||||
|
floorW +
|
||||||
|
scoreW +
|
||||||
|
livesW +
|
||||||
|
faceW +
|
||||||
|
healthW +
|
||||||
|
ammoW +
|
||||||
|
weaponW +
|
||||||
|
(gap * 6);
|
||||||
|
|
||||||
|
final int offsetX =
|
||||||
|
projectionOffsetX +
|
||||||
|
((projectionWidth - hudContentWidth) ~/ 2).clamp(0, projectionWidth);
|
||||||
|
final int baseY = viewHeight + 1;
|
||||||
|
|
||||||
// 3. Clear HUD Base
|
// 3. Clear HUD Base
|
||||||
if (isTerminal) {
|
if (isTerminal) {
|
||||||
_fillTerminalRect(
|
_fillTerminalRect(
|
||||||
0,
|
projectionOffsetX,
|
||||||
viewHeight * 2,
|
viewHeight * 2,
|
||||||
width,
|
projectionWidth,
|
||||||
(height - viewHeight) * 2,
|
hudRows * 2,
|
||||||
vgaStatusBarBlue,
|
vgaStatusBarBlue,
|
||||||
);
|
);
|
||||||
_fillTerminalRect(0, viewHeight * 2, width, 1, white);
|
_fillTerminalRect(
|
||||||
|
projectionOffsetX,
|
||||||
|
viewHeight * 2,
|
||||||
|
projectionWidth,
|
||||||
|
1,
|
||||||
|
white,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
_fillRect(
|
_fillRect(
|
||||||
0,
|
0,
|
||||||
@@ -335,36 +393,35 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Draw the Panels
|
// 5. Draw compact panels.
|
||||||
// FLOOR
|
int cursorX = offsetX;
|
||||||
drawBorderedPanel(offsetX + 4, viewHeight + 2, 12, 5);
|
|
||||||
_writeString(offsetX + 7, viewHeight + 3, "FLOOR", white, vgaPanelDark);
|
|
||||||
_writeString(
|
|
||||||
offsetX + 9,
|
|
||||||
viewHeight + 5,
|
|
||||||
engine.activeLevel.name.split(' ').last,
|
|
||||||
white,
|
|
||||||
vgaPanelDark,
|
|
||||||
);
|
|
||||||
|
|
||||||
// SCORE
|
drawBorderedPanel(cursorX, baseY + 1, floorW, 4);
|
||||||
drawBorderedPanel(offsetX + 18, viewHeight + 2, 24, 5);
|
_writeString(cursorX + 2, baseY + 2, "FLR", white, vgaPanelDark);
|
||||||
_writeString(offsetX + 27, viewHeight + 3, "SCORE", white, vgaPanelDark);
|
String floorLabel = engine.activeLevel.name.split(' ').last;
|
||||||
|
if (floorLabel.length > 4) {
|
||||||
|
floorLabel = floorLabel.substring(floorLabel.length - 4);
|
||||||
|
}
|
||||||
|
_writeString(cursorX + 2, baseY + 3, floorLabel, white, vgaPanelDark);
|
||||||
|
cursorX += floorW + gap;
|
||||||
|
|
||||||
|
drawBorderedPanel(cursorX, baseY + 1, scoreW, 4);
|
||||||
|
_writeString(cursorX + 4, baseY + 2, "SCORE", white, vgaPanelDark);
|
||||||
_writeString(
|
_writeString(
|
||||||
offsetX + 27,
|
cursorX + 4,
|
||||||
viewHeight + 5,
|
baseY + 3,
|
||||||
engine.player.score.toString().padLeft(6, '0'),
|
engine.player.score.toString().padLeft(6, '0'),
|
||||||
white,
|
white,
|
||||||
vgaPanelDark,
|
vgaPanelDark,
|
||||||
);
|
);
|
||||||
|
cursorX += scoreW + gap;
|
||||||
|
|
||||||
// LIVES
|
drawBorderedPanel(cursorX, baseY + 1, livesW, 4);
|
||||||
drawBorderedPanel(offsetX + 44, viewHeight + 2, 12, 5);
|
_writeString(cursorX + 2, baseY + 2, "LIV", white, vgaPanelDark);
|
||||||
_writeString(offsetX + 47, viewHeight + 3, "LIVES", white, vgaPanelDark);
|
_writeString(cursorX + 3, baseY + 3, "3", white, vgaPanelDark);
|
||||||
_writeString(offsetX + 49, viewHeight + 5, "3", white, vgaPanelDark);
|
cursorX += livesW + gap;
|
||||||
|
|
||||||
// FACE (With Reactive BJ Logic)
|
drawBorderedPanel(cursorX, baseY, faceW, 5);
|
||||||
drawBorderedPanel(offsetX + 58, viewHeight + 1, 14, 7);
|
|
||||||
String face = "ಠ⌣ಠ";
|
String face = "ಠ⌣ಠ";
|
||||||
if (engine.player.health <= 0) {
|
if (engine.player.health <= 0) {
|
||||||
face = "x⸑x";
|
face = "x⸑x";
|
||||||
@@ -375,37 +432,89 @@ 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, vgaPanelDark);
|
_writeString(cursorX + 3, baseY + 2, face, yellow, vgaPanelDark);
|
||||||
|
cursorX += faceW + gap;
|
||||||
|
|
||||||
// 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(cursorX, baseY + 1, healthW, 4);
|
||||||
_writeString(offsetX + 78, viewHeight + 3, "HEALTH", white, vgaPanelDark);
|
_writeString(cursorX + 2, baseY + 2, "HEALTH", white, vgaPanelDark);
|
||||||
_writeString(
|
_writeString(
|
||||||
offsetX + 79,
|
cursorX + 3,
|
||||||
viewHeight + 5,
|
baseY + 3,
|
||||||
"${engine.player.health}%",
|
"${engine.player.health}%",
|
||||||
healthColor,
|
healthColor,
|
||||||
vgaPanelDark,
|
vgaPanelDark,
|
||||||
);
|
);
|
||||||
|
cursorX += healthW + gap;
|
||||||
|
|
||||||
// AMMO
|
drawBorderedPanel(cursorX, baseY + 1, ammoW, 4);
|
||||||
drawBorderedPanel(offsetX + 92, viewHeight + 2, 12, 5);
|
_writeString(cursorX + 2, baseY + 2, "AMMO", white, vgaPanelDark);
|
||||||
_writeString(offsetX + 95, viewHeight + 3, "AMMO", white, vgaPanelDark);
|
|
||||||
_writeString(
|
_writeString(
|
||||||
offsetX + 97,
|
cursorX + 2,
|
||||||
viewHeight + 5,
|
baseY + 3,
|
||||||
"${engine.player.ammo}",
|
"${engine.player.ammo}",
|
||||||
white,
|
white,
|
||||||
vgaPanelDark,
|
vgaPanelDark,
|
||||||
);
|
);
|
||||||
|
cursorX += ammoW + gap;
|
||||||
|
|
||||||
// WEAPON
|
drawBorderedPanel(cursorX, baseY + 1, weaponW, 4);
|
||||||
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 > weaponW - 2) {
|
||||||
_writeString(offsetX + 107, viewHeight + 4, weapon, white, vgaPanelDark);
|
weapon = weapon.substring(0, weaponW - 2);
|
||||||
|
}
|
||||||
|
_writeString(cursorX + 1, baseY + 3, weapon, white, vgaPanelDark);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _drawMinimalHud(WolfEngine engine) {
|
||||||
|
final int vgaStatusBarBlue = ColorPalette.vga32Bit[153];
|
||||||
|
final int white = ColorPalette.vga32Bit[15];
|
||||||
|
final int red = ColorPalette.vga32Bit[4];
|
||||||
|
|
||||||
|
final int hudRows = height - viewHeight;
|
||||||
|
if (isTerminal) {
|
||||||
|
_fillTerminalRect(
|
||||||
|
projectionOffsetX,
|
||||||
|
viewHeight * 2,
|
||||||
|
projectionWidth,
|
||||||
|
hudRows * 2,
|
||||||
|
vgaStatusBarBlue,
|
||||||
|
);
|
||||||
|
_fillTerminalRect(
|
||||||
|
projectionOffsetX,
|
||||||
|
viewHeight * 2,
|
||||||
|
projectionWidth,
|
||||||
|
1,
|
||||||
|
white,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
_fillRect(0, viewHeight, width, hudRows, ' ', vgaStatusBarBlue);
|
||||||
|
_writeString(0, viewHeight, "═" * width, white);
|
||||||
|
}
|
||||||
|
|
||||||
|
final int healthColor = engine.player.health > 25 ? white : red;
|
||||||
|
String weapon = engine.player.currentWeapon.type.name.spacePascalCase!
|
||||||
|
.toUpperCase();
|
||||||
|
if (weapon.length > 8) {
|
||||||
|
weapon = weapon.substring(0, 8);
|
||||||
|
}
|
||||||
|
final String hudText =
|
||||||
|
'H:${engine.player.health}% A:${engine.player.ammo} S:${engine.player.score} W:$weapon';
|
||||||
|
|
||||||
|
final int lineY = viewHeight + 1;
|
||||||
|
if (lineY >= height) return;
|
||||||
|
|
||||||
|
final int drawStartX = isTerminal ? projectionOffsetX : 0;
|
||||||
|
final int drawWidth = isTerminal ? projectionWidth : width;
|
||||||
|
final int maxTextLen = math.max(0, drawWidth - 2);
|
||||||
|
String clipped = hudText;
|
||||||
|
if (clipped.length > maxTextLen) {
|
||||||
|
clipped = clipped.substring(0, maxTextLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
final int startX = drawStartX + ((drawWidth - clipped.length) ~/ 2);
|
||||||
|
_writeString(startX, lineY, clipped, healthColor, vgaStatusBarBlue);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _drawFullVgaHud(WolfEngine engine) {
|
void _drawFullVgaHud(WolfEngine engine) {
|
||||||
@@ -526,11 +635,13 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
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;
|
int maxDrawHeight = isTerminal ? _terminalPixelHeight : height;
|
||||||
|
int maxDrawWidth = isTerminal ? _viewportRightX : width;
|
||||||
|
|
||||||
double scaleX = width / 320.0;
|
double scaleX = (isTerminal ? projectionWidth : width) / 320.0;
|
||||||
double scaleY = (isTerminal ? _terminalPixelHeight : height) / 200.0;
|
double scaleY = (isTerminal ? _terminalPixelHeight : height) / 200.0;
|
||||||
|
|
||||||
int destStartX = (startX_320 * scaleX).toInt();
|
int destStartX =
|
||||||
|
(isTerminal ? projectionOffsetX : 0) + (startX_320 * scaleX).toInt();
|
||||||
int destStartY = (startY_200 * scaleY).toInt();
|
int destStartY = (startY_200 * scaleY).toInt();
|
||||||
int destWidth = (image.width * scaleX).toInt();
|
int destWidth = (image.width * scaleX).toInt();
|
||||||
int destHeight = (image.height * scaleY).toInt();
|
int destHeight = (image.height * scaleY).toInt();
|
||||||
@@ -541,7 +652,7 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
int drawY = destStartY + dy;
|
int drawY = destStartY + dy;
|
||||||
|
|
||||||
if (drawX >= 0 &&
|
if (drawX >= 0 &&
|
||||||
drawX < width &&
|
drawX < maxDrawWidth &&
|
||||||
drawY >= 0 &&
|
drawY >= 0 &&
|
||||||
drawY < maxDrawHeight) {
|
drawY < maxDrawHeight) {
|
||||||
int srcX = (dx / scaleX).toInt().clamp(0, image.width - 1);
|
int srcX = (dx / scaleX).toInt().clamp(0, image.width - 1);
|
||||||
@@ -597,12 +708,19 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
|
|
||||||
void _applyDamageFlashToScene() {
|
void _applyDamageFlashToScene() {
|
||||||
for (int y = 0; y < _terminalPixelHeight; y++) {
|
for (int y = 0; y < _terminalPixelHeight; y++) {
|
||||||
for (int x = 0; x < width; x++) {
|
for (int x = projectionOffsetX; x < _viewportRightX; x++) {
|
||||||
_scenePixels[y][x] = _applyDamageFlashToColor(_scenePixels[y][x]);
|
_scenePixels[y][x] = _applyDamageFlashToColor(_scenePixels[y][x]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int _argbToRawColor(int argb) {
|
||||||
|
int r = (argb >> 16) & 0xFF;
|
||||||
|
int g = (argb >> 8) & 0xFF;
|
||||||
|
int b = argb & 0xFF;
|
||||||
|
return (0xFF000000) | (b << 16) | (g << 8) | r;
|
||||||
|
}
|
||||||
|
|
||||||
int _applyDamageFlashToColor(int color) {
|
int _applyDamageFlashToColor(int color) {
|
||||||
double intensity = _engine.player.damageFlash;
|
double intensity = _engine.player.damageFlash;
|
||||||
int redBoost = (150 * intensity).toInt();
|
int redBoost = (150 * intensity).toInt();
|
||||||
|
|||||||
@@ -19,6 +19,13 @@ 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 width of the projection area used for raycasting and sprites.
|
||||||
|
/// Most renderers use the full buffer width.
|
||||||
|
int get projectionWidth => width;
|
||||||
|
|
||||||
|
/// Horizontal offset of the projection area within the output buffer.
|
||||||
|
int get projectionOffsetX => 0;
|
||||||
|
|
||||||
/// The logical height of the 3D projection before a renderer maps rows to output pixels.
|
/// 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
|
/// Most renderers use the visible view height. Terminal ASCII can override this to render
|
||||||
/// more vertical detail and collapse it into half-block glyphs.
|
/// more vertical detail and collapse it into half-block glyphs.
|
||||||
@@ -31,7 +38,7 @@ abstract class Rasterizer {
|
|||||||
height = buffer.height;
|
height = buffer.height;
|
||||||
// The 3D view typically takes up the top 80% of the screen
|
// The 3D view typically takes up the top 80% of the screen
|
||||||
viewHeight = (height * 0.8).toInt();
|
viewHeight = (height * 0.8).toInt();
|
||||||
zBuffer = List.filled(width, 0.0);
|
zBuffer = List.filled(projectionWidth, 0.0);
|
||||||
|
|
||||||
// 1. Setup the frame (clear screen, draw floor/ceiling)
|
// 1. Setup the frame (clear screen, draw floor/ceiling)
|
||||||
prepareFrame(engine);
|
prepareFrame(engine);
|
||||||
@@ -108,6 +115,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 sceneWidth = projectionWidth;
|
||||||
final int sceneHeight = projectionViewHeight;
|
final int sceneHeight = projectionViewHeight;
|
||||||
|
|
||||||
final Map<String, double> doorOffsets = engine.doorManager
|
final Map<String, double> doorOffsets = engine.doorManager
|
||||||
@@ -121,8 +129,8 @@ abstract class Rasterizer {
|
|||||||
);
|
);
|
||||||
Coordinate2D plane = Coordinate2D(-dir.y, dir.x) * math.tan(fov / 2);
|
Coordinate2D plane = Coordinate2D(-dir.y, dir.x) * math.tan(fov / 2);
|
||||||
|
|
||||||
for (int x = 0; x < width; x++) {
|
for (int x = 0; x < sceneWidth; x++) {
|
||||||
double cameraX = 2 * x / width - 1.0;
|
double cameraX = 2 * x / sceneWidth - 1.0;
|
||||||
Coordinate2D rayDir = dir + (plane * cameraX);
|
Coordinate2D rayDir = dir + (plane * cameraX);
|
||||||
|
|
||||||
int mapX = player.x.toInt();
|
int mapX = player.x.toInt();
|
||||||
@@ -302,7 +310,7 @@ abstract class Rasterizer {
|
|||||||
|
|
||||||
// Tell the implementation to draw this column
|
// Tell the implementation to draw this column
|
||||||
drawWallColumn(
|
drawWallColumn(
|
||||||
x,
|
projectionOffsetX + x,
|
||||||
drawStart,
|
drawStart,
|
||||||
drawEnd,
|
drawEnd,
|
||||||
columnHeight,
|
columnHeight,
|
||||||
@@ -317,6 +325,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 sceneWidth = projectionWidth;
|
||||||
final int sceneHeight = projectionViewHeight;
|
final int sceneHeight = projectionViewHeight;
|
||||||
|
|
||||||
// Sort from furthest to closest (Painter's Algorithm)
|
// Sort from furthest to closest (Painter's Algorithm)
|
||||||
@@ -343,7 +352,7 @@ abstract class Rasterizer {
|
|||||||
|
|
||||||
// Only process if the sprite is in front of the camera
|
// Only process if the sprite is in front of the camera
|
||||||
if (transformY > 0) {
|
if (transformY > 0) {
|
||||||
int spriteScreenX = ((width / 2) * (1 + transformX / transformY))
|
int spriteScreenX = ((sceneWidth / 2) * (1 + transformX / transformY))
|
||||||
.toInt();
|
.toInt();
|
||||||
int spriteHeight = ((sceneHeight / transformY).abs() * verticalStretch)
|
int spriteHeight = ((sceneHeight / transformY).abs() * verticalStretch)
|
||||||
.toInt();
|
.toInt();
|
||||||
@@ -361,7 +370,7 @@ abstract class Rasterizer {
|
|||||||
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, drawEndX);
|
int clipEndX = math.min(sceneWidth, drawEndX);
|
||||||
|
|
||||||
int safeIndex = entity.spriteIndex.clamp(
|
int safeIndex = entity.spriteIndex.clamp(
|
||||||
0,
|
0,
|
||||||
@@ -377,7 +386,7 @@ abstract class Rasterizer {
|
|||||||
|
|
||||||
// Tell the implementation to draw this stripe
|
// Tell the implementation to draw this stripe
|
||||||
drawSpriteStripe(
|
drawSpriteStripe(
|
||||||
stripe,
|
projectionOffsetX + stripe,
|
||||||
drawStartY,
|
drawStartY,
|
||||||
drawEndY,
|
drawEndY,
|
||||||
spriteHeight,
|
spriteHeight,
|
||||||
|
|||||||
Reference in New Issue
Block a user