Last active
November 30, 2023 08:27
-
-
Save nosmirck/a6143a9af97d768d659ef41bdd051cfa to your computer and use it in GitHub Desktop.
Widgetstein
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:math'; | |
import 'package:flutter/material.dart'; | |
void main() { | |
runApp(MyApp()); | |
} | |
class MyApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'Widgetstein', | |
theme: ThemeData( | |
primarySwatch: Colors.blue, | |
), | |
home: MyHomePage(title: 'Widgetstein'), | |
); | |
} | |
} | |
class MyHomePage extends StatefulWidget { | |
MyHomePage({Key? key, required this.title}) : super(key: key); | |
final String title; | |
@override | |
_MyHomePageState createState() => _MyHomePageState(); | |
} | |
class _MyHomePageState extends State<MyHomePage> { | |
final colors = [Colors.white, Colors.blueGrey]; | |
final map = [ | |
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], | |
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], | |
[1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1], | |
[1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1], | |
[1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1], | |
[1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1], | |
[1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1], | |
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], | |
[1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1], | |
[1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1], | |
[1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1], | |
[1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1], | |
[1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1], | |
[1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1], | |
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], | |
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], | |
]; | |
final walls = <Map<String, double>>[]; | |
double playerX = 5.5; | |
double playerY = 7.6; | |
double playerAngle = 4.2; | |
double resolution = 0.2; | |
double fov = pi / 3.0; | |
@override | |
void didChangeDependencies() { | |
super.didChangeDependencies(); | |
update(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar( | |
title: Text(widget.title), | |
), | |
body: Column( | |
children: [ | |
Expanded( | |
child: Stack( | |
children: [ | |
LayoutBuilder( | |
builder: (context, constraints) { | |
final size = | |
min(constraints.maxHeight, constraints.maxWidth) / | |
map.length; | |
return Column( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: map | |
.map( | |
(e) => Row( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: e.map( | |
(e) { | |
return Container( | |
width: size, | |
height: size, | |
decoration: BoxDecoration( | |
color: colors[e], | |
border: Border.all( | |
color: Colors.black, | |
width: 0.1, | |
), | |
), | |
); | |
}, | |
).toList(), | |
), | |
) | |
.toList(), | |
); | |
}, | |
), | |
LayoutBuilder( | |
builder: (context, constraints) { | |
final size = | |
min(constraints.maxHeight, constraints.maxWidth); | |
final playerSize = size / map.length * 0.2; | |
return Center( | |
child: Container( | |
padding: EdgeInsets.zero, | |
margin: EdgeInsets.zero, | |
height: size, | |
width: size, | |
color: Colors.greenAccent.withOpacity(0.1), | |
alignment: Alignment( | |
mapNum( | |
playerX, 0.0, map.length.toDouble(), -1.0, 1.0), | |
mapNum(playerY, 0.0, map.length.toDouble(), -1.0, | |
1.0)), | |
child: SizedBox( | |
width: playerSize * 2, | |
height: playerSize * 2, | |
child: Transform.rotate( | |
angle: playerAngle, | |
child: Stack( | |
alignment: Alignment.center, | |
clipBehavior: Clip.none, | |
children: [ | |
...walls.map( | |
(e) => Positioned( | |
left: playerSize, | |
child: Transform.rotate( | |
alignment: Alignment.centerLeft, | |
angle: e['angle']!, | |
child: SizedBox( | |
width: | |
e['distance']! * size / map.length, | |
height: 2, | |
child: Container( | |
color: Colors.blue, | |
), | |
), | |
), | |
), | |
), | |
Positioned( | |
left: playerSize, | |
child: SizedBox( | |
width: playerSize * 2, | |
height: 2, | |
child: Container( | |
color: Colors.green, | |
), | |
), | |
), | |
SizedBox( | |
width: playerSize, | |
height: playerSize, | |
child: ClipOval( | |
child: Container( | |
color: Colors.redAccent, | |
), | |
), | |
), | |
], | |
), | |
), | |
), | |
), | |
); | |
}, | |
), | |
Positioned( | |
top: 0, | |
child: Column( | |
children: [ | |
Text('Player Angle'), | |
Slider( | |
value: playerAngle, | |
onChanged: (value) { | |
playerAngle = value; | |
update(); | |
}, | |
min: -3 * pi, | |
max: 3 * pi, | |
), | |
Text('Player X'), | |
Slider( | |
value: playerX, | |
onChanged: (value) { | |
playerX = value; | |
update(); | |
}, | |
min: 0.0, | |
max: map.length.toDouble(), | |
), | |
Text('Player Y'), | |
Slider( | |
value: playerY, | |
onChanged: (value) { | |
playerY = value; | |
update(); | |
}, | |
min: 0.0, | |
max: map.length.toDouble(), | |
), | |
], | |
), | |
) | |
], | |
), | |
), | |
Expanded( | |
child: Stack( | |
children: [ | |
Column( | |
children: [ | |
Expanded( | |
child: Container( | |
decoration: BoxDecoration( | |
gradient: LinearGradient( | |
begin: Alignment.topCenter, | |
end: Alignment.bottomCenter, | |
colors: [ | |
Colors.lightBlue, | |
Colors.blue.shade900, | |
], | |
), | |
), | |
), | |
), | |
Expanded( | |
child: Container( | |
decoration: BoxDecoration( | |
gradient: LinearGradient( | |
begin: Alignment.bottomCenter, | |
end: Alignment.topCenter, | |
colors: [ | |
Colors.lightGreen, | |
Colors.green.shade900, | |
], | |
), | |
), | |
), | |
), | |
], | |
), | |
LayoutBuilder(builder: (context, constraints) { | |
return Row( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: walls.map( | |
(e) { | |
final side = e['side']!.toInt(); | |
final perpDistance = e['perpDistance']!; | |
final distance = e['distance']!; | |
final distance2 = min(1.0 / (distance), 1.0); | |
final colorBright = max( | |
(mapNum(distance2, 0.0, 1.0, 0.0, 255.0)) * | |
(side == 0 ? 1.0 : 0.75), | |
15) | |
.toInt(); | |
final color = Color.fromARGB( | |
255, colorBright, colorBright, colorBright); | |
return Expanded( | |
child: Center( | |
child: Container( | |
height: constraints.maxHeight / perpDistance, | |
decoration: BoxDecoration( | |
color: color, | |
border: Border.all( | |
color: color, | |
width: 1, | |
), | |
), | |
), | |
), | |
); | |
}, | |
).toList(), | |
); | |
}) | |
], | |
), | |
), | |
], | |
), | |
); | |
} | |
void update() { | |
walls.clear(); | |
final cols = (resolution * MediaQuery.of(context).size.width).toInt(); | |
final fov2 = fov / 2.0; | |
final angleStep = fov / cols; | |
final maxDistance = map.length * 2.0; | |
for (var i = 0; i < cols; i++) { | |
final angle = -fov2 + angleStep * i; | |
final rayDirX = cos(playerAngle + angle); | |
final rayDirY = sin(playerAngle + angle); | |
final rayUnitStepSizeX = | |
sqrt(1 + (rayDirY / rayDirX) * (rayDirY / rayDirX)); | |
final rayUnitStepSizeY = | |
sqrt(1 + (rayDirX / rayDirY) * (rayDirX / rayDirY)); | |
var mapCheckX = playerX.toInt(); | |
var mapCheckY = playerY.toInt(); | |
var rayLenght1DX = (rayDirX < 0 | |
? playerX - mapCheckX.toDouble() | |
: (mapCheckX + 1).toDouble() - playerX) * | |
rayUnitStepSizeX; | |
var rayLenght1DY = (rayDirY < 0 | |
? playerY - mapCheckY.toDouble() | |
: (mapCheckY + 1).toDouble() - playerY) * | |
rayUnitStepSizeY; | |
var stepX = rayDirX < 0 ? -1 : 1; | |
var stepY = rayDirY < 0 ? -1 : 1; | |
var tileFound = false; | |
var distance = 0.0; | |
var side = 0.0; | |
if (mapCheckX >= 0 && | |
mapCheckX < map.length && | |
mapCheckY >= 0 && | |
mapCheckY < map.length) { | |
if (map[mapCheckY][mapCheckX] > 0) { | |
tileFound = true; | |
} | |
} | |
while (!tileFound && distance < maxDistance) { | |
if (rayLenght1DX < rayLenght1DY) { | |
mapCheckX += stepX; | |
distance = rayLenght1DX; | |
rayLenght1DX += rayUnitStepSizeX; | |
side = 0.0; | |
} else { | |
mapCheckY += stepY; | |
distance = rayLenght1DY; | |
rayLenght1DY += rayUnitStepSizeY; | |
side = 1.0; | |
} | |
if (mapCheckX >= 0 && | |
mapCheckX < map.length && | |
mapCheckY >= 0 && | |
mapCheckY < map.length) { | |
if (map[mapCheckY][mapCheckX] > 0) { | |
tileFound = true; | |
} | |
} | |
} | |
walls.add({ | |
'distance': distance, | |
'perpDistance': cos(angle) * distance, | |
'angle': angle, | |
'side': side, | |
}); | |
setState(() {}); | |
} | |
} | |
} | |
double mapNum( | |
double x, double inMin, double inMax, double outMin, double outMax) { | |
return (x - inMin) * (outMax - outMin) / (inMax - inMin) + outMin; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment