From 78f63a0ea06985291f93332c1dd7202e1270530a Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 28 Aug 2018 16:02:43 +0200 Subject: [PATCH] Begin Refactor StageXL into Engine --- lib/src/Engine.dart | 42 ++++++++++--- lib/src/Grid.dart | 49 +-------------- lib/src/Render.dart | 121 ++++++++++++++++++++++++++++++++++++++ pubspec.yaml | 1 + test/src/render_test.dart | 15 +++++ 5 files changed, 173 insertions(+), 55 deletions(-) create mode 100644 lib/src/Render.dart create mode 100644 test/src/render_test.dart diff --git a/lib/src/Engine.dart b/lib/src/Engine.dart index b06e53a..1d75066 100644 --- a/lib/src/Engine.dart +++ b/lib/src/Engine.dart @@ -2,6 +2,7 @@ import 'dart:html' as html; import 'dart:math'; import 'package:rules_of_living/src/Grid.dart'; +import 'package:rules_of_living/src/Render.dart'; class Engine { // Elapsed Time Counter - useful for Safety Timeout @@ -29,6 +30,9 @@ 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. @@ -42,17 +46,33 @@ class Engine { num _updateLag = 0.0; num _drawLag = 0.0; + Grid __grid; + Grid get _grid => __grid; + void set _grid(Grid value) { + __grid = 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; - Grid _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]) { + void _resetRenderer() { + if (canvas == null || _grid == null) return; + _render = Render(canvas, gridSize); + } + + Engine([x = 100, y = 100, canvas]) { _grid = Grid(x, y); + this.canvas = canvas; _elapsed.start(); _grid.addPattern(amount: 15, dispersal: 5); @@ -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 = _grid.update(); + if (stateChanges.length == 0) { + running = false; + return; + } else { + _render.mergeChanges(stateChanges); + } } /// Advances Logic One Update @@ -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, @@ -140,6 +164,6 @@ class Engine { } void toggleEdgeRendering() { - _grid.renderEdges = !_grid.renderEdges; +// _grid.renderEdges = !_grid.renderEdges; } } diff --git a/lib/src/Grid.dart b/lib/src/Grid.dart index d95c25e..edeb1ad 100644 --- a/lib/src/Grid.dart +++ b/lib/src/Grid.dart @@ -1,4 +1,3 @@ -import 'dart:html' as html; import 'dart:math' as math; import 'package:rules_of_living/src/Cell.dart'; @@ -11,9 +10,6 @@ class Grid { final int h; final List> map; - bool _dirty = true; - bool _renderEdges = true; - int _startingSeed; int _x; int _y; @@ -40,7 +36,6 @@ class Grid { seed: _startingSeed, x: _x, y: _y); - _dirty = true; } void addPattern( @@ -97,7 +92,6 @@ class Grid { } break; } - _dirty = true; } void setCellState(int x, int y, bool state) { @@ -148,8 +142,8 @@ class Grid { return grid; } - bool update() { - bool stateChanges = false; + Map update() { + Map stateChanges = Map(); for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { // DEFAULTS TO CONWAY GAME OF LIFE RANGE OF ONE @@ -159,10 +153,8 @@ class Grid { for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { Cell c = map[y][x]; - if (c.state != c.nextState) stateChanges = true; + if (c.state != c.nextState) stateChanges[y * w + x] = c.nextState; c.advanceState(); - - if (!_dirty && map[y][x].dirty) _dirty = true; } } return stateChanges; @@ -184,39 +176,4 @@ class Grid { } return count; } - - void render(html.CanvasElement canvas, [num interp]) { - // only renders if any cells changed between renders - if (!_dirty) return; - - html.CanvasRenderingContext2D ctx = canvas.getContext('2d'); - int brickW = (canvas.width ~/ map[0].length); - int brickH = (canvas.height ~/ map.length); - ctx.clearRect(0, 0, canvas.width, canvas.height); - - for (int y = 0; y < map.length; y++) { - for (int x = 0; x < map[y].length; x++) { - if (_renderEdges) { - ctx.setStrokeColorRgb(100, 100, 100); - ctx.strokeRect(x * brickW, y * brickH, brickW, brickH); - } - - Cell c = map[y][x]; - if (c.state == true) - ctx.setFillColorRgb(155, 155, 255); - else - ctx.setFillColorRgb(0, 0, 0); - ctx.fillRect(x * brickW, y * brickH, brickW, brickH); - } - } - - _dirty = false; - } - - void set renderEdges(bool on) { - _renderEdges = on; - _dirty = true; - } - - bool get renderEdges => _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/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", () {}); +}