Created with <3 with dartpad.dev.
Created
October 8, 2023 08:19
-
-
Save tan86/cdf42ed368e50d7172ef63f01844f6f8 to your computer and use it in GitHub Desktop.
flow-fields
This file contains hidden or 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 'package:flutter/material.dart'; | |
import 'package:flutter/scheduler.dart'; | |
import 'dart:math'; | |
import 'dart:ui'; | |
void main() => runApp(const App()); | |
class App extends StatelessWidget { | |
const App({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
return const MaterialApp( | |
debugShowCheckedModeBanner: false, | |
home: Scaffold( | |
backgroundColor: Colors.black, | |
body: Center( | |
child: FlowField(), | |
), | |
), | |
); | |
} | |
} | |
class FlowField extends StatefulWidget { | |
const FlowField({super.key}); | |
@override | |
State<StatefulWidget> createState() => FlowFieldState(); | |
} | |
class FlowFieldState extends State<FlowField> | |
with SingleTickerProviderStateMixin { | |
static const double w = 300; | |
static const double h = 300; | |
static const n = 25; // no. of fields per row | |
static const pn = 200; // no. of particles | |
static const fieldSize = w / n; | |
late final Ticker _ticker; | |
/// | |
static Vec2 angleVec2(double radian) => (x: cos(radian), y: sin(radian)); | |
static Vec2 rndAngleVec2() => angleVec2(Random().nextDouble() * pi * 2); | |
static Vec2 windowToField(Vec2 v) { | |
final fv = ( | |
x: (v.x / fieldSize).floorToDouble(), | |
y: (v.y / fieldSize).floorToDouble(), | |
); | |
return fv.clamp(min: 0, max: n - 1); | |
} | |
@override | |
void initState() { | |
super.initState(); | |
_ticker = createTicker(_onTick)..start(); | |
} | |
@override | |
void dispose() { | |
_ticker.dispose(); | |
super.dispose(); | |
} | |
void _onTick(Duration elapsed) { | |
for (var i = 0; i < pn; i++) { | |
final p = particles[i]; | |
final fp = windowToField(p); | |
final f = fields[fp.x.toInt() * fp.y.toInt()]; | |
particles[i] = p + f; | |
} | |
setState(() {}); | |
} | |
/// | |
final fields = List<Vec2>.generate( | |
n * n, | |
(_) => rndAngleVec2(), | |
growable: false, | |
); | |
final particles = List<Vec2>.generate( | |
pn, | |
(_) => ( | |
x: Random().nextDouble() * w, | |
y: Random().nextDouble() * h, | |
), | |
growable: false, | |
); | |
@override | |
Widget build(BuildContext context) { | |
return CustomPaint( | |
painter: FlowFieldPainter( | |
n: n, | |
fs: fieldSize, | |
fields: fields, | |
particles: particles, | |
), | |
size: const Size(w, h), | |
); | |
} | |
} | |
class FlowFieldPainter extends CustomPainter { | |
FlowFieldPainter({ | |
required this.n, | |
required this.fs, | |
required this.fields, | |
required this.particles, | |
}); | |
final int n; | |
final double fs; | |
final List<Vec2> fields; | |
final List<Vec2> particles; | |
/// | |
late final hfs = fs / 2; | |
final p = Paint()..color = Colors.white; | |
@override | |
void paint(Canvas canvas, Size size) { | |
for (var i = 0; i < particles.length; i++) { | |
final pp = particles[i]; | |
canvas.drawCircle(Offset(pp.x, pp.y), 2, p); | |
} | |
// canvas.saveLayer(null, Paint()..blendMode = BlendMode.multiply); | |
} | |
void drawFlowLines(Canvas canvas) { | |
final p = Paint()..color = Colors.red; | |
for (var y = 0; y < n; y++) { | |
for (var x = 0; x < n; x++) { | |
final os = Offset(hfs, hfs) + Offset(x * fs, y * fs); | |
final f = fields[x * y]; | |
final upper = os + Offset(hfs + f.x, hfs * f.y); | |
final lower = os + Offset(-hfs + f.x, -hfs * f.y); | |
canvas.drawLine(upper, lower, p); | |
canvas.drawCircle(upper, 1, p); | |
} | |
} | |
} | |
@override | |
bool shouldRepaint(covariant FlowFieldPainter oldDelegate) => true; | |
} | |
typedef Vec2 = ({double x, double y}); | |
extension Vec2Ext on Vec2 { | |
Vec2 operator +(Vec2 o) => (x: x + o.x, y: y + o.y); | |
Vec2 clamp({required double min, required double max}) => ( | |
x: clampDouble(x, min, max), | |
y: clampDouble(y, min, max), | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment