Migrate to a software rasterizer to dramatically improve performance
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
|
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
|
||||||
import 'package:wolf_3d_entities/wolf_3d_entities.dart';
|
import 'package:wolf_3d_entities/wolf_3d_entities.dart';
|
||||||
import 'package:wolf_3d_flutter/wolf_3d.dart';
|
import 'package:wolf_3d_flutter/wolf_3d.dart';
|
||||||
import 'package:wolf_3d_renderer/color_palette.dart';
|
|
||||||
|
|
||||||
class SpriteGallery extends StatelessWidget {
|
class SpriteGallery extends StatelessWidget {
|
||||||
final List<Sprite> sprites;
|
final List<Sprite> sprites;
|
||||||
@@ -76,12 +75,12 @@ class SingleSpritePainter extends CustomPainter {
|
|||||||
double pixelSize = size.width / 64;
|
double pixelSize = size.width / 64;
|
||||||
for (int x = 0; x < 64; x++) {
|
for (int x = 0; x < 64; x++) {
|
||||||
for (int y = 0; y < 64; y++) {
|
for (int y = 0; y < 64; y++) {
|
||||||
int colorByte = sprite[x][y];
|
int colorByte = sprite.pixels[x * 64 + y];
|
||||||
if (colorByte != 255) {
|
if (colorByte != 255) {
|
||||||
// Skip transparency
|
// Skip transparency
|
||||||
canvas.drawRect(
|
canvas.drawRect(
|
||||||
Rect.fromLTWH(x * pixelSize, y * pixelSize, pixelSize, pixelSize),
|
Rect.fromLTWH(x * pixelSize, y * pixelSize, pixelSize, pixelSize),
|
||||||
Paint()..color = ColorPalette.vga[colorByte],
|
Paint()..color = Color(ColorPalette.vga32Bit[colorByte]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -180,32 +180,33 @@ abstract class WLParser {
|
|||||||
// --- Private Helpers ---
|
// --- Private Helpers ---
|
||||||
|
|
||||||
static Sprite _parseWallChunk(ByteData vswap, int offset) {
|
static Sprite _parseWallChunk(ByteData vswap, int offset) {
|
||||||
// Generate the 64x64 pixel grid in column-major order functionally
|
final pixels = Uint8List(64 * 64);
|
||||||
return List.generate(
|
for (int x = 0; x < 64; x++) {
|
||||||
64,
|
for (int y = 0; y < 64; y++) {
|
||||||
(x) => List.generate(64, (y) => vswap.getUint8(offset + (x * 64) + y)),
|
// Flat 1D index: x * 64 + y
|
||||||
);
|
pixels[x * 64 + y] = vswap.getUint8(offset + (x * 64) + y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Sprite(pixels);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Sprite _parseSingleSprite(ByteData vswap, int offset) {
|
static Sprite _parseSingleSprite(ByteData vswap, int offset) {
|
||||||
// Initialize the 64x64 grid with 255 (The Magenta Transparency Color!)
|
// Initialize the 1D array with 255 (Transparency)
|
||||||
Sprite sprite = List.generate(64, (_) => List.filled(64, 255));
|
final pixels = Uint8List(64 * 64)..fillRange(0, 4096, 255);
|
||||||
|
Sprite sprite = Sprite(pixels);
|
||||||
|
|
||||||
int leftPix = vswap.getUint16(offset, Endian.little);
|
int leftPix = vswap.getUint16(offset, Endian.little);
|
||||||
int rightPix = vswap.getUint16(offset + 2, Endian.little);
|
int rightPix = vswap.getUint16(offset + 2, Endian.little);
|
||||||
|
|
||||||
// Parse vertical columns within the sprite bounds
|
|
||||||
for (int x = leftPix; x <= rightPix; x++) {
|
for (int x = leftPix; x <= rightPix; x++) {
|
||||||
int colOffset = vswap.getUint16(
|
int colOffset = vswap.getUint16(
|
||||||
offset + 4 + ((x - leftPix) * 2),
|
offset + 4 + ((x - leftPix) * 2),
|
||||||
Endian.little,
|
Endian.little,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (colOffset != 0) {
|
if (colOffset != 0) {
|
||||||
_parseSpriteColumn(vswap, sprite, x, offset, offset + colOffset);
|
_parseSpriteColumn(vswap, sprite, x, offset, offset + colOffset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sprite;
|
return sprite;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,23 +217,21 @@ abstract class WLParser {
|
|||||||
int baseOffset,
|
int baseOffset,
|
||||||
int cmdOffset,
|
int cmdOffset,
|
||||||
) {
|
) {
|
||||||
// Execute the column drawing commands
|
|
||||||
while (true) {
|
while (true) {
|
||||||
int endY = vswap.getUint16(cmdOffset, Endian.little);
|
int endY = vswap.getUint16(cmdOffset, Endian.little);
|
||||||
if (endY == 0) break; // 0 marks the end of the column
|
if (endY == 0) break;
|
||||||
endY ~/= 2; // Wolf3D stores Y coordinates multiplied by 2
|
endY ~/= 2;
|
||||||
|
|
||||||
int pixelOfs = vswap.getUint16(cmdOffset + 2, Endian.little);
|
int pixelOfs = vswap.getUint16(cmdOffset + 2, Endian.little);
|
||||||
|
|
||||||
int startY = vswap.getUint16(cmdOffset + 4, Endian.little);
|
int startY = vswap.getUint16(cmdOffset + 4, Endian.little);
|
||||||
startY ~/= 2;
|
startY ~/= 2;
|
||||||
|
|
||||||
for (int y = startY; y < endY; y++) {
|
for (int y = startY; y < endY; y++) {
|
||||||
// The Carmack 286 Hack: pixelOfs + y gives the exact byte address
|
// Write directly to the 1D array
|
||||||
sprite[x][y] = vswap.getUint8(baseOffset + pixelOfs + y);
|
sprite.pixels[x * 64 + y] = vswap.getUint8(baseOffset + pixelOfs + y);
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdOffset += 6; // Move to the next 6-byte instruction
|
cmdOffset += 6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
262
packages/wolf_3d_data_types/lib/src/color_palette.dart
Normal file
262
packages/wolf_3d_data_types/lib/src/color_palette.dart
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
abstract class ColorPalette {
|
||||||
|
static final Uint32List vga32Bit = Uint32List.fromList([
|
||||||
|
0xFF000000,
|
||||||
|
0xFFAA0000,
|
||||||
|
0xFF00AA00,
|
||||||
|
0xFFAAAA00,
|
||||||
|
0xFF0000AA,
|
||||||
|
0xFFAA00AA,
|
||||||
|
0xFF0055AA,
|
||||||
|
0xFFAAAAAA,
|
||||||
|
0xFF555555,
|
||||||
|
0xFFFF5555,
|
||||||
|
0xFF55FF55,
|
||||||
|
0xFFFFFF55,
|
||||||
|
0xFF5555FF,
|
||||||
|
0xFFFF55FF,
|
||||||
|
0xFF55FFFF,
|
||||||
|
0xFFFFFFFF,
|
||||||
|
0xFFEEEEEE,
|
||||||
|
0xFFDEDEDE,
|
||||||
|
0xFFD2D2D2,
|
||||||
|
0xFFC2C2C2,
|
||||||
|
0xFFB6B6B6,
|
||||||
|
0xFFAAAAAA,
|
||||||
|
0xFF999999,
|
||||||
|
0xFF8D8D8D,
|
||||||
|
0xFF7D7D7D,
|
||||||
|
0xFF717171,
|
||||||
|
0xFF656565,
|
||||||
|
0xFF555555,
|
||||||
|
0xFF484848,
|
||||||
|
0xFF383838,
|
||||||
|
0xFF2C2C2C,
|
||||||
|
0xFF202020,
|
||||||
|
0xFF0000FF,
|
||||||
|
0xFF0000EE,
|
||||||
|
0xFF0000E2,
|
||||||
|
0xFF0000D6,
|
||||||
|
0xFF0000CA,
|
||||||
|
0xFF0000BE,
|
||||||
|
0xFF0000B2,
|
||||||
|
0xFF0000A5,
|
||||||
|
0xFF000099,
|
||||||
|
0xFF000089,
|
||||||
|
0xFF00007D,
|
||||||
|
0xFF000071,
|
||||||
|
0xFF000065,
|
||||||
|
0xFF000059,
|
||||||
|
0xFF00004C,
|
||||||
|
0xFF000040,
|
||||||
|
0xFFDADAFF,
|
||||||
|
0xFFBABAFF,
|
||||||
|
0xFF9D9DFF,
|
||||||
|
0xFF7D7DFF,
|
||||||
|
0xFF5D5DFF,
|
||||||
|
0xFF4040FF,
|
||||||
|
0xFF2020FF,
|
||||||
|
0xFF0000FF,
|
||||||
|
0xFF5DAAFF,
|
||||||
|
0xFF4099FF,
|
||||||
|
0xFF2089FF,
|
||||||
|
0xFF0079FF,
|
||||||
|
0xFF006DE6,
|
||||||
|
0xFF0061CE,
|
||||||
|
0xFF0055B6,
|
||||||
|
0xFF004C9D,
|
||||||
|
0xFFDAFFFF,
|
||||||
|
0xBAFFFF,
|
||||||
|
0x9DFFFF,
|
||||||
|
0x7DFFFF,
|
||||||
|
0x5DFAFF,
|
||||||
|
0x40F6FF,
|
||||||
|
0x20F6FF,
|
||||||
|
0x00F6FF,
|
||||||
|
0x00DAE6,
|
||||||
|
0x00C6CE,
|
||||||
|
0x00AEB6,
|
||||||
|
0x009D9D,
|
||||||
|
0x008585,
|
||||||
|
0x006D71,
|
||||||
|
0x005559,
|
||||||
|
0x004040,
|
||||||
|
0x5DFFD2,
|
||||||
|
0x40FFC6,
|
||||||
|
0x20FFB6,
|
||||||
|
0x00FFA1,
|
||||||
|
0x00E691,
|
||||||
|
0x00CE81,
|
||||||
|
0x00B675,
|
||||||
|
0x009D61,
|
||||||
|
0xDAFFDA,
|
||||||
|
0xBAFFBE,
|
||||||
|
0x9DFF9D,
|
||||||
|
0x7DFF81,
|
||||||
|
0x5DFF61,
|
||||||
|
0x40FF40,
|
||||||
|
0x20FF20,
|
||||||
|
0x00FF00,
|
||||||
|
0x00FF00,
|
||||||
|
0x00EE00,
|
||||||
|
0x00E200,
|
||||||
|
0x00D600,
|
||||||
|
0x00CA04,
|
||||||
|
0x00BE04,
|
||||||
|
0x00B204,
|
||||||
|
0x00A504,
|
||||||
|
0x009904,
|
||||||
|
0x008904,
|
||||||
|
0x007D04,
|
||||||
|
0x007104,
|
||||||
|
0x006504,
|
||||||
|
0x005904,
|
||||||
|
0x004C04,
|
||||||
|
0x004004,
|
||||||
|
0xFFFFDA,
|
||||||
|
0xFFFFBA,
|
||||||
|
0xFFFF9D,
|
||||||
|
0xFAFF7D,
|
||||||
|
0xFFFF5D,
|
||||||
|
0xFFFF40,
|
||||||
|
0xFFFF20,
|
||||||
|
0xFFFF00,
|
||||||
|
0xE6E600,
|
||||||
|
0xCECE00,
|
||||||
|
0xB6B600,
|
||||||
|
0x9D9D00,
|
||||||
|
0x858500,
|
||||||
|
0x717100,
|
||||||
|
0x595900,
|
||||||
|
0x404000,
|
||||||
|
0xFFBE5D,
|
||||||
|
0xFFB240,
|
||||||
|
0xFFAA20,
|
||||||
|
0xFF9D00,
|
||||||
|
0xE68D00,
|
||||||
|
0xCE7D00,
|
||||||
|
0xB66D00,
|
||||||
|
0x9D5D00,
|
||||||
|
0xDADADA,
|
||||||
|
0xFFBEBA,
|
||||||
|
0xFF9D9D,
|
||||||
|
0xFF817D,
|
||||||
|
0xFF615D,
|
||||||
|
0xFF4040,
|
||||||
|
0xFF2420,
|
||||||
|
0xFF0400,
|
||||||
|
0xFF0000,
|
||||||
|
0xEE0000,
|
||||||
|
0xE20000,
|
||||||
|
0xD60000,
|
||||||
|
0xCA0000,
|
||||||
|
0xBE0000,
|
||||||
|
0xB20000,
|
||||||
|
0xA50000,
|
||||||
|
0x990000,
|
||||||
|
0x890000,
|
||||||
|
0x7D0000,
|
||||||
|
0x710000,
|
||||||
|
0x650000,
|
||||||
|
0x590000,
|
||||||
|
0x4C0000,
|
||||||
|
0x400000,
|
||||||
|
0x282828,
|
||||||
|
0x34E2FF,
|
||||||
|
0x24D6FF,
|
||||||
|
0x18CEFF,
|
||||||
|
0x08C2FF,
|
||||||
|
0x00B6FF,
|
||||||
|
0xFF20B6,
|
||||||
|
0xFF00AA,
|
||||||
|
0xE60099,
|
||||||
|
0xCE0081,
|
||||||
|
0xB60075,
|
||||||
|
0x9D0061,
|
||||||
|
0x850050,
|
||||||
|
0x710044,
|
||||||
|
0x590034,
|
||||||
|
0x400028,
|
||||||
|
0xFFDAFF,
|
||||||
|
0xFFBAFF,
|
||||||
|
0xFF9DFF,
|
||||||
|
0xFF7DFF,
|
||||||
|
0xFF5DFF,
|
||||||
|
0xFF40FF,
|
||||||
|
0xFF20FF,
|
||||||
|
0xFF00FF,
|
||||||
|
0xE600E2,
|
||||||
|
0xCE00CA,
|
||||||
|
0xB600B6,
|
||||||
|
0x9D009D,
|
||||||
|
0x850085,
|
||||||
|
0x71006D,
|
||||||
|
0x590059,
|
||||||
|
0x400040,
|
||||||
|
0xDEEAFF,
|
||||||
|
0xD2E2FF,
|
||||||
|
0xC6DAFF,
|
||||||
|
0xBED6FF,
|
||||||
|
0xB2CEFF,
|
||||||
|
0xA5C6FF,
|
||||||
|
0x9DBEFF,
|
||||||
|
0x91BAFF,
|
||||||
|
0x81B2FF,
|
||||||
|
0x1F57FA,
|
||||||
|
0x619DFF,
|
||||||
|
0x5D95F2,
|
||||||
|
0x598DEA,
|
||||||
|
0x5589DE,
|
||||||
|
0x5081D2,
|
||||||
|
0x4C7DCA,
|
||||||
|
0x4879BE,
|
||||||
|
0x4471B6,
|
||||||
|
0x4069AA,
|
||||||
|
0x3C65A1,
|
||||||
|
0x38619D,
|
||||||
|
0x345D91,
|
||||||
|
0x305989,
|
||||||
|
0x2C5081,
|
||||||
|
0x284C75,
|
||||||
|
0x24486D,
|
||||||
|
0x20405D,
|
||||||
|
0x1C3C55,
|
||||||
|
0x183848,
|
||||||
|
0x183040,
|
||||||
|
0x142C38,
|
||||||
|
0x0C2028,
|
||||||
|
0x650061,
|
||||||
|
0x656500,
|
||||||
|
0x616100,
|
||||||
|
0x1C0000,
|
||||||
|
0x2C0000,
|
||||||
|
0x102430,
|
||||||
|
0x480048,
|
||||||
|
0x500050,
|
||||||
|
0x340000,
|
||||||
|
0x1C1C1C,
|
||||||
|
0x4C4C4C,
|
||||||
|
0x5D5D5D,
|
||||||
|
0x404040,
|
||||||
|
0x303030,
|
||||||
|
0x343434,
|
||||||
|
0xF6F6DA,
|
||||||
|
0xEAEABA,
|
||||||
|
0xDEDED9,
|
||||||
|
0xCACA75,
|
||||||
|
0xC2C248,
|
||||||
|
0xB6B620,
|
||||||
|
0xB2B220,
|
||||||
|
0xA5A500,
|
||||||
|
0x999900,
|
||||||
|
0x8D8D00,
|
||||||
|
0x858500,
|
||||||
|
0x7D7D00,
|
||||||
|
0x797900,
|
||||||
|
0x757500,
|
||||||
|
0x717100,
|
||||||
|
0x6D6D00,
|
||||||
|
0x890099,
|
||||||
|
]);
|
||||||
|
}
|
||||||
19
packages/wolf_3d_data_types/lib/src/frame_buffer.dart
Normal file
19
packages/wolf_3d_data_types/lib/src/frame_buffer.dart
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
class FrameBuffer {
|
||||||
|
final int width;
|
||||||
|
final int height;
|
||||||
|
|
||||||
|
// A 1D array representing the 2D screen.
|
||||||
|
// Length = width * height.
|
||||||
|
final Uint32List pixels;
|
||||||
|
|
||||||
|
FrameBuffer(this.width, this.height) : pixels = Uint32List(width * height);
|
||||||
|
|
||||||
|
// Helper to clear the screen (e.g., draw ceiling and floor)
|
||||||
|
void clear(int ceilingColor32, int floorColor32) {
|
||||||
|
int half = (width * height) ~/ 2;
|
||||||
|
pixels.fillRange(0, half, ceilingColor32);
|
||||||
|
pixels.fillRange(half, pixels.length, floorColor32);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,21 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
typedef Matrix<T> = List<List<T>>;
|
typedef Matrix<T> = List<List<T>>;
|
||||||
|
typedef SpriteMap = Matrix<int>;
|
||||||
|
|
||||||
typedef Sprite = Matrix<int>;
|
class Sprite {
|
||||||
|
final Uint8List pixels;
|
||||||
|
|
||||||
typedef Wall = Sprite;
|
Sprite(this.pixels);
|
||||||
|
|
||||||
typedef Level = Matrix<int>;
|
// Factory to convert your 2D matrices into a 1D array during load time
|
||||||
|
factory Sprite.fromMatrix(Matrix<int> matrix) {
|
||||||
|
final pixels = Uint8List(64 * 64);
|
||||||
|
for (int y = 0; y < 64; y++) {
|
||||||
|
for (int x = 0; x < 64; x++) {
|
||||||
|
pixels[x * 64 + y] = matrix[x][y];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Sprite(pixels);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
|
|||||||
|
|
||||||
class WolfLevel {
|
class WolfLevel {
|
||||||
final String name;
|
final String name;
|
||||||
final Sprite wallGrid;
|
final SpriteMap wallGrid;
|
||||||
final Sprite objectGrid;
|
final SpriteMap objectGrid;
|
||||||
final int musicIndex;
|
final int musicIndex;
|
||||||
|
|
||||||
const WolfLevel({
|
const WolfLevel({
|
||||||
|
|||||||
@@ -4,10 +4,12 @@
|
|||||||
library;
|
library;
|
||||||
|
|
||||||
export 'src/cardinal_direction.dart' show CardinalDirection;
|
export 'src/cardinal_direction.dart' show CardinalDirection;
|
||||||
|
export 'src/color_palette.dart' show ColorPalette;
|
||||||
export 'src/coordinate_2d.dart' show Coordinate2D;
|
export 'src/coordinate_2d.dart' show Coordinate2D;
|
||||||
export 'src/difficulty.dart' show Difficulty;
|
export 'src/difficulty.dart' show Difficulty;
|
||||||
export 'src/enemy_map_data.dart' show EnemyMapData;
|
export 'src/enemy_map_data.dart' show EnemyMapData;
|
||||||
export 'src/episode.dart' show Episode;
|
export 'src/episode.dart' show Episode;
|
||||||
|
export 'src/frame_buffer.dart' show FrameBuffer;
|
||||||
export 'src/game_file.dart' show GameFile;
|
export 'src/game_file.dart' show GameFile;
|
||||||
export 'src/game_version.dart' show GameVersion;
|
export 'src/game_version.dart' show GameVersion;
|
||||||
export 'src/image.dart' show VgaImage;
|
export 'src/image.dart' show VgaImage;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class DoorManager {
|
|||||||
|
|
||||||
DoorManager({required this.onPlaySound});
|
DoorManager({required this.onPlaySound});
|
||||||
|
|
||||||
void initDoors(Sprite wallGrid) {
|
void initDoors(SpriteMap wallGrid) {
|
||||||
doors.clear();
|
doors.clear();
|
||||||
for (int y = 0; y < wallGrid.length; y++) {
|
for (int y = 0; y < wallGrid.length; y++) {
|
||||||
for (int x = 0; x < wallGrid[y].length; x++) {
|
for (int x = 0; x < wallGrid[y].length; x++) {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class PushwallManager {
|
|||||||
final Map<String, Pushwall> pushwalls = {};
|
final Map<String, Pushwall> pushwalls = {};
|
||||||
Pushwall? activePushwall;
|
Pushwall? activePushwall;
|
||||||
|
|
||||||
void initPushwalls(Sprite wallGrid, Sprite objectGrid) {
|
void initPushwalls(SpriteMap wallGrid, SpriteMap objectGrid) {
|
||||||
pushwalls.clear();
|
pushwalls.clear();
|
||||||
activePushwall = null;
|
activePushwall = null;
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ class PushwallManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void update(Duration elapsed, Sprite wallGrid) {
|
void update(Duration elapsed, SpriteMap wallGrid) {
|
||||||
if (activePushwall == null) return;
|
if (activePushwall == null) return;
|
||||||
final pw = activePushwall!;
|
final pw = activePushwall!;
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ class PushwallManager {
|
|||||||
double playerX,
|
double playerX,
|
||||||
double playerY,
|
double playerY,
|
||||||
double playerAngle,
|
double playerAngle,
|
||||||
Sprite wallGrid,
|
SpriteMap wallGrid,
|
||||||
) {
|
) {
|
||||||
// Only one pushwall can move at a time in the original engine!
|
// Only one pushwall can move at a time in the original engine!
|
||||||
if (activePushwall != null) return;
|
if (activePushwall != null) return;
|
||||||
|
|||||||
@@ -1,65 +1,53 @@
|
|||||||
import 'dart:math' as math;
|
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_data_types/wolf_3d_data_types.dart';
|
||||||
import 'package:wolf_3d_engine/wolf_3d_engine.dart';
|
import 'package:wolf_3d_engine/wolf_3d_engine.dart';
|
||||||
import 'package:wolf_3d_entities/wolf_3d_entities.dart';
|
import 'package:wolf_3d_entities/wolf_3d_entities.dart';
|
||||||
import 'package:wolf_3d_renderer/color_palette.dart';
|
|
||||||
|
|
||||||
class RaycasterPainter extends CustomPainter {
|
class SoftwareRasterizer {
|
||||||
final Level map;
|
// Pre-calculated VGA colors for ceiling and floor
|
||||||
final List<Sprite> textures;
|
final int ceilingColor = ColorPalette.vga32Bit[25];
|
||||||
final Player player;
|
final int floorColor = ColorPalette.vga32Bit[29];
|
||||||
final double fov;
|
|
||||||
final Map<String, double> doorOffsets;
|
|
||||||
final Pushwall? activePushwall;
|
|
||||||
final List<Sprite> sprites;
|
|
||||||
final List<Entity> entities;
|
|
||||||
|
|
||||||
RaycasterPainter({
|
void render(WolfEngine engine, FrameBuffer buffer) {
|
||||||
required this.map,
|
// 1. Wipe the screen clean with ceiling and floor colors
|
||||||
required this.textures,
|
_clearScreen(buffer);
|
||||||
required this.player,
|
|
||||||
required this.fov,
|
|
||||||
required this.doorOffsets,
|
|
||||||
this.activePushwall,
|
|
||||||
required this.sprites,
|
|
||||||
required this.entities,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
// 2. We need a Z-Buffer (1D array mapping to screen width) so sprites
|
||||||
void paint(Canvas canvas, Size size) {
|
// know if they are hiding behind a wall slice.
|
||||||
final Paint bgPaint = Paint()..isAntiAlias = false;
|
List<double> zBuffer = List.filled(buffer.width, 0.0);
|
||||||
|
|
||||||
// 1. Draw Ceiling & Floor
|
// 3. Do the math and draw the walls, filling the Z-Buffer as we go
|
||||||
canvas.drawRect(
|
_castWalls(engine, buffer, zBuffer);
|
||||||
Rect.fromLTWH(0, 0, size.width, size.height / 2),
|
|
||||||
bgPaint..color = Colors.blueGrey[900]!,
|
|
||||||
);
|
|
||||||
canvas.drawRect(
|
|
||||||
Rect.fromLTWH(0, size.height / 2, size.width, size.height / 2),
|
|
||||||
bgPaint..color = Colors.brown[900]!,
|
|
||||||
);
|
|
||||||
|
|
||||||
const int renderWidth = 320;
|
// 4. Draw the entities/sprites
|
||||||
double columnWidth = size.width / renderWidth;
|
_castSprites(engine, buffer, zBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
final Paint columnPaint = Paint()
|
void _clearScreen(FrameBuffer buffer) {
|
||||||
..isAntiAlias = false
|
int halfScreen = (buffer.width * buffer.height) ~/ 2;
|
||||||
..strokeWidth = columnWidth + 0.5;
|
buffer.pixels.fillRange(0, halfScreen, ceilingColor);
|
||||||
|
buffer.pixels.fillRange(halfScreen, buffer.pixels.length, floorColor);
|
||||||
|
}
|
||||||
|
|
||||||
List<double> zBuffer = List.filled(renderWidth, 0.0);
|
void _castWalls(WolfEngine engine, FrameBuffer buffer, List<double> zBuffer) {
|
||||||
|
final double fov = math.pi / 3;
|
||||||
|
final Player player = engine.player;
|
||||||
|
final SpriteMap map = engine.currentLevel;
|
||||||
|
|
||||||
|
// Fetch dynamic states from the managers
|
||||||
|
final Map<String, double> doorOffsets = engine.doorManager
|
||||||
|
.getOffsetsForRenderer();
|
||||||
|
final Pushwall? activePushwall = engine.pushwallManager.activePushwall;
|
||||||
|
|
||||||
Coordinate2D dir = Coordinate2D(
|
Coordinate2D dir = Coordinate2D(
|
||||||
math.cos(player.angle),
|
math.cos(player.angle),
|
||||||
math.sin(player.angle),
|
math.sin(player.angle),
|
||||||
);
|
);
|
||||||
|
|
||||||
Coordinate2D plane = Coordinate2D(-dir.y, dir.x) * math.tan(fov / 2);
|
Coordinate2D plane = Coordinate2D(-dir.y, dir.x) * math.tan(fov / 2);
|
||||||
|
|
||||||
// --- 1. CAST WALLS ---
|
for (int x = 0; x < buffer.width; x++) {
|
||||||
for (int x = 0; x < renderWidth; x++) {
|
double cameraX = 2 * x / buffer.width - 1.0;
|
||||||
double cameraX = 2 * x / renderWidth - 1.0;
|
|
||||||
Coordinate2D rayDir = dir + (plane * cameraX);
|
Coordinate2D rayDir = dir + (plane * cameraX);
|
||||||
|
|
||||||
int mapX = player.x.toInt();
|
int mapX = player.x.toInt();
|
||||||
@@ -78,8 +66,8 @@ class RaycasterPainter extends CustomPainter {
|
|||||||
int side = 0;
|
int side = 0;
|
||||||
int hitWallId = 0;
|
int hitWallId = 0;
|
||||||
|
|
||||||
double textureOffset = 0.0; // Replaces doorOffset to handle both
|
double textureOffset = 0.0;
|
||||||
bool customDistCalculated = false; // Flag to skip standard distance
|
bool customDistCalculated = false;
|
||||||
Set<String> ignoredDoors = {};
|
Set<String> ignoredDoors = {};
|
||||||
|
|
||||||
if (rayDir.x < 0) {
|
if (rayDir.x < 0) {
|
||||||
@@ -97,7 +85,7 @@ class RaycasterPainter extends CustomPainter {
|
|||||||
sideDistY = (mapY + 1.0 - player.y) * deltaDistY;
|
sideDistY = (mapY + 1.0 - player.y) * deltaDistY;
|
||||||
}
|
}
|
||||||
|
|
||||||
// DDA Loop
|
// --- DDA LOOP ---
|
||||||
while (!hit) {
|
while (!hit) {
|
||||||
if (sideDistX < sideDistY) {
|
if (sideDistX < sideDistY) {
|
||||||
sideDistX += deltaDistX;
|
sideDistX += deltaDistX;
|
||||||
@@ -118,7 +106,7 @@ class RaycasterPainter extends CustomPainter {
|
|||||||
} else if (map[mapY][mapX] > 0) {
|
} else if (map[mapY][mapX] > 0) {
|
||||||
String mapKey = '$mapX,$mapY';
|
String mapKey = '$mapX,$mapY';
|
||||||
|
|
||||||
// --- DOOR LOGIC ---
|
// DOOR LOGIC
|
||||||
if (map[mapY][mapX] >= 90 && !ignoredDoors.contains(mapKey)) {
|
if (map[mapY][mapX] >= 90 && !ignoredDoors.contains(mapKey)) {
|
||||||
double currentOffset = doorOffsets[mapKey] ?? 0.0;
|
double currentOffset = doorOffsets[mapKey] ?? 0.0;
|
||||||
if (currentOffset > 0.0) {
|
if (currentOffset > 0.0) {
|
||||||
@@ -131,37 +119,35 @@ class RaycasterPainter extends CustomPainter {
|
|||||||
wallXTemp -= wallXTemp.floor();
|
wallXTemp -= wallXTemp.floor();
|
||||||
if (wallXTemp < currentOffset) {
|
if (wallXTemp < currentOffset) {
|
||||||
ignoredDoors.add(mapKey);
|
ignoredDoors.add(mapKey);
|
||||||
continue; // Ray passed through the open door gap
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hit = true;
|
hit = true;
|
||||||
hitWallId = map[mapY][mapX];
|
hitWallId = map[mapY][mapX];
|
||||||
textureOffset = currentOffset;
|
textureOffset = currentOffset;
|
||||||
}
|
}
|
||||||
// --- PUSHWALL LOGIC ---
|
// PUSHWALL LOGIC
|
||||||
else if (activePushwall != null &&
|
else if (activePushwall != null &&
|
||||||
mapX == activePushwall!.x &&
|
mapX == activePushwall.x &&
|
||||||
mapY == activePushwall!.y) {
|
mapY == activePushwall.y) {
|
||||||
hit = true;
|
hit = true;
|
||||||
hitWallId = map[mapY][mapX];
|
hitWallId = map[mapY][mapX];
|
||||||
|
|
||||||
double pOffset = activePushwall!.offset;
|
double pOffset = activePushwall.offset;
|
||||||
int pDirX = activePushwall!.dirX;
|
int pDirX = activePushwall.dirX;
|
||||||
int pDirY = activePushwall!.dirY;
|
int pDirY = activePushwall.dirY;
|
||||||
|
|
||||||
perpWallDist = (side == 0)
|
perpWallDist = (side == 0)
|
||||||
? (sideDistX - deltaDistX)
|
? (sideDistX - deltaDistX)
|
||||||
: (sideDistY - deltaDistY);
|
: (sideDistY - deltaDistY);
|
||||||
|
|
||||||
// Did we hit the face that is being pushed deeper?
|
|
||||||
if (side == 0 && pDirX != 0) {
|
if (side == 0 && pDirX != 0) {
|
||||||
if (pDirX == stepX) {
|
if (pDirX == stepX) {
|
||||||
double intersect = perpWallDist + pOffset * deltaDistX;
|
double intersect = perpWallDist + pOffset * deltaDistX;
|
||||||
if (intersect < sideDistY) {
|
if (intersect < sideDistY) {
|
||||||
perpWallDist = intersect; // Hit the recessed front face
|
perpWallDist = intersect;
|
||||||
} else {
|
} else {
|
||||||
side =
|
side = 1;
|
||||||
1; // Missed the front face, hit the newly exposed side!
|
|
||||||
perpWallDist = sideDistY - deltaDistY;
|
perpWallDist = sideDistY - deltaDistY;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -180,7 +166,6 @@ class RaycasterPainter extends CustomPainter {
|
|||||||
perpWallDist -= (1.0 - pOffset) * deltaDistY;
|
perpWallDist -= (1.0 - pOffset) * deltaDistY;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// We hit the side of the sliding block. Did the ray slip behind it?
|
|
||||||
double wallFraction = (side == 0)
|
double wallFraction = (side == 0)
|
||||||
? player.y + perpWallDist * rayDir.y
|
? player.y + perpWallDist * rayDir.y
|
||||||
: player.x + perpWallDist * rayDir.x;
|
: player.x + perpWallDist * rayDir.x;
|
||||||
@@ -189,23 +174,17 @@ class RaycasterPainter extends CustomPainter {
|
|||||||
if (side == 0) {
|
if (side == 0) {
|
||||||
if (pDirY == 1 && wallFraction < pOffset) hit = false;
|
if (pDirY == 1 && wallFraction < pOffset) hit = false;
|
||||||
if (pDirY == -1 && wallFraction > (1.0 - pOffset)) hit = false;
|
if (pDirY == -1 && wallFraction > (1.0 - pOffset)) hit = false;
|
||||||
if (hit) {
|
if (hit) textureOffset = pOffset * pDirY;
|
||||||
textureOffset =
|
|
||||||
pOffset * pDirY; // Stick the texture to the block
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (pDirX == 1 && wallFraction < pOffset) hit = false;
|
if (pDirX == 1 && wallFraction < pOffset) hit = false;
|
||||||
if (pDirX == -1 && wallFraction > (1.0 - pOffset)) hit = false;
|
if (pDirX == -1 && wallFraction > (1.0 - pOffset)) hit = false;
|
||||||
if (hit) {
|
if (hit) textureOffset = pOffset * pDirX;
|
||||||
textureOffset =
|
|
||||||
pOffset * pDirX; // Stick the texture to the block
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!hit) continue;
|
||||||
|
customDistCalculated = true;
|
||||||
}
|
}
|
||||||
if (!hit) continue; // The ray slipped past! Keep looping.
|
// STANDARD WALL
|
||||||
customDistCalculated = true; // Lock in our custom distance math
|
|
||||||
}
|
|
||||||
// --- STANDARD WALL ---
|
|
||||||
else {
|
else {
|
||||||
hit = true;
|
hit = true;
|
||||||
hitWallId = map[mapY][mapX];
|
hitWallId = map[mapY][mapX];
|
||||||
@@ -215,7 +194,6 @@ class RaycasterPainter extends CustomPainter {
|
|||||||
|
|
||||||
if (hitOutOfBounds) continue;
|
if (hitOutOfBounds) continue;
|
||||||
|
|
||||||
// Apply standard math ONLY if we didn't calculate a sub-tile pushwall distance
|
|
||||||
if (!customDistCalculated) {
|
if (!customDistCalculated) {
|
||||||
if (side == 0) {
|
if (side == 0) {
|
||||||
perpWallDist = (sideDistX - deltaDistX);
|
perpWallDist = (sideDistX - deltaDistX);
|
||||||
@@ -224,6 +202,7 @@ class RaycasterPainter extends CustomPainter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log the distance so sprites know if they are occluded
|
||||||
zBuffer[x] = perpWallDist;
|
zBuffer[x] = perpWallDist;
|
||||||
|
|
||||||
double wallX = (side == 0)
|
double wallX = (side == 0)
|
||||||
@@ -231,32 +210,91 @@ class RaycasterPainter extends CustomPainter {
|
|||||||
: player.x + perpWallDist * rayDir.x;
|
: player.x + perpWallDist * rayDir.x;
|
||||||
wallX -= wallX.floor();
|
wallX -= wallX.floor();
|
||||||
|
|
||||||
double drawX = x * columnWidth;
|
|
||||||
|
|
||||||
_drawTexturedColumn(
|
_drawTexturedColumn(
|
||||||
canvas,
|
x,
|
||||||
drawX,
|
|
||||||
perpWallDist,
|
perpWallDist,
|
||||||
wallX,
|
wallX,
|
||||||
side,
|
side,
|
||||||
size,
|
|
||||||
hitWallId,
|
hitWallId,
|
||||||
textures,
|
engine.data.walls, // Wall textures
|
||||||
textureOffset,
|
textureOffset,
|
||||||
columnPaint,
|
buffer,
|
||||||
|
player.angle,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- 2. DRAW SPRITES ---
|
void _drawTexturedColumn(
|
||||||
// (Keep your existing sprite rendering logic exactly the same)
|
int x,
|
||||||
List<Entity> activeSprites = List.from(entities);
|
double distance,
|
||||||
|
double wallX,
|
||||||
|
int side,
|
||||||
|
int hitWallId,
|
||||||
|
List<Sprite> textures,
|
||||||
|
double textureOffset,
|
||||||
|
FrameBuffer buffer,
|
||||||
|
double playerAngle,
|
||||||
|
) {
|
||||||
|
if (distance <= 0.01) distance = 0.01;
|
||||||
|
|
||||||
|
int lineHeight = (buffer.height / distance).toInt();
|
||||||
|
|
||||||
|
int drawStart = -lineHeight ~/ 2 + buffer.height ~/ 2;
|
||||||
|
if (drawStart < 0) drawStart = 0;
|
||||||
|
|
||||||
|
int drawEnd = lineHeight ~/ 2 + buffer.height ~/ 2;
|
||||||
|
if (drawEnd >= buffer.height) drawEnd = buffer.height - 1;
|
||||||
|
|
||||||
|
int texNum;
|
||||||
|
if (hitWallId >= 90) {
|
||||||
|
texNum = 98.clamp(0, textures.length - 1);
|
||||||
|
} else {
|
||||||
|
texNum = ((hitWallId - 1) * 2).clamp(0, textures.length - 2);
|
||||||
|
if (side == 1) texNum += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int texX = (((wallX - textureOffset) % 1.0) * 64).toInt().clamp(0, 63);
|
||||||
|
if (side == 0 && math.cos(playerAngle) > 0) texX = 63 - texX;
|
||||||
|
if (side == 1 && math.sin(playerAngle) < 0) texX = 63 - texX;
|
||||||
|
|
||||||
|
double step = 64.0 / lineHeight;
|
||||||
|
double texPos = (drawStart - buffer.height / 2 + lineHeight / 2) * step;
|
||||||
|
|
||||||
|
Sprite texture = textures[texNum];
|
||||||
|
|
||||||
|
for (int y = drawStart; y < drawEnd; y++) {
|
||||||
|
int texY = texPos.toInt() & 63;
|
||||||
|
texPos += step;
|
||||||
|
|
||||||
|
int colorByte = texture.pixels[texX * 64 + texY];
|
||||||
|
int color32 = ColorPalette.vga32Bit[colorByte];
|
||||||
|
|
||||||
|
buffer.pixels[y * buffer.width + x] = color32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _castSprites(
|
||||||
|
WolfEngine engine,
|
||||||
|
FrameBuffer buffer,
|
||||||
|
List<double> zBuffer,
|
||||||
|
) {
|
||||||
|
final Player player = engine.player;
|
||||||
|
final List<Entity> activeSprites = List.from(engine.entities);
|
||||||
|
|
||||||
|
// Sort entities from furthest to closest (Painter's Algorithm)
|
||||||
activeSprites.sort((a, b) {
|
activeSprites.sort((a, b) {
|
||||||
double distA = player.position.distanceTo(a.position);
|
double distA = player.position.distanceTo(a.position);
|
||||||
double distB = player.position.distanceTo(b.position);
|
double distB = player.position.distanceTo(b.position);
|
||||||
return distB.compareTo(distA);
|
return distB.compareTo(distA);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Coordinate2D dir = Coordinate2D(
|
||||||
|
math.cos(player.angle),
|
||||||
|
math.sin(player.angle),
|
||||||
|
);
|
||||||
|
Coordinate2D plane =
|
||||||
|
Coordinate2D(-dir.y, dir.x) * math.tan((math.pi / 3) / 2);
|
||||||
|
|
||||||
for (Entity entity in activeSprites) {
|
for (Entity entity in activeSprites) {
|
||||||
Coordinate2D spritePos = entity.position - player.position;
|
Coordinate2D spritePos = entity.position - player.position;
|
||||||
|
|
||||||
@@ -266,104 +304,55 @@ class RaycasterPainter extends CustomPainter {
|
|||||||
invDet * (-plane.y * spritePos.x + plane.x * spritePos.y);
|
invDet * (-plane.y * spritePos.x + plane.x * spritePos.y);
|
||||||
|
|
||||||
if (transformY > 0) {
|
if (transformY > 0) {
|
||||||
int spriteScreenX = ((renderWidth / 2) * (1 + transformX / transformY))
|
int spriteScreenX = ((buffer.width / 2) * (1 + transformX / transformY))
|
||||||
.toInt();
|
.toInt();
|
||||||
int spriteHeight = (size.height / transformY).abs().toInt();
|
int spriteHeight = (buffer.height / transformY).abs().toInt();
|
||||||
int spriteColumnWidth = (spriteHeight / columnWidth).toInt();
|
|
||||||
|
|
||||||
int drawStartX = -spriteColumnWidth ~/ 2 + spriteScreenX;
|
// In 1x1 buffer pixels, the width of the sprite is equal to its height
|
||||||
int drawEndX = spriteColumnWidth ~/ 2 + spriteScreenX;
|
int spriteWidth = spriteHeight;
|
||||||
|
|
||||||
|
int drawStartY = -spriteHeight ~/ 2 + buffer.height ~/ 2;
|
||||||
|
if (drawStartY < 0) drawStartY = 0;
|
||||||
|
|
||||||
|
int drawEndY = spriteHeight ~/ 2 + buffer.height ~/ 2;
|
||||||
|
if (drawEndY >= buffer.height) drawEndY = buffer.height - 1;
|
||||||
|
|
||||||
|
int drawStartX = -spriteWidth ~/ 2 + spriteScreenX;
|
||||||
|
int drawEndX = spriteWidth ~/ 2 + spriteScreenX;
|
||||||
|
|
||||||
int clipStartX = math.max(0, drawStartX);
|
int clipStartX = math.max(0, drawStartX);
|
||||||
int clipEndX = math.min(renderWidth - 1, drawEndX);
|
int clipEndX = math.min(buffer.width - 1, drawEndX);
|
||||||
|
|
||||||
|
int safeIndex = entity.spriteIndex.clamp(
|
||||||
|
0,
|
||||||
|
engine.data.sprites.length - 1,
|
||||||
|
);
|
||||||
|
Sprite spritePixels = engine.data.sprites[safeIndex];
|
||||||
|
|
||||||
for (int stripe = clipStartX; stripe < clipEndX; stripe++) {
|
for (int stripe = clipStartX; stripe < clipEndX; stripe++) {
|
||||||
|
// Z-Buffer Check! Only draw the vertical stripe if it's in front of the wall
|
||||||
if (transformY < zBuffer[stripe]) {
|
if (transformY < zBuffer[stripe]) {
|
||||||
double texXDouble = (stripe - drawStartX) * 64 / spriteColumnWidth;
|
int texX = ((stripe - drawStartX) * 64 ~/ spriteWidth).clamp(0, 63);
|
||||||
int texX = texXDouble.toInt().clamp(0, 63);
|
|
||||||
|
|
||||||
double startY = (size.height / 2) - (spriteHeight / 2);
|
double step = 64.0 / spriteHeight;
|
||||||
double stepY = spriteHeight / 64.0;
|
double texPos =
|
||||||
double drawX = stripe * columnWidth;
|
(drawStartY - buffer.height / 2 + spriteHeight / 2) * step;
|
||||||
|
|
||||||
int safeIndex = entity.spriteIndex.clamp(0, sprites.length - 1);
|
for (int y = drawStartY; y < drawEndY; y++) {
|
||||||
Sprite spritePixels = sprites[safeIndex];
|
int texY = texPos.toInt() & 63;
|
||||||
|
texPos += step;
|
||||||
|
|
||||||
for (int ty = 0; ty < 64; ty++) {
|
int colorByte = spritePixels.pixels[texX * 64 + texY];
|
||||||
int colorByte = spritePixels[texX][ty];
|
|
||||||
|
|
||||||
if (colorByte != 255) {
|
if (colorByte != 255) {
|
||||||
double endY = startY + stepY + 0.5;
|
// 255 is transparent
|
||||||
|
buffer.pixels[y * buffer.width + stripe] =
|
||||||
if (endY > 0 && startY < size.height) {
|
ColorPalette.vga32Bit[colorByte];
|
||||||
columnPaint.color = ColorPalette.vga[colorByte];
|
|
||||||
canvas.drawLine(
|
|
||||||
Offset(drawX, startY),
|
|
||||||
Offset(drawX, endY),
|
|
||||||
columnPaint,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
startY += stepY;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _drawTexturedColumn(
|
|
||||||
Canvas canvas,
|
|
||||||
double drawX,
|
|
||||||
double distance,
|
|
||||||
double wallX,
|
|
||||||
int side,
|
|
||||||
Size size,
|
|
||||||
int hitWallId,
|
|
||||||
List<Sprite> textures,
|
|
||||||
double textureOffset,
|
|
||||||
Paint paint,
|
|
||||||
) {
|
|
||||||
if (distance <= 0.01) distance = 0.01;
|
|
||||||
|
|
||||||
double wallHeight = size.height / distance;
|
|
||||||
int drawStart = ((size.height / 2) - (wallHeight / 2)).toInt();
|
|
||||||
|
|
||||||
int texNum;
|
|
||||||
int texX;
|
|
||||||
|
|
||||||
if (hitWallId >= 90) {
|
|
||||||
// DOORS
|
|
||||||
texNum = 98.clamp(0, textures.length - 1);
|
|
||||||
texX = ((wallX - textureOffset) * 64).toInt().clamp(0, 63);
|
|
||||||
} else {
|
|
||||||
// WALLS & PUSHWALLS
|
|
||||||
texNum = ((hitWallId - 1) * 2).clamp(0, textures.length - 2);
|
|
||||||
if (side == 1) texNum += 1;
|
|
||||||
// We apply the modulo % 1.0 to handle negative texture offsets smoothly!
|
|
||||||
texX = (((wallX - textureOffset) % 1.0) * 64).toInt().clamp(0, 63);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (side == 0 && math.cos(player.angle) > 0) texX = 63 - texX;
|
|
||||||
if (side == 1 && math.sin(player.angle) < 0) texX = 63 - texX;
|
|
||||||
|
|
||||||
double startY = drawStart.toDouble();
|
|
||||||
double stepY = wallHeight / 64.0;
|
|
||||||
|
|
||||||
for (int ty = 0; ty < 64; ty++) {
|
|
||||||
int colorByte = textures[texNum][texX][ty];
|
|
||||||
|
|
||||||
paint.color = ColorPalette.vga[colorByte];
|
|
||||||
|
|
||||||
double endY = startY + stepY + 0.5;
|
|
||||||
|
|
||||||
if (endY > 0 && startY < size.height) {
|
|
||||||
canvas.drawLine(Offset(drawX, startY), Offset(drawX, endY), paint);
|
|
||||||
}
|
|
||||||
startY += stepY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool shouldRepaint(RaycasterPainter oldDelegate) => true;
|
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@ class WolfEngine {
|
|||||||
|
|
||||||
// State
|
// State
|
||||||
late Player player;
|
late Player player;
|
||||||
late Level currentLevel;
|
late SpriteMap currentLevel;
|
||||||
late WolfLevel activeLevel;
|
late WolfLevel activeLevel;
|
||||||
List<Entity> entities = [];
|
List<Entity> entities = [];
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ class WolfEngine {
|
|||||||
activeLevel = episode.levels[_currentLevelIndex];
|
activeLevel = episode.levels[_currentLevelIndex];
|
||||||
|
|
||||||
currentLevel = List.generate(64, (y) => List.from(activeLevel.wallGrid[y]));
|
currentLevel = List.generate(64, (y) => List.from(activeLevel.wallGrid[y]));
|
||||||
final Level objectLevel = activeLevel.objectGrid;
|
final SpriteMap objectLevel = activeLevel.objectGrid;
|
||||||
|
|
||||||
doorManager.initDoors(currentLevel);
|
doorManager.initDoors(currentLevel);
|
||||||
|
|
||||||
|
|||||||
@@ -8,4 +8,5 @@ export 'src/engine_input.dart';
|
|||||||
export 'src/managers/door_manager.dart';
|
export 'src/managers/door_manager.dart';
|
||||||
export 'src/managers/pushwall_manager.dart';
|
export 'src/managers/pushwall_manager.dart';
|
||||||
export 'src/player/player.dart';
|
export 'src/player/player.dart';
|
||||||
|
export 'src/rasterizer.dart';
|
||||||
export 'src/wolf_3d_engine_base.dart';
|
export 'src/wolf_3d_engine_base.dart';
|
||||||
|
|||||||
@@ -1,293 +0,0 @@
|
|||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
extension WolfPaletteMatch on Color {
|
|
||||||
/// Finds the index of the closest color in the wolfPalette
|
|
||||||
int findClosestIndex(List<Color> palette) {
|
|
||||||
int closestIndex = 0;
|
|
||||||
double minDistance = double.infinity;
|
|
||||||
|
|
||||||
for (int i = 0; i < palette.length; i++) {
|
|
||||||
final Color pColor = palette[i];
|
|
||||||
|
|
||||||
// Calculate squared Euclidean distance (skipping sqrt for performance)
|
|
||||||
double distance =
|
|
||||||
pow(r - pColor.r, 2).toDouble() +
|
|
||||||
pow(g - pColor.g, 2).toDouble() +
|
|
||||||
pow(b - pColor.b, 2).toDouble();
|
|
||||||
|
|
||||||
if (distance < minDistance) {
|
|
||||||
minDistance = distance;
|
|
||||||
closestIndex = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return closestIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the actual Color object from the palette that matches best
|
|
||||||
Color toWolfColor(List<Color> palette) {
|
|
||||||
return palette[findClosestIndex(palette)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class ColorPalette {
|
|
||||||
static const List<Color> vga = [
|
|
||||||
Color(0xFF000000),
|
|
||||||
Color(0xFF0000AA),
|
|
||||||
Color(0xFF00AA00),
|
|
||||||
Color(0xFF00AAAA),
|
|
||||||
Color(0xFFAA0000),
|
|
||||||
Color(0xFFAA00AA),
|
|
||||||
Color(0xFFAA5500),
|
|
||||||
Color(0xFFAAAAAA),
|
|
||||||
Color(0xFF555555),
|
|
||||||
Color(0xFF5555FF),
|
|
||||||
Color(0xFF55FF55),
|
|
||||||
Color(0xFF55FFFF),
|
|
||||||
Color(0xFFFF5555),
|
|
||||||
Color(0xFFFF55FF),
|
|
||||||
Color(0xFFFFFF55),
|
|
||||||
Color(0xFFFFFFFF),
|
|
||||||
Color(0xFFEEEEEE),
|
|
||||||
Color(0xFFDEDEDE),
|
|
||||||
Color(0xFFD2D2D2),
|
|
||||||
Color(0xFFC2C2C2),
|
|
||||||
Color(0xFFB6B6B6),
|
|
||||||
Color(0xFFAAAAAA),
|
|
||||||
Color(0xFF999999),
|
|
||||||
Color(0xFF8D8D8D),
|
|
||||||
Color(0xFF7D7D7D),
|
|
||||||
Color(0xFF717171),
|
|
||||||
Color(0xFF656565),
|
|
||||||
Color(0xFF555555),
|
|
||||||
Color(0xFF484848),
|
|
||||||
Color(0xFF383838),
|
|
||||||
Color(0xFF2C2C2C),
|
|
||||||
Color(0xFF202020),
|
|
||||||
Color(0xFFFF0000),
|
|
||||||
Color(0xFFEE0000),
|
|
||||||
Color(0xFFE20000),
|
|
||||||
Color(0xFFD60000),
|
|
||||||
Color(0xFFCA0000),
|
|
||||||
Color(0xFFBE0000),
|
|
||||||
Color(0xFFB20000),
|
|
||||||
Color(0xFFA50000),
|
|
||||||
Color(0xFF990000),
|
|
||||||
Color(0xFF890000),
|
|
||||||
Color(0xFF7D0000),
|
|
||||||
Color(0xFF710000),
|
|
||||||
Color(0xFF650000),
|
|
||||||
Color(0xFF590000),
|
|
||||||
Color(0xFF4C0000),
|
|
||||||
Color(0xFF400000),
|
|
||||||
Color(0xFFFFDADA),
|
|
||||||
Color(0xFFFFBABA),
|
|
||||||
Color(0xFFFF9D9D),
|
|
||||||
Color(0xFFFF7D7D),
|
|
||||||
Color(0xFFFF5D5D),
|
|
||||||
Color(0xFFFF4040),
|
|
||||||
Color(0xFFFF2020),
|
|
||||||
Color(0xFFFF0000),
|
|
||||||
Color(0xFFFFAA5D),
|
|
||||||
Color(0xFFFF9940),
|
|
||||||
Color(0xFFFF8920),
|
|
||||||
Color(0xFFFF7900),
|
|
||||||
Color(0xFFE66D00),
|
|
||||||
Color(0xFFCE6100),
|
|
||||||
Color(0xFFB65500),
|
|
||||||
Color(0xFF9D4C00),
|
|
||||||
Color(0xFFFFFFDA),
|
|
||||||
Color(0xFFFFFFBA),
|
|
||||||
Color(0xFFFFFF9D),
|
|
||||||
Color(0xFFFFFF7D),
|
|
||||||
Color(0xFFFFFA5D),
|
|
||||||
Color(0xFFFFF640),
|
|
||||||
Color(0xFFFFF620),
|
|
||||||
Color(0xFFFFF600),
|
|
||||||
Color(0xFFE6DA00),
|
|
||||||
Color(0xFFCEC600),
|
|
||||||
Color(0xFFB6AE00),
|
|
||||||
Color(0xFF9D9D00),
|
|
||||||
Color(0xFF858500),
|
|
||||||
Color(0xFF716D00),
|
|
||||||
Color(0xFF595500),
|
|
||||||
Color(0xFF404000),
|
|
||||||
Color(0xFFD2FF5D),
|
|
||||||
Color(0xFFC6FF40),
|
|
||||||
Color(0xFFB6FF20),
|
|
||||||
Color(0xFFA1FF00),
|
|
||||||
Color(0xFF91E600),
|
|
||||||
Color(0xFF81CE00),
|
|
||||||
Color(0xFF75B600),
|
|
||||||
Color(0xFF619D00),
|
|
||||||
Color(0xFFDAFFDA),
|
|
||||||
Color(0xFFBEFFBA),
|
|
||||||
Color(0xFF9DFF9D),
|
|
||||||
Color(0xFF81FF7D),
|
|
||||||
Color(0xFF61FF5D),
|
|
||||||
Color(0xFF40FF40),
|
|
||||||
Color(0xFF20FF20),
|
|
||||||
Color(0xFF00FF00),
|
|
||||||
Color(0xFF00FF00),
|
|
||||||
Color(0xFF00EE00),
|
|
||||||
Color(0xFF00E200),
|
|
||||||
Color(0xFF00D600),
|
|
||||||
Color(0xFF04CA00),
|
|
||||||
Color(0xFF04BE00),
|
|
||||||
Color(0xFF04B200),
|
|
||||||
Color(0xFF04A500),
|
|
||||||
Color(0xFF049900),
|
|
||||||
Color(0xFF048900),
|
|
||||||
Color(0xFF047D00),
|
|
||||||
Color(0xFF047100),
|
|
||||||
Color(0xFF046500),
|
|
||||||
Color(0xFF045900),
|
|
||||||
Color(0xFF044C00),
|
|
||||||
Color(0xFF044000),
|
|
||||||
Color(0xFFDAFFFF),
|
|
||||||
Color(0xFFBAFFFF),
|
|
||||||
Color(0xFF9DFFFF),
|
|
||||||
Color(0xFF7DFFFA),
|
|
||||||
Color(0xFF5DFFFF),
|
|
||||||
Color(0xFF40FFFF),
|
|
||||||
Color(0xFF20FFFF),
|
|
||||||
Color(0xFF00FFFF),
|
|
||||||
Color(0xFF00E6E6),
|
|
||||||
Color(0xFF00CECE),
|
|
||||||
Color(0xFF00B6B6),
|
|
||||||
Color(0xFF009D9D),
|
|
||||||
Color(0xFF008585),
|
|
||||||
Color(0xFF007171),
|
|
||||||
Color(0xFF005959),
|
|
||||||
Color(0xFF004040),
|
|
||||||
Color(0xFF5DBEFF),
|
|
||||||
Color(0xFF40B2FF),
|
|
||||||
Color(0xFF20AAFF),
|
|
||||||
Color(0xFF009DFF),
|
|
||||||
Color(0xFF008DE6),
|
|
||||||
Color(0xFF007DCE),
|
|
||||||
Color(0xFF006DB6),
|
|
||||||
Color(0xFF005D9D),
|
|
||||||
Color(0xFFDADADA),
|
|
||||||
Color(0xFFBABEFF),
|
|
||||||
Color(0xFF9D9DFF),
|
|
||||||
Color(0xFF7D81FF),
|
|
||||||
Color(0xFF5D61FF),
|
|
||||||
Color(0xFF4040FF),
|
|
||||||
Color(0xFF2024FF),
|
|
||||||
Color(0xFF0004FF),
|
|
||||||
Color(0xFF0000FF),
|
|
||||||
Color(0xFF0000EE),
|
|
||||||
Color(0xFF0000E2),
|
|
||||||
Color(0xFF0000D6),
|
|
||||||
Color(0xFF0000CA),
|
|
||||||
Color(0xFF0000BE),
|
|
||||||
Color(0xFF0000B2),
|
|
||||||
Color(0xFF0000A5),
|
|
||||||
Color(0xFF000099),
|
|
||||||
Color(0xFF000089),
|
|
||||||
Color(0xFF00007D),
|
|
||||||
Color(0xFF000071),
|
|
||||||
Color(0xFF000065),
|
|
||||||
Color(0xFF000059),
|
|
||||||
Color(0xFF00004C),
|
|
||||||
Color(0xFF000040),
|
|
||||||
Color(0xFF282828),
|
|
||||||
Color(0xFFFFE234),
|
|
||||||
Color(0xFFFFD624),
|
|
||||||
Color(0xFFFFCE18),
|
|
||||||
Color(0xFFFFC208),
|
|
||||||
Color(0xFFFFB600),
|
|
||||||
Color(0xFFB620FF),
|
|
||||||
Color(0xFFAA00FF),
|
|
||||||
Color(0xFF9900E6),
|
|
||||||
Color(0xFF8100CE),
|
|
||||||
Color(0xFF7500B6),
|
|
||||||
Color(0xFF61009D),
|
|
||||||
Color(0xFF500085),
|
|
||||||
Color(0xFF440071),
|
|
||||||
Color(0xFF340059),
|
|
||||||
Color(0xFF280040),
|
|
||||||
Color(0xFFFFDAFF),
|
|
||||||
Color(0xFFFFBAFF),
|
|
||||||
Color(0xFFFF9DFF),
|
|
||||||
Color(0xFFFF7DFF),
|
|
||||||
Color(0xFFFF5DFF),
|
|
||||||
Color(0xFFFF40FF),
|
|
||||||
Color(0xFFFF20FF),
|
|
||||||
Color(0xFFFF00FF),
|
|
||||||
Color(0xFFE200E6),
|
|
||||||
Color(0xFFCA00CE),
|
|
||||||
Color(0xFFB600B6),
|
|
||||||
Color(0xFF9D009D),
|
|
||||||
Color(0xFF850085),
|
|
||||||
Color(0xFF6D0071),
|
|
||||||
Color(0xFF590059),
|
|
||||||
Color(0xFF400040),
|
|
||||||
Color(0xFFFFEADE),
|
|
||||||
Color(0xFFFFE2D2),
|
|
||||||
Color(0xFFFFDAC6),
|
|
||||||
Color(0xFFFFD6BE),
|
|
||||||
Color(0xFFFFCEB2),
|
|
||||||
Color(0xFFFFC6A5),
|
|
||||||
Color(0xFFFFBE9D),
|
|
||||||
Color(0xFFFFBA91),
|
|
||||||
Color(0xFFFFB281),
|
|
||||||
Color(0xFFFA571F),
|
|
||||||
Color(0xFFFF9D61),
|
|
||||||
Color(0xFFF2955D),
|
|
||||||
Color(0xFFEA8D59),
|
|
||||||
Color(0xFFDE8955),
|
|
||||||
Color(0xFFD28150),
|
|
||||||
Color(0xFFCA7D4C),
|
|
||||||
Color(0xFFBE7948),
|
|
||||||
Color(0xFFB67144),
|
|
||||||
Color(0xFFAA6940),
|
|
||||||
Color(0xFFA1653C),
|
|
||||||
Color(0xFF9D6138),
|
|
||||||
Color(0xFF915D34),
|
|
||||||
Color(0xFF895930),
|
|
||||||
Color(0xFF81502C),
|
|
||||||
Color(0xFF754C28),
|
|
||||||
Color(0xFF6D4824),
|
|
||||||
Color(0xFF5D4020),
|
|
||||||
Color(0xFF553C1C),
|
|
||||||
Color(0xFF483818),
|
|
||||||
Color(0xFF403018),
|
|
||||||
Color(0xFF382C14),
|
|
||||||
Color(0xFF28200C),
|
|
||||||
Color(0xFF610065),
|
|
||||||
Color(0xFF006565),
|
|
||||||
Color(0xFF006161),
|
|
||||||
Color(0xFF00001C),
|
|
||||||
Color(0xFF00002C),
|
|
||||||
Color(0xFF302410),
|
|
||||||
Color(0xFF480048),
|
|
||||||
Color(0xFF500050),
|
|
||||||
Color(0xFF000034),
|
|
||||||
Color(0xFF1C1C1C),
|
|
||||||
Color(0xFF4C4C4C),
|
|
||||||
Color(0xFF5D5D5D),
|
|
||||||
Color(0xFF404040),
|
|
||||||
Color(0xFF303030),
|
|
||||||
Color(0xFF343434),
|
|
||||||
Color(0xFFDAF6F6),
|
|
||||||
Color(0xFFBAEAEA),
|
|
||||||
Color(0xFF9DDEDE),
|
|
||||||
Color(0xFF75CACA),
|
|
||||||
Color(0xFF48C2C2),
|
|
||||||
Color(0xFF20B6B6),
|
|
||||||
Color(0xFF20B2B2),
|
|
||||||
Color(0xFF00A5A5),
|
|
||||||
Color(0xFF009999),
|
|
||||||
Color(0xFF008D8D),
|
|
||||||
Color(0xFF008585),
|
|
||||||
Color(0xFF007D7D),
|
|
||||||
Color(0xFF007979),
|
|
||||||
Color(0xFF007575),
|
|
||||||
Color(0xFF007171),
|
|
||||||
Color(0xFF006D6D),
|
|
||||||
Color(0xFF990089),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
|
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
|
||||||
import 'package:wolf_3d_renderer/color_palette.dart';
|
|
||||||
|
|
||||||
class WeaponPainter extends CustomPainter {
|
class WeaponPainter extends CustomPainter {
|
||||||
final Sprite? sprite;
|
final Sprite? sprite;
|
||||||
@@ -24,11 +23,11 @@ class WeaponPainter extends CustomPainter {
|
|||||||
|
|
||||||
for (int x = 0; x < 64; x++) {
|
for (int x = 0; x < 64; x++) {
|
||||||
for (int y = 0; y < 64; y++) {
|
for (int y = 0; y < 64; y++) {
|
||||||
int colorByte = sprite![x][y];
|
int colorByte = sprite!.pixels[x * 64 + y];
|
||||||
|
|
||||||
if (colorByte != 255) {
|
if (colorByte != 255) {
|
||||||
// 255 is our transparent magenta
|
// 255 is our transparent magenta
|
||||||
_paint.color = ColorPalette.vga[colorByte];
|
_paint.color = Color(ColorPalette.vga32Bit[colorByte]);
|
||||||
|
|
||||||
canvas.drawRect(
|
canvas.drawRect(
|
||||||
Rect.fromLTWH(
|
Rect.fromLTWH(
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'dart:math' as math;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
@@ -6,7 +6,6 @@ import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
|
|||||||
import 'package:wolf_3d_engine/wolf_3d_engine.dart';
|
import 'package:wolf_3d_engine/wolf_3d_engine.dart';
|
||||||
import 'package:wolf_3d_input/wolf_3d_input.dart';
|
import 'package:wolf_3d_input/wolf_3d_input.dart';
|
||||||
import 'package:wolf_3d_renderer/hud.dart';
|
import 'package:wolf_3d_renderer/hud.dart';
|
||||||
import 'package:wolf_3d_renderer/raycast_painter.dart';
|
|
||||||
import 'package:wolf_3d_renderer/weapon_painter.dart';
|
import 'package:wolf_3d_renderer/weapon_painter.dart';
|
||||||
|
|
||||||
class WolfRenderer extends StatefulWidget {
|
class WolfRenderer extends StatefulWidget {
|
||||||
@@ -29,22 +28,23 @@ class WolfRenderer extends StatefulWidget {
|
|||||||
|
|
||||||
class _WolfRendererState extends State<WolfRenderer>
|
class _WolfRendererState extends State<WolfRenderer>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
// 1. The input reader
|
|
||||||
final WolfInput inputManager = WolfInput();
|
final WolfInput inputManager = WolfInput();
|
||||||
|
|
||||||
// 2. The central brain of the game
|
|
||||||
late final WolfEngine engine;
|
late final WolfEngine engine;
|
||||||
|
|
||||||
late Ticker _gameLoop;
|
late Ticker _gameLoop;
|
||||||
final FocusNode _focusNode = FocusNode();
|
final FocusNode _focusNode = FocusNode();
|
||||||
|
|
||||||
final double fov = math.pi / 3;
|
// --- NEW RASTERIZER STATE ---
|
||||||
|
// Lock the internal rendering resolution to the classic 320x200
|
||||||
|
final FrameBuffer _frameBuffer = FrameBuffer(320, 200);
|
||||||
|
final SoftwareRasterizer _rasterizer = SoftwareRasterizer();
|
||||||
|
|
||||||
|
ui.Image? _renderedFrame;
|
||||||
|
bool _isRendering = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
// Initialize the engine and hand over all the data and dependencies
|
|
||||||
engine = WolfEngine(
|
engine = WolfEngine(
|
||||||
data: widget.data,
|
data: widget.data,
|
||||||
difficulty: widget.difficulty,
|
difficulty: widget.difficulty,
|
||||||
@@ -57,33 +57,56 @@ class _WolfRendererState extends State<WolfRenderer>
|
|||||||
|
|
||||||
engine.init();
|
engine.init();
|
||||||
|
|
||||||
// Start the loop
|
|
||||||
_gameLoop = createTicker(_tick)..start();
|
_gameLoop = createTicker(_tick)..start();
|
||||||
_focusNode.requestFocus();
|
_focusNode.requestFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- ORCHESTRATOR ---
|
|
||||||
void _tick(Duration elapsed) {
|
void _tick(Duration elapsed) {
|
||||||
// 1. Read the keyboard state
|
if (!engine.isInitialized) return;
|
||||||
inputManager.update();
|
|
||||||
|
|
||||||
// 2. Let the engine do all the math, physics, collision, and logic!
|
inputManager.update();
|
||||||
engine.tick(elapsed, inputManager.currentInput);
|
engine.tick(elapsed, inputManager.currentInput);
|
||||||
|
|
||||||
// 3. Force a UI repaint using the newly updated engine state
|
// Only start rendering a new frame if the previous one is finished.
|
||||||
setState(() {});
|
// This prevents memory leaks and stuttering on lower-end hardware!
|
||||||
|
if (!_isRendering) {
|
||||||
|
_isRendering = true;
|
||||||
|
|
||||||
|
// 1. Crunch the math and fill the 1D memory array
|
||||||
|
_rasterizer.render(engine, _frameBuffer);
|
||||||
|
|
||||||
|
// 2. Convert the raw Uint32List memory into a Flutter ui.Image
|
||||||
|
ui.decodeImageFromPixels(
|
||||||
|
// Extract the underlying byte buffer from our 32-bit integer array
|
||||||
|
_frameBuffer.pixels.buffer.asUint8List(),
|
||||||
|
_frameBuffer.width,
|
||||||
|
_frameBuffer.height,
|
||||||
|
ui.PixelFormat.rgba8888, // Standard 32-bit color format
|
||||||
|
(ui.Image image) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
// ALWAYS dispose the old frame before assigning the new one
|
||||||
|
// to prevent massive memory leaks on the GPU!
|
||||||
|
_renderedFrame?.dispose();
|
||||||
|
_renderedFrame = image;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_isRendering = false;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_gameLoop.dispose();
|
_gameLoop.dispose();
|
||||||
_focusNode.dispose();
|
_focusNode.dispose();
|
||||||
|
_renderedFrame?.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// Wait for the engine to finish parsing the level
|
|
||||||
if (!engine.isInitialized) {
|
if (!engine.isInitialized) {
|
||||||
return const Center(child: CircularProgressIndicator(color: Colors.teal));
|
return const Center(child: CircularProgressIndicator(color: Colors.teal));
|
||||||
}
|
}
|
||||||
@@ -104,30 +127,18 @@ class _WolfRendererState extends State<WolfRenderer>
|
|||||||
aspectRatio: 16 / 10,
|
aspectRatio: 16 / 10,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
// --- 3D WORLD ---
|
// --- 3D WORLD (PIXEL BUFFER) ---
|
||||||
CustomPaint(
|
CustomPaint(
|
||||||
size: Size(
|
size: Size(
|
||||||
constraints.maxWidth,
|
constraints.maxWidth,
|
||||||
constraints.maxHeight,
|
constraints.maxHeight,
|
||||||
),
|
),
|
||||||
painter: RaycasterPainter(
|
painter: BufferPainter(_renderedFrame),
|
||||||
// Read state directly from the engine
|
|
||||||
map: engine.currentLevel,
|
|
||||||
textures: widget.data.walls,
|
|
||||||
player: engine.player,
|
|
||||||
fov: fov,
|
|
||||||
doorOffsets: engine.doorManager
|
|
||||||
.getOffsetsForRenderer(),
|
|
||||||
entities: engine.entities,
|
|
||||||
sprites: widget.data.sprites,
|
|
||||||
activePushwall:
|
|
||||||
engine.pushwallManager.activePushwall,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
|
||||||
// --- FIRST PERSON WEAPON ---
|
// --- FIRST PERSON WEAPON ---
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: -20,
|
bottom: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
child: Center(
|
child: Center(
|
||||||
@@ -171,8 +182,6 @@ class _WolfRendererState extends State<WolfRenderer>
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// --- UI ---
|
|
||||||
Hud(player: engine.player),
|
Hud(player: engine.player),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -180,3 +189,34 @@ class _WolfRendererState extends State<WolfRenderer>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- DEAD SIMPLE PAINTER ---
|
||||||
|
// It literally just stretches the 320x200 image to fill the screen
|
||||||
|
class BufferPainter extends CustomPainter {
|
||||||
|
final ui.Image? frame;
|
||||||
|
|
||||||
|
BufferPainter(this.frame);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
if (frame == null) return;
|
||||||
|
|
||||||
|
// FilterQuality.none guarantees the classic, chunky, un-blurred pixels!
|
||||||
|
final Paint paint = Paint()..filterQuality = FilterQuality.none;
|
||||||
|
|
||||||
|
final Rect srcRect = Rect.fromLTWH(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
frame!.width.toDouble(),
|
||||||
|
frame!.height.toDouble(),
|
||||||
|
);
|
||||||
|
final Rect dstRect = Rect.fromLTWH(0, 0, size.width, size.height);
|
||||||
|
|
||||||
|
canvas.drawImageRect(frame!, srcRect, dstRect, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant BufferPainter oldDelegate) {
|
||||||
|
return oldDelegate.frame != frame;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user