Skip to content

Instantly share code, notes, and snippets.

@stargazing-dino
Created February 7, 2020 05:12
Show Gist options
  • Save stargazing-dino/67037d98e1bd3a3c3abed5da5142e14c to your computer and use it in GitHub Desktop.
Save stargazing-dino/67037d98e1bd3a3c3abed5da5142e14c to your computer and use it in GitHub Desktop.
A simple terrain generation algorithm implemented in flutter based off https://www.youtube.com/watch?v=IKB1hWWedMk&list=PLRqwX-V7Uu6ZiZxtDDRCi6uhfTH4FilpH&index=14
import 'dart:math';
import 'dart:typed_data';
import 'dart:ui' as UI;
import 'package:flutter/material.dart';
import 'package:vector_math/vector_math_64.dart' hide Colors;
final TargetPlatform platform = TargetPlatform.android;
void main() {
runApp(App());
}
void main() {
runApp(App());
}
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark(),
debugShowCheckedModeBanner: false,
home: TerrainGeneration(),
);
}
}
final up = Vector3(0.0, 1.0, 0.0);
class TerrainGeneration extends StatefulWidget {
@override
TerrainGenerationState createState() => TerrainGenerationState();
}
class TerrainGenerationState extends State<TerrainGeneration>
with SingleTickerProviderStateMixin {
// Fields private to this class
AnimationController _animationController;
final _customPaintKey = GlobalKey();
bool _isInitialized = false;
// Accessible in TerrainGenerationPainter.
// There is no need to setState after updating these
// They'll update with every vsync tick.
int columns, rows;
List<List<double>> terrain;
double scale = 40.0;
var flying = 0.0;
final simplexNoise = SimplexNoise();
@override
void initState() {
_animationController = AnimationController(
vsync: this,
duration: const Duration(days: 365),
);
// Await the build phase to be complete in order to get sizing
Future.delayed(Duration.zero, () {
final context = _customPaintKey.currentContext;
final RenderBox box = context.findRenderObject();
setup(box.size);
});
_animationController.forward();
super.initState();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Waves')),
body: SizedBox.expand(
child: AnimatedBuilder(
animation: _animationController,
builder: (context, __) {
return CustomPaint(
key: _customPaintKey,
willChange: true,
painter: _isInitialized
? TerrainGenerationPainter(_animationController, this)
: null,
);
},
),
),
);
}
void setup(Size size) {
final width = size.width;
// Add extra columns to help it fill the width of the screen
columns = (width / scale).round() + 16;
rows = (size.height / scale).round();
terrain = List.generate(
rows,
(_) => List.generate(columns + 1, (_) => 0.0),
);
setState(() => _isInitialized = true);
}
}
class TerrainGenerationPainter extends CustomPainter {
const TerrainGenerationPainter(this.animation, this.state)
: super(repaint: animation);
final Animation<double> animation;
final TerrainGenerationState state;
@override
void paint(Canvas canvas, Size size) {
final scale = state.scale;
state.flying -= 0.01;
var iOff = state.flying;
for (int i = 0; i < state.rows; i++) {
var jOff = 0.0;
state.terrain.add([]);
for (int j = 0; j < state.columns; j++) {
state.terrain[i][j] = state.simplexNoise.noise2D(iOff, jOff) * .25;
// terrain[i][j] = random.nextDouble() * .25;
jOff += 0.1;
}
iOff += 0.1;
}
final List<List<Vector3>> allVertices = [];
// final List<List<Vector3>> allVertices = [];
// To Account for all those columns I added in order to fill the space
canvas.translate(-100, 0);
for (int i = 0; i < state.rows; i++) {
allVertices.add([]);
for (int j = 0; j < state.columns - 1; j++) {
final vertices = allVertices[i];
vertices.add(
Vector3(j * scale, i * scale, state.terrain[i][j]),
);
vertices.add(
Vector3(j * scale, (i + 1) * scale, state.terrain[i][j + 1]),
);
}
}
final width = size.width;
final height = size.height;
final cameraPosition = Vector3(0, 0, 6);
final brush = Paint()
..color = Colors.red
..style = PaintingStyle.stroke
..strokeWidth = 4.0;
final lookAt = Vector3.zero();
final cameraDistance = cameraPosition.distanceTo(lookAt);
final aspectRatio = width / height;
final zNearPlane = cameraDistance - 0.1;
final zFarPlane = cameraDistance * 2;
final fovYRadians = 0.7;
// Build matrices to get us into an OpenGL-like coordinate space
// bottom left corner of the screen is at (-1, -1), top right is at (1, 1)
final toOpenGlCoords = Matrix4.compose(
Vector3(-1.0, 1.0, 0.0),
Quaternion.axisAngle(Vector3(1.0, 0.0, 0.0), pi),
Vector3(2 / width, 2 / height, 1.0),
);
final toFlutterCoords = Matrix4.tryInvert(toOpenGlCoords);
var modelMatrix = Matrix4.rotationX(-pi / 2.3).scaled(1.0, 4.5, 1.0);
final viewMatrix = makeViewMatrix(cameraPosition, lookAt, up);
final projectionMatrix = makePerspectiveMatrix(
fovYRadians,
aspectRatio,
zNearPlane,
zFarPlane,
);
final mvp = projectionMatrix * viewMatrix * modelMatrix;
final flutterCompatTransform = toFlutterCoords * mvp * toOpenGlCoords;
for (final vertices in allVertices) {
final projected = vertices.map<Vector2>((vertex) {
final vertexCopy = vertex.clone();
vertexCopy.applyProjection(flutterCompatTransform);
return Vector2(vertexCopy.x, vertexCopy.y);
}).toList();
final storage = _encodeVector2List(projected);
canvas.drawVertices(
UI.Vertices.raw(
VertexMode.triangleStrip,
storage,
),
BlendMode.srcATop,
brush,
);
}
}
// This is copied from dart:ui
Int32List _encodeColorList(List<Color> colors) {
final int colorCount = colors.length;
final Int32List result = Int32List(colorCount);
for (int i = 0; i < colorCount; ++i) result[i] = colors[i].value;
return result;
}
// This is copied from dart:ui
Float32List _encodeVector2List(List<Vector2> vertices) {
final int pointCount = vertices.length;
final Float32List result = Float32List(pointCount * 2);
for (int i = 0; i < pointCount; ++i) {
final int xIndex = i * 2;
final int yIndex = xIndex + 1;
final Vector2 point = vertices[i];
result[xIndex] = point.x;
result[yIndex] = point.y;
}
return result;
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment