diff --git a/lib/src/Engine.dart b/lib/src/Engine.dart index 84d52e8..17f474a 100644 --- a/lib/src/Engine.dart +++ b/lib/src/Engine.dart @@ -1,6 +1,7 @@ import 'dart:html' as html; import 'dart:math'; +import 'package:rules_of_living/src/Render.dart'; import 'package:rules_of_living/src/Simulation.dart'; class Engine { @@ -29,33 +30,52 @@ class Engine { // ms stuck in updateloop after which game will declare itself unresponsive final int SAFETY_TIMEOUT = 2000; + bool running = false; + Render _render; + /// Grid Size /// /// Number of cells on x coordinate and y coordinate. Can be set individually. - Point get gridSize => Point(_grid.w, _grid.h); + Point get gridSize => Point(_simulation.w, _simulation.h); void set gridSize(Point value) { if (value.x <= 0 || value.y <= 0) 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 _drawLag = 0.0; + Simulation __simulation; + Simulation get _simulation => __simulation; + void set _simulation(Simulation value) { + __simulation = value; + _resetRenderer(); + } + /// The Canvas to Paint on /// /// 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 /// rendered later. - html.CanvasElement canvas; - Simulation _grid; - bool running = false; + html.CanvasElement _canvas; + html.CanvasElement get canvas => _canvas; + void set canvas(html.CanvasElement canvas) { + _canvas = canvas; + _resetRenderer(); + } - Engine([x = 100, y = 100, this.canvas]) { - _grid = Simulation(x, y); + void _resetRenderer() { + 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(); - _grid.addPattern(amount: 15, dispersal: 5); + _simulation.addPattern(amount: 15, dispersal: 5); html.window.animationFrame.then(animFrame); } @@ -65,12 +85,12 @@ class Engine { } void reset() { - _grid.reset(); + _simulation.reset(); running = false; } void clear() { - _grid = new Simulation(gridSize.x, gridSize.y); + _simulation = new Simulation(gridSize.x, gridSize.y); running = false; } @@ -101,7 +121,13 @@ class Engine { /// directly, since it is automatically taken care of by the processing function. /// If simulation should be advanced manually one time, prefer using step(). void update() { - if (!_grid.update()) running = false; + Map stateChanges = _simulation.update(); + if (stateChanges.length == 0) { + running = false; + return; + } else { + _render.mergeChanges(stateChanges); + } } /// Advances Logic One Update @@ -111,7 +137,7 @@ class Engine { /// (though this should usually not pose a problem). void step() { running = false; - _grid.update(); + _simulation.update(); } /// Renders the Current Simulation State @@ -119,9 +145,7 @@ class Engine { /// Renders the simulation once. Will usually automatically be called by /// the internal engine processing. Does not do anything if no canvas is /// defined. - void render([num interp]) { - if (canvas != null) _grid.render(canvas, interp); - } + void render([num interp]) {} void addPattern( {CellPattern pattern, @@ -130,7 +154,7 @@ class Engine { int amount, int dispersal, int seed}) { - _grid.addPattern( + _simulation.addPattern( pattern: pattern, x: x, y: y, @@ -140,6 +164,6 @@ class Engine { } void toggleEdgeRendering() { - _grid.renderEdges = !_grid.renderEdges; +// _grid.renderEdges = !_grid.renderEdges; } } diff --git a/lib/src/Render.dart b/lib/src/Render.dart new file mode 100644 index 0000000..7eb1a04 --- /dev/null +++ b/lib/src/Render.dart @@ -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 _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 _gridSize; + Point 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 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 _getColorMap(Point 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 colorMap = Map(); + List 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 _initRenderGrid(Point cellSize, Point gridSize) { + List 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 stateChanges) { + if (stateChanges.length == 0) return; + stateChanges.forEach((int i, bool state) { + _renderContainer.getChildAt(i).bitmapData = + (state ? _colorMap[_colorScheme.ON] : _colorMap[_colorScheme.OFF]); + }); + setDirty(); + } +} diff --git a/lib/src/Simulation.dart b/lib/src/Simulation.dart index ca7bb71..a10c16f 100644 --- a/lib/src/Simulation.dart +++ b/lib/src/Simulation.dart @@ -125,19 +125,20 @@ class Simulation { return null; } - bool update() { - bool stateChanges = false; + Map update() { + Map stateChanges = Map(); for (int i = 0; i < map.length; i++) { math.Point p = map.toCoordinates(i); 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 - map.forEach((Cell el) { - if (el.state != el.nextState) stateChanges = true; + for (int i = 0; i < map.length; i++) { + Cell el = map[i]; + if (el.state != el.nextState) stateChanges[i] = el.nextState; el.advanceState(); - }); - stateChanges ? _dirty = true : false; + } + (stateChanges.length > 0) ? _dirty = true : false; return stateChanges; } diff --git a/pubspec.yaml b/pubspec.yaml index a63ea9a..94b94ee 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,6 +10,7 @@ environment: dependencies: angular: ^5.0.0-beta angular_components: ^0.9.0-beta + stagexl: ^1.4.0+1 dev_dependencies: angular_test: ^2.0.0-beta diff --git a/test/src/render_test.dart b/test/src/render_test.dart new file mode 100644 index 0000000..3c39387 --- /dev/null +++ b/test/src/render_test.dart @@ -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", () {}); +}