import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:wolf_3d_data_types/wolf_3d_data_types.dart'; import 'package:wolf_3d_engine/wolf_3d_engine.dart'; import 'package:wolf_3d_entities/wolf_3d_entities.dart'; class ColoredChar { final String char; final Color color; ColoredChar(this.char, this.color); } class AsciiRasterizer { static const String _charset = "@%#*+=-:. "; // NEW: Helper to safely convert and artificially boost your raw memory colors Color _vgaToColor(int vgaColor, {double brightnessBoost = 2.0}) { int r = vgaColor & 0xFF; int g = (vgaColor >> 8) & 0xFF; int b = (vgaColor >> 16) & 0xFF; // Apply the boost and clamp to 255 to prevent color overflow r = (r * brightnessBoost).toInt().clamp(0, 255); g = (g * brightnessBoost).toInt().clamp(0, 255); b = (b * brightnessBoost).toInt().clamp(0, 255); // Force Alpha to 255 (fully opaque) return Color.fromARGB(255, r, g, b); } List> render(WolfEngine engine, FrameBuffer framebuffer) { final int width = framebuffer.width; final int height = framebuffer.height; // Grab ceiling and floor colors from the original palette final Color ceilingColor = _vgaToColor(ColorPalette.vga32Bit[25]); final Color floorColor = _vgaToColor(ColorPalette.vga32Bit[29]); final List> screen = List.generate( height, (_) => List.filled(width, ColoredChar(' ', ceilingColor)), ); final List zBuffer = List.filled(width, 0.0); final Player player = engine.player; final SpriteMap map = engine.currentLevel; final List wallTextures = engine.data.walls; final double fov = math.pi / 3; Coordinate2D dir = Coordinate2D( math.cos(player.angle), math.sin(player.angle), ); Coordinate2D plane = Coordinate2D(-dir.y, dir.x) * math.tan(fov / 2); // 1. CAST WALLS for (int x = 0; x < width; x++) { double cameraX = 2 * x / width - 1.0; Coordinate2D rayDir = dir + (plane * cameraX); int mapX = player.x.toInt(); int mapY = player.y.toInt(); double deltaDistX = (rayDir.x == 0) ? 1e30 : (1.0 / rayDir.x).abs(); double deltaDistY = (rayDir.y == 0) ? 1e30 : (1.0 / rayDir.y).abs(); double sideDistX, sideDistY; int stepX, stepY, side = 0, hitWallId = 0; bool hit = false; if (rayDir.x < 0) { stepX = -1; sideDistX = (player.x - mapX) * deltaDistX; } else { stepX = 1; sideDistX = (mapX + 1.0 - player.x) * deltaDistX; } if (rayDir.y < 0) { stepY = -1; sideDistY = (player.y - mapY) * deltaDistY; } else { stepY = 1; sideDistY = (mapY + 1.0 - player.y) * deltaDistY; } while (!hit) { if (sideDistX < sideDistY) { sideDistX += deltaDistX; mapX += stepX; side = 0; } else { sideDistY += deltaDistY; mapY += stepY; side = 1; } if (mapY < 0 || mapY >= map.length || mapX < 0 || mapX >= map[0].length) { break; } if (map[mapY][mapX] > 0) { hit = true; hitWallId = map[mapY][mapX]; } } double perpWallDist = (side == 0) ? (sideDistX - deltaDistX) : (sideDistY - deltaDistY); if (perpWallDist < 0.1) perpWallDist = 0.1; zBuffer[x] = perpWallDist; double wallX = (side == 0) ? player.y + perpWallDist * rayDir.y : player.x + perpWallDist * rayDir.x; wallX -= wallX.floor(); int texX = (wallX * 64).toInt().clamp(0, 63); int texNum = ((hitWallId - 1) * 2).clamp(0, wallTextures.length - 2); if (side == 1) texNum += 1; Sprite texture = wallTextures[texNum]; int columnHeight = (height / perpWallDist).toInt(); int drawStart = (-columnHeight ~/ 2 + height ~/ 2).clamp(0, height); int drawEnd = (columnHeight ~/ 2 + height ~/ 2).clamp(0, height); double brightness = (1.5 / (perpWallDist + 1.0)).clamp(0.0, 1.0); String wallChar = _charset[((1.0 - brightness) * (_charset.length - 1)).toInt().clamp( 0, _charset.length - 1, )]; for (int y = 0; y < height; y++) { if (y >= drawStart && y < drawEnd) { double relativeY = (y - drawStart) / (drawEnd - drawStart); int texY = (relativeY * 64).toInt().clamp(0, 63); int colorByte = texture.pixels[texX * 64 + texY]; // Use our new color conversion! Color pixelColor = _vgaToColor(ColorPalette.vga32Bit[colorByte]); // Optional: slightly darken the Y-side walls for a faux-lighting effect // if (side == 1) { // pixelColor = Color.fromARGB( // 255, // (pixelColor.r * 0.7).toInt(), // (pixelColor.g * 0.7).toInt(), // (pixelColor.b * 0.7).toInt(), // ); // } screen[y][x] = ColoredChar(wallChar, pixelColor); } else if (y >= drawEnd) { // Floor screen[y][x] = ColoredChar('.', floorColor); } else { // Ceiling screen[y][x] = ColoredChar(' ', ceilingColor); } } } // 2. CAST SPRITES (Enemies/Items) final List activeSprites = List.from(engine.entities); activeSprites.sort((a, b) { double distA = player.position.distanceTo(a.position); double distB = player.position.distanceTo(b.position); return distB.compareTo(distA); }); for (Entity entity in activeSprites) { Coordinate2D spritePos = entity.position - player.position; double invDet = 1.0 / (plane.x * dir.y - dir.x * plane.y); double transformX = invDet * (dir.y * spritePos.x - dir.x * spritePos.y); double transformY = invDet * (-plane.y * spritePos.x + plane.x * spritePos.y); if (transformY > 0) { int spriteScreenX = ((width / 2) * (1 + transformX / transformY)) .toInt(); int spriteHeight = (height / transformY).abs().toInt(); int spriteWidth = (spriteHeight * (width / height) * 0.6).toInt(); int drawStartY = -spriteHeight ~/ 2 + height ~/ 2; int drawEndY = spriteHeight ~/ 2 + height ~/ 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 safeIndex = entity.spriteIndex.clamp( 0, engine.data.sprites.length - 1, ); Sprite spritePixels = engine.data.sprites[safeIndex]; double brightness = (1.5 / (transformY + 1.0)).clamp(0.0, 1.0); String spriteChar = _charset[((1.0 - brightness) * (_charset.length - 1)).toInt().clamp( 0, _charset.length - 1, )]; for (int stripe = clipStartX; stripe < clipEndX; stripe++) { if (transformY < zBuffer[stripe]) { int texX = ((stripe - drawStartX) * 64 ~/ spriteWidth).clamp(0, 63); for ( int y = math.max(0, drawStartY); y < math.min(height, drawEndY); y++ ) { double relativeY = (y - drawStartY) / (drawEndY - drawStartY); int texY = (relativeY * 64).toInt().clamp(0, 63); int colorByte = spritePixels.pixels[texX * 64 + texY]; if (colorByte != 255) { // Apply the safe color conversion here as well Color pixelColor = _vgaToColor( ColorPalette.vga32Bit[colorByte], ); screen[y][stripe] = ColoredChar(spriteChar, pixelColor); } } } } } } return screen; } }