diff --git a/lib/main.dart b/lib/main.dart index 7ce7514..324a58b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -133,11 +133,22 @@ class _PlayerState extends State { return KeyEventResult.ignored; }, - child: CustomPaint( - painter: PlayerPainter( - offset: playerPosition, - delta: playerDelta, - ), + child: Stack( + children: [ + CustomPaint( + painter: RayPainter( + playerAngle: playerAngle, + offset: playerPosition, + delta: playerDelta, + ), + ), + CustomPaint( + painter: PlayerPainter( + offset: playerPosition, + delta: playerDelta, + ), + ), + ], ), ); } @@ -176,6 +187,240 @@ class PlayerPainter extends CustomPainter { bool shouldRebuildSemantics(covariant oldDelegate) => false; } +class RayPainter extends CustomPainter { + final double playerAngle; + final Offset offset; + final Offset delta; + + const new({ + required this.playerAngle, + required this.offset, + required this.delta, + }); + + @override + void paint(Canvas canvas, Size size) { + const double dr = math.pi / 180; + + int dof; + int mx; + int my; + int mapPoint; + + double yo = 1; + double xo = 1; + + final Offset playerCenter = offset + const Offset(4, 4); + + double rayX = 0; + double rayY = 0; + double disT; + + double rayAngle = delta.direction - (dr * 30); + + if (rayAngle < 0) { + rayAngle += 2 * math.pi; + } + if (rayAngle > 2 * math.pi) { + rayAngle -= 2 * math.pi; + } + + int r; + for (r = 0; r < 60; r++) { + // * --- Check Horizontal Lines --- + dof = 0; + double distanceHor = 1000000; + double hx = playerCenter.dx; + double hy = playerCenter.dy; + + // Negative inverse of tangent + final double aTan = -1.0 / math.tan(rayAngle); + + // ? Looking up (ray angle > π) + if (rayAngle > math.pi) { + rayY = ((playerCenter.dy / mapSize).floorToDouble() * mapSize) - 0.0001; + + rayX = (playerCenter.dy - rayY) * aTan + playerCenter.dx; + + yo = -mapSize / 1; + xo = -yo * aTan; + } + + // ? Looking down + if (rayAngle < math.pi) { + rayY = + ((playerCenter.dy / mapSize).floorToDouble() * mapSize) + mapSize; + + rayX = (playerCenter.dy - rayY) * aTan + playerCenter.dx; + + yo = mapSize / 1; + xo = -yo * aTan; + } + + // ? Looking straight left or right + if (rayAngle == 0.0 || rayAngle == math.pi) { + // It's impossible to ever hit a horizontal line + rayX = playerCenter.dx; + rayY = playerCenter.dy; + dof = 8; + } + + int mv = 0; + int mh = 0; + + while (dof < 8) { + mx = rayX ~/ mapSize; + my = rayY ~/ mapSize; + + mapPoint = my * mapX + mx; + + if (mapPoint > 0 && mapPoint < mapX * mapY && map[mapPoint] > 0) { + // We hit a wall! + + mh = map[mapPoint]; + + hx = rayX; + hy = rayY; + distanceHor = dist( + playerCenter.dx, + playerCenter.dy, + hx, + hy, + rayAngle, + ); + + dof = 8; + } else { + rayX += xo; + rayY += yo; + dof += 1; + } + } + + // * --- Check Vertical Lines --- + dof = 0; + double distanceVert = 1000000; + double vx = playerCenter.dx; + double vy = playerCenter.dy; + + // Negative of tangent + final double nTan = -math.tan(rayAngle); + + const double pi2 = math.pi / 2; + const double pi3 = 3 * math.pi / 2; + + // ? Looking left + if (rayAngle > pi2 && rayAngle < pi3) { + rayX = ((playerCenter.dx / mapSize).floorToDouble() * mapSize) - 0.0001; + rayY = (playerCenter.dx - rayX) * nTan + playerCenter.dy; + xo = -mapSize / 1; + yo = -xo * nTan; + } + + // ? Looking right + if (rayAngle < pi2 || rayAngle > pi3) { + rayX = + ((playerCenter.dx / mapSize).floorToDouble() * mapSize) + mapSize; + rayY = (playerCenter.dx - rayX) * nTan + playerCenter.dy; + xo = mapSize / 1; + yo = -xo * nTan; + } + + // ? Looking straight up or down + if (rayAngle == pi2 || rayAngle == pi3) { + rayX = playerCenter.dx; + rayY = playerCenter.dy; + dof = 8; + } + + while (dof < 8) { + mx = rayX ~/ mapSize; + my = rayY ~/ mapSize; + + mapPoint = my * mapX + mx; + + if (mapPoint > 0 && mapPoint < mapX * mapY && map[mapPoint] > 0) { + // We hit a wall! + + mv = map[mapPoint]; + + // Save the values and calculate the distance from the player + vx = rayX; + vy = rayY; + distanceVert = dist( + playerCenter.dx, + playerCenter.dy, + vx, + vy, + rayAngle, + ); + + dof = 8; // Break the loop - no need to calculate more. + } else { + // If we didn't hit a wall, we check the next horizontal line by adding + // the x and y offset. + rayX += xo; + rayY += yo; + dof += 1; + } + } + + Color wallColor = Colors.transparent; + + if (distanceVert < distanceHor) { + rayX = vx; + rayY = vy; + disT = distanceVert; + // Vertical walls are drawn a little brighter than horizontal walls + wallColor = const Color.fromRGBO(230, 0, 0, 1); + + if (mv == 2) { + wallColor = const Color.fromRGBO(0, 179, 0, 1); + } + } + + if (distanceHor < distanceVert) { + rayX = hx; + rayY = hy; + disT = distanceHor; + // Horizontal walls are drawn a little darker than vertical walls + wallColor = const Color.fromRGBO(179, 0, 0, 1); + + if (mh == 2) { + wallColor = const Color.fromRGBO(0, 102, 0, 1); + } + } + + canvas.drawLine( + playerCenter, + Offset(rayX, rayY), + Paint() + ..color = wallColor + ..isAntiAlias = false + ..strokeWidth = 1.5, + ); + + rayAngle += dr; + + if (rayAngle < 0) { + rayAngle += 2 * math.pi; + } + if (rayAngle > 2 * math.pi) { + rayAngle -= 2 * math.pi; + } + } + } + + double dist(double ax, double ay, double bx, double by, double ang) { + return math.sqrt((bx - ax) * (bx - ax) + (by - ay) * (by - ay)); + } + + @override + bool shouldRepaint(covariant oldDelegate) => false; + @override + bool shouldRebuildSemantics(covariant oldDelegate) => false; +} + // * Map class Map extends StatelessWidget { const new({super.key}); @@ -214,6 +459,12 @@ class MapPainter extends CustomPainter { void paint(Canvas canvas, Size size) { final paintWhite = Paint()..color = Colors.white; final paintBlack = Paint()..color = Colors.black; + final paintGrey = Paint()..color = Colors.grey; + + canvas.drawRect( + Rect.fromLTWH(0, 0, tileSize * mapX, tileSize * mapY), + paintGrey, + ); for (int i = 0; i < mapData.length; i++) { final double x = (i % mapX) * tileSize;