Refactor rendering architecture and replace rasterizer with renderer
- Introduced SoftwareRenderer as a pixel-accurate software rendering backend. - Removed the obsolete wolf_3d_rasterizer.dart file. - Created a new wolf_3d_renderer.dart file to centralize rendering exports. - Updated tests to accommodate the new rendering structure, including pushwall and projection sampling tests. - Modified the WolfAsciiRenderer and WolfFlutterRenderer to utilize the new SoftwareRenderer. - Enhanced enemy spawn tests to include new enemy states. Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import 'package:test/test.dart';
|
||||
import 'package:wolf_3d_dart/src/rendering/renderer_backend.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
||||
|
||||
void main() {
|
||||
group('Renderer wall texture sampling', () {
|
||||
test('anchors wall texel sampling to projection height center', () {
|
||||
final renderer = _TestRenderer(customProjectionViewHeight: 40);
|
||||
renderer.configureViewGeometry(width: 64, height: 64, viewHeight: 20);
|
||||
|
||||
// With sceneHeight=40 and columnHeight=20, projected wall spans y=10..30.
|
||||
// Top pixel should sample from top texel row.
|
||||
expect(renderer.wallTexY(10, 20), 0);
|
||||
|
||||
// Bottom visible pixel should sample close to bottom texel row.
|
||||
expect(renderer.wallTexY(29, 20), inInclusiveRange(60, 63));
|
||||
});
|
||||
|
||||
test('keeps legacy behavior when projection height equals view height', () {
|
||||
final renderer = _TestRenderer(customProjectionViewHeight: 20);
|
||||
renderer.configureViewGeometry(width: 64, height: 64, viewHeight: 20);
|
||||
|
||||
// With sceneHeight=viewHeight=20 and columnHeight=20, top starts at y=0.
|
||||
expect(renderer.wallTexY(0, 20), 0);
|
||||
expect(renderer.wallTexY(19, 20), inInclusiveRange(60, 63));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class _TestRenderer extends RendererBackend<FrameBuffer> {
|
||||
_TestRenderer({required this.customProjectionViewHeight});
|
||||
|
||||
final int customProjectionViewHeight;
|
||||
|
||||
@override
|
||||
int get projectionViewHeight => customProjectionViewHeight;
|
||||
|
||||
void configureViewGeometry({
|
||||
required int width,
|
||||
required int height,
|
||||
required int viewHeight,
|
||||
}) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.viewHeight = viewHeight;
|
||||
}
|
||||
|
||||
@override
|
||||
void prepareFrame(WolfEngine engine) {}
|
||||
|
||||
@override
|
||||
void drawWallColumn(
|
||||
int x,
|
||||
int drawStart,
|
||||
int drawEnd,
|
||||
int columnHeight,
|
||||
Sprite texture,
|
||||
int texX,
|
||||
double perpWallDist,
|
||||
int side,
|
||||
) {}
|
||||
|
||||
@override
|
||||
void drawSpriteStripe(
|
||||
int stripeX,
|
||||
int drawStartY,
|
||||
int drawEndY,
|
||||
int spriteHeight,
|
||||
Sprite texture,
|
||||
int texX,
|
||||
double transformY,
|
||||
) {}
|
||||
|
||||
@override
|
||||
void drawWeapon(WolfEngine engine) {}
|
||||
|
||||
@override
|
||||
void drawHud(WolfEngine engine) {}
|
||||
|
||||
@override
|
||||
FrameBuffer finalizeFrame() {
|
||||
return FrameBuffer(1, 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:test/test.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_input.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_renderer.dart';
|
||||
|
||||
void main() {
|
||||
group('Pushwall rendering', () {
|
||||
test('active pushwall occludes the wall behind it while sliding', () {
|
||||
final wallGrid = _buildGrid();
|
||||
final objectGrid = _buildGrid();
|
||||
|
||||
_fillBoundaries(wallGrid, 2);
|
||||
objectGrid[2][2] = MapObject.playerEast;
|
||||
|
||||
wallGrid[2][4] = 1;
|
||||
objectGrid[2][4] = MapObject.pushwallTrigger;
|
||||
|
||||
wallGrid[2][6] = 2;
|
||||
|
||||
final engine = WolfEngine(
|
||||
data: WolfensteinData(
|
||||
version: GameVersion.shareware,
|
||||
walls: [
|
||||
_solidSprite(1),
|
||||
_solidSprite(1),
|
||||
_solidSprite(2),
|
||||
_solidSprite(2),
|
||||
],
|
||||
sprites: List.generate(436, (_) => _solidSprite(255)),
|
||||
sounds: [],
|
||||
adLibSounds: [],
|
||||
music: [],
|
||||
vgaImages: [],
|
||||
episodes: [
|
||||
Episode(
|
||||
name: 'Episode 1',
|
||||
levels: [
|
||||
WolfLevel(
|
||||
name: 'Test Level',
|
||||
wallGrid: wallGrid,
|
||||
objectGrid: objectGrid,
|
||||
musicIndex: 0,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
difficulty: Difficulty.medium,
|
||||
startingEpisode: 0,
|
||||
frameBuffer: FrameBuffer(64, 64),
|
||||
input: CliInput(),
|
||||
onGameWon: () {},
|
||||
);
|
||||
|
||||
engine.init();
|
||||
|
||||
final pushwall = engine.pushwallManager.pushwalls['4,2']!;
|
||||
pushwall
|
||||
..dirX = 1
|
||||
..dirY = 0
|
||||
..offset = 0.5;
|
||||
engine.pushwallManager.activePushwall = pushwall;
|
||||
|
||||
final frame = SoftwareRenderer().render(engine);
|
||||
final centerIndex =
|
||||
(frame.height ~/ 2) * frame.width + (frame.width ~/ 2);
|
||||
|
||||
expect(frame.pixels[centerIndex], ColorPalette.vga32Bit[1]);
|
||||
expect(frame.pixels[centerIndex], isNot(ColorPalette.vga32Bit[2]));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
SpriteMap _buildGrid() => List.generate(64, (_) => List.filled(64, 0));
|
||||
|
||||
void _fillBoundaries(SpriteMap grid, int wallId) {
|
||||
for (int i = 0; i < 64; i++) {
|
||||
grid[0][i] = wallId;
|
||||
grid[63][i] = wallId;
|
||||
grid[i][0] = wallId;
|
||||
grid[i][63] = wallId;
|
||||
}
|
||||
}
|
||||
|
||||
Sprite _solidSprite(int colorIndex) {
|
||||
return Sprite(Uint8List.fromList(List.filled(64 * 64, colorIndex)));
|
||||
}
|
||||
Reference in New Issue
Block a user