Begin Refactor StageXL into Engine
This commit is contained in:
parent
9c37f87045
commit
6b0fae44b6
5 changed files with 185 additions and 23 deletions
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:html' as html;
|
import 'dart:html' as html;
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:rules_of_living/src/Render.dart';
|
||||||
import 'package:rules_of_living/src/Simulation.dart';
|
import 'package:rules_of_living/src/Simulation.dart';
|
||||||
|
|
||||||
class Engine {
|
class Engine {
|
||||||
|
@ -29,33 +30,52 @@ class Engine {
|
||||||
// ms stuck in updateloop after which game will declare itself unresponsive
|
// ms stuck in updateloop after which game will declare itself unresponsive
|
||||||
final int SAFETY_TIMEOUT = 2000;
|
final int SAFETY_TIMEOUT = 2000;
|
||||||
|
|
||||||
|
bool running = false;
|
||||||
|
Render _render;
|
||||||
|
|
||||||
/// Grid Size
|
/// Grid Size
|
||||||
///
|
///
|
||||||
/// Number of cells on x coordinate and y coordinate. Can be set individually.
|
/// Number of cells on x coordinate and y coordinate. Can be set individually.
|
||||||
Point get gridSize => Point<int>(_grid.w, _grid.h);
|
Point get gridSize => Point<int>(_simulation.w, _simulation.h);
|
||||||
void set gridSize(Point<int> value) {
|
void set gridSize(Point<int> value) {
|
||||||
if (value.x <= 0 || value.y <= 0)
|
if (value.x <= 0 || value.y <= 0)
|
||||||
throw ArgumentError("grid size must not be smaller than 1");
|
throw ArgumentError("grid size must not be smaller than 1");
|
||||||
_grid = Simulation(value.x, value.y);
|
_simulation = Simulation(value.x, value.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
num _updateLag = 0.0;
|
num _updateLag = 0.0;
|
||||||
num _drawLag = 0.0;
|
num _drawLag = 0.0;
|
||||||
|
|
||||||
|
Simulation __simulation;
|
||||||
|
Simulation get _simulation => __simulation;
|
||||||
|
void set _simulation(Simulation value) {
|
||||||
|
__simulation = value;
|
||||||
|
_resetRenderer();
|
||||||
|
}
|
||||||
|
|
||||||
/// The Canvas to Paint on
|
/// The Canvas to Paint on
|
||||||
///
|
///
|
||||||
/// Manually define or change the canvas the engine should render to. Should
|
/// Manually define or change the canvas the engine should render to. Should
|
||||||
/// be used if no canvas was defined at engine creation and it should be
|
/// be used if no canvas was defined at engine creation and it should be
|
||||||
/// rendered later.
|
/// rendered later.
|
||||||
html.CanvasElement canvas;
|
html.CanvasElement _canvas;
|
||||||
Simulation _grid;
|
html.CanvasElement get canvas => _canvas;
|
||||||
bool running = false;
|
void set canvas(html.CanvasElement canvas) {
|
||||||
|
_canvas = canvas;
|
||||||
|
_resetRenderer();
|
||||||
|
}
|
||||||
|
|
||||||
Engine([x = 100, y = 100, this.canvas]) {
|
void _resetRenderer() {
|
||||||
_grid = Simulation(x, y);
|
if (this.canvas == null || _simulation == null) return;
|
||||||
|
_render = Render(canvas, gridSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
Engine([x = 100, y = 100, canvas]) {
|
||||||
|
this.canvas = canvas;
|
||||||
|
_simulation = Simulation(x, y);
|
||||||
|
|
||||||
_elapsed.start();
|
_elapsed.start();
|
||||||
_grid.addPattern(amount: 15, dispersal: 5);
|
_simulation.addPattern(amount: 15, dispersal: 5);
|
||||||
html.window.animationFrame.then(animFrame);
|
html.window.animationFrame.then(animFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,12 +85,12 @@ class Engine {
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
_grid.reset();
|
_simulation.reset();
|
||||||
running = false;
|
running = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void clear() {
|
void clear() {
|
||||||
_grid = new Simulation(gridSize.x, gridSize.y);
|
_simulation = new Simulation(gridSize.x, gridSize.y);
|
||||||
running = false;
|
running = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +121,13 @@ class Engine {
|
||||||
/// directly, since it is automatically taken care of by the processing function.
|
/// directly, since it is automatically taken care of by the processing function.
|
||||||
/// If simulation should be advanced manually one time, prefer using step().
|
/// If simulation should be advanced manually one time, prefer using step().
|
||||||
void update() {
|
void update() {
|
||||||
if (!_grid.update()) running = false;
|
Map<int, bool> stateChanges = _simulation.update();
|
||||||
|
if (stateChanges.length == 0) {
|
||||||
|
running = false;
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
_render.mergeChanges(stateChanges);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Advances Logic One Update
|
/// Advances Logic One Update
|
||||||
|
@ -111,7 +137,7 @@ class Engine {
|
||||||
/// (though this should usually not pose a problem).
|
/// (though this should usually not pose a problem).
|
||||||
void step() {
|
void step() {
|
||||||
running = false;
|
running = false;
|
||||||
_grid.update();
|
_simulation.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Renders the Current Simulation State
|
/// Renders the Current Simulation State
|
||||||
|
@ -119,9 +145,7 @@ class Engine {
|
||||||
/// Renders the simulation once. Will usually automatically be called by
|
/// Renders the simulation once. Will usually automatically be called by
|
||||||
/// the internal engine processing. Does not do anything if no canvas is
|
/// the internal engine processing. Does not do anything if no canvas is
|
||||||
/// defined.
|
/// defined.
|
||||||
void render([num interp]) {
|
void render([num interp]) {}
|
||||||
if (canvas != null) _grid.render(canvas, interp);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addPattern(
|
void addPattern(
|
||||||
{CellPattern pattern,
|
{CellPattern pattern,
|
||||||
|
@ -130,7 +154,7 @@ class Engine {
|
||||||
int amount,
|
int amount,
|
||||||
int dispersal,
|
int dispersal,
|
||||||
int seed}) {
|
int seed}) {
|
||||||
_grid.addPattern(
|
_simulation.addPattern(
|
||||||
pattern: pattern,
|
pattern: pattern,
|
||||||
x: x,
|
x: x,
|
||||||
y: y,
|
y: y,
|
||||||
|
@ -140,6 +164,6 @@ class Engine {
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggleEdgeRendering() {
|
void toggleEdgeRendering() {
|
||||||
_grid.renderEdges = !_grid.renderEdges;
|
// _grid.renderEdges = !_grid.renderEdges;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
121
lib/src/Render.dart
Normal file
121
lib/src/Render.dart
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
import 'dart:html' as html;
|
||||||
|
import 'dart:math';
|
||||||
|
import 'package:stagexl/stagexl.dart' as sxl;
|
||||||
|
|
||||||
|
class ColorScheme {
|
||||||
|
//TODO make iterable (perhaps through backing list which gets accesses by the variables)
|
||||||
|
final int ON;
|
||||||
|
final int EXPANSION;
|
||||||
|
final int CORE;
|
||||||
|
final int OFF;
|
||||||
|
|
||||||
|
const ColorScheme(this.ON, this.EXPANSION, this.CORE,
|
||||||
|
[this.OFF = sxl.Color.Transparent]);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Render {
|
||||||
|
final sxl.Stage _stage;
|
||||||
|
final sxl.RenderLoop _renderLoop = sxl.RenderLoop();
|
||||||
|
Map<int, sxl.BitmapData> _colorMap;
|
||||||
|
final sxl.BitmapContainer _renderContainer = sxl.BitmapContainer();
|
||||||
|
|
||||||
|
bool transparentBG = false;
|
||||||
|
bool _renderEdges = false;
|
||||||
|
bool get renderEdges => _renderEdges;
|
||||||
|
void set renderEdges(bool value) {
|
||||||
|
_renderEdges = value;
|
||||||
|
_colorMap = _getColorMap(_cellSize, _colorScheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
Point<int> _gridSize;
|
||||||
|
Point<int> get _cellSize => Point(
|
||||||
|
_stage.stageWidth ~/ _gridSize.x, _stage.stageHeight ~/ _gridSize.y);
|
||||||
|
|
||||||
|
final ColorScheme _colorScheme;
|
||||||
|
//TODO replace with scheme data structure to enable different color scheme injection
|
||||||
|
static const defaultScheme =
|
||||||
|
ColorScheme(sxl.Color.Blue, 1, 1, sxl.Color.Black);
|
||||||
|
|
||||||
|
// TODO gridSize rendering can only be scaled uniformly currently - switch to Point(w,h)?
|
||||||
|
Render(html.CanvasElement canvas, Point<int> gridSize,
|
||||||
|
[ColorScheme colorScheme = defaultScheme])
|
||||||
|
: _stage = _createStage(canvas),
|
||||||
|
_colorScheme = colorScheme,
|
||||||
|
_gridSize = gridSize {
|
||||||
|
_colorMap = _getColorMap(_cellSize, colorScheme);
|
||||||
|
_initRenderGrid(_cellSize, gridSize).forEach((sxl.Bitmap bm) {
|
||||||
|
_renderContainer.addChild(bm);
|
||||||
|
});
|
||||||
|
_renderLoop.addStage(_stage);
|
||||||
|
_stage.addChild(_renderContainer);
|
||||||
|
setDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
static sxl.Stage _createStage(html.CanvasElement canvas) {
|
||||||
|
sxl.StageXL.stageOptions.renderEngine = sxl.RenderEngine.WebGL;
|
||||||
|
sxl.StageXL.stageOptions.stageRenderMode = sxl.StageRenderMode.AUTO_INVALID;
|
||||||
|
// sxl.StageXL.stageOptions.backgroundColor = sxl.Color.Yellow;
|
||||||
|
return sxl.Stage(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<int, sxl.BitmapData> _getColorMap(Point<int> size, ColorScheme scheme) {
|
||||||
|
print("${size.toString()}, ${scheme.toString()}");
|
||||||
|
|
||||||
|
// Creates a shape with color of each quad in scheme
|
||||||
|
sxl.Shape shape = sxl.Shape();
|
||||||
|
// ON
|
||||||
|
shape.graphics.beginPath();
|
||||||
|
shape.graphics.rect(0 * size.x, 0, size.x, size.y);
|
||||||
|
shape.graphics.fillColor(scheme.ON ?? sxl.Color.Transparent);
|
||||||
|
shape.graphics
|
||||||
|
.strokeColor(renderEdges ? sxl.Color.DarkGray : sxl.Color.Transparent);
|
||||||
|
shape.graphics.closePath();
|
||||||
|
// OFF
|
||||||
|
shape.graphics.beginPath();
|
||||||
|
shape.graphics.rect(1 * size.x, 0, size.x, size.y);
|
||||||
|
shape.graphics.fillColor(scheme.OFF ?? sxl.Color.Transparent);
|
||||||
|
shape.graphics
|
||||||
|
.strokeColor(renderEdges ? sxl.Color.DarkGray : sxl.Color.Transparent);
|
||||||
|
shape.graphics.closePath();
|
||||||
|
|
||||||
|
// creates one texture out of shape
|
||||||
|
sxl.BitmapData texture = sxl.BitmapData(2 * size.x, size.y);
|
||||||
|
texture.draw(shape);
|
||||||
|
// re-slice texture into individual BitmapDatas
|
||||||
|
Map<int, sxl.BitmapData> colorMap = Map();
|
||||||
|
List<sxl.BitmapData> colorBitmaps = texture.sliceIntoFrames(size.x, size.y);
|
||||||
|
print("Found ${colorBitmaps.length} colors.");
|
||||||
|
// TODO more elegant way through iterables etc; also include EXPANSION, CORE functionality
|
||||||
|
colorMap[scheme.ON] = colorBitmaps[0];
|
||||||
|
colorMap[scheme.OFF] = colorBitmaps[1];
|
||||||
|
|
||||||
|
return colorMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<sxl.Bitmap> _initRenderGrid(Point<int> cellSize, Point<int> gridSize) {
|
||||||
|
List<sxl.Bitmap> grid = List();
|
||||||
|
for (int y = 0; y < gridSize.y; y++) {
|
||||||
|
for (int x = 0; x < gridSize.x; x++) {
|
||||||
|
sxl.Bitmap bm = sxl.Bitmap();
|
||||||
|
|
||||||
|
bm.bitmapData = _colorMap[_colorScheme.OFF];
|
||||||
|
bm.x = x * cellSize.x;
|
||||||
|
bm.y = y * cellSize.y;
|
||||||
|
grid.add(bm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDirty() => _stage.invalidate();
|
||||||
|
|
||||||
|
void mergeChanges(Map<int, bool> stateChanges) {
|
||||||
|
if (stateChanges.length == 0) return;
|
||||||
|
stateChanges.forEach((int i, bool state) {
|
||||||
|
_renderContainer.getChildAt(i).bitmapData =
|
||||||
|
(state ? _colorMap[_colorScheme.ON] : _colorMap[_colorScheme.OFF]);
|
||||||
|
});
|
||||||
|
setDirty();
|
||||||
|
}
|
||||||
|
}
|
|
@ -125,19 +125,20 @@ class Simulation {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool update() {
|
Map<int, bool> update() {
|
||||||
bool stateChanges = false;
|
Map<int, bool> stateChanges = Map();
|
||||||
|
|
||||||
for (int i = 0; i < map.length; i++) {
|
for (int i = 0; i < map.length; i++) {
|
||||||
math.Point p = map.toCoordinates(i);
|
math.Point p = map.toCoordinates(i);
|
||||||
map[i].update(getSurroundingNeighbors(p.x, p.y, 1));
|
map[i].update(getSurroundingNeighbors(p.x, p.y, 1));
|
||||||
}
|
}
|
||||||
// TODO when implementing changeSet we can remove this second loop and add to changeSet in the first
|
// TODO when implementing changeSet we can remove this second loop and add to changeSet in the first
|
||||||
map.forEach((Cell el) {
|
for (int i = 0; i < map.length; i++) {
|
||||||
if (el.state != el.nextState) stateChanges = true;
|
Cell el = map[i];
|
||||||
|
if (el.state != el.nextState) stateChanges[i] = el.nextState;
|
||||||
el.advanceState();
|
el.advanceState();
|
||||||
});
|
}
|
||||||
stateChanges ? _dirty = true : false;
|
(stateChanges.length > 0) ? _dirty = true : false;
|
||||||
return stateChanges;
|
return stateChanges;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ environment:
|
||||||
dependencies:
|
dependencies:
|
||||||
angular: ^5.0.0-beta
|
angular: ^5.0.0-beta
|
||||||
angular_components: ^0.9.0-beta
|
angular_components: ^0.9.0-beta
|
||||||
|
stagexl: ^1.4.0+1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
angular_test: ^2.0.0-beta
|
angular_test: ^2.0.0-beta
|
||||||
|
|
15
test/src/render_test.dart
Normal file
15
test/src/render_test.dart
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import 'dart:html' as html;
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
@TestOn('browser')
|
||||||
|
import 'package:rules_of_living/src/Render.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
Render sut;
|
||||||
|
setUp(() {
|
||||||
|
html.CanvasElement canvas = html.CanvasElement();
|
||||||
|
sut = Render(canvas, 8);
|
||||||
|
});
|
||||||
|
group("colorMap", () {});
|
||||||
|
}
|
Loading…
Reference in a new issue