diff --git a/Box b/Box index 443b373..ed1da29 160000 --- a/Box +++ b/Box @@ -1 +1 @@ -Subproject commit 443b373c87e95adb3ff6d49aed8ae3918238e262 +Subproject commit ed1da2918647fa393f62f564dead39f2f7ef9b13 diff --git a/assets/scripts/scene.toy b/assets/scripts/scene.toy index 635ecd6..69976b2 100644 --- a/assets/scripts/scene.toy +++ b/assets/scripts/scene.toy @@ -15,28 +15,32 @@ var mapGridHeight: int = null; //entities var player: opaque = null; +var renderer: opaque = null; //lifetime functions -fn onInit(node: opaque) { +fn onLoad(node: opaque) { //init the rng with a seed (random) if (rng != null) { rng.freeRandomGenerator(); } rng = createRandomGenerator(clock().hash()); - - //generate the level, filling out the entity data - node.generateLevel(rng); - //generate the child node to render the map - var renderer: opaque = node.loadChildNode("scripts:/tilemap/renderer.toy"); - renderer.callNodeFn("setTilemap", tilemap); + renderer = node.loadChildNode("scripts:/tilemap/renderer.toy"); //load the music loadMusic("music:/Music_Skylands_placeholder.ogg"); playMusic(); } +fn onInit(node: opaque) { + //generate the level, filling out the entity data + node.generateLevel(rng); + + //update the visible area from the player's perspective + updateVisible(); +} + fn onStep(node: opaque) { for (var i: int = 0; i < node.getChildNodeCount(); i++) { var child = node.getChildNode(i); @@ -162,6 +166,14 @@ fn runAI(node: opaque) { } } +//update the visible portions of the map +fn updateVisible() { + var playerX: int = player.callNodeFn("getGridPositionX"); + var playerY: int = player.callNodeFn("getGridPositionY"); + + renderer.callNodeFn("updateVisble", playerX, playerY, tilemap); +} + //for collisions fn getWalkableAt(node: opaque, x: int, y: int) { @@ -205,3 +217,4 @@ fn depthComparator(lhs: opaque, rhs: opaque) { return lhsPositionY < rhsPositionY; } + diff --git a/assets/scripts/tilemap/renderer.toy b/assets/scripts/tilemap/renderer.toy index 92279e8..80ba8cf 100644 --- a/assets/scripts/tilemap/renderer.toy +++ b/assets/scripts/tilemap/renderer.toy @@ -1,6 +1,7 @@ import standard; import engine; import node; +import math; //constants for generating maps @@ -23,6 +24,9 @@ var MAP_GRID_WIDTH: int const = CELL_WIDTH * CELL_COUNT_X; var MAP_GRID_HEIGHT: int const = CELL_HEIGHT * CELL_COUNT_Y; +var tilemapCache = []; + + //lifecycle functions fn onLoad(node: opaque) { //load the atlas into this node @@ -31,47 +35,171 @@ fn onLoad(node: opaque) { //create a child as a render target var child: opaque = node.loadChildNode("scripts:/tilemap/renderer-child.toy"); child.createNodeTexture(MAP_GRID_WIDTH * TILE_PIXEL_WIDTH, MAP_GRID_HEIGHT * TILE_PIXEL_HEIGHT); + + //generate a grid filled with only empty tiles, as a starting point + for (var j: int = 0; j < MAP_GRID_HEIGHT; j++) { + for (var i: int = 0; i < MAP_GRID_WIDTH; i++) { + tilemapCache.push(-1); //x + tilemapCache.push(-1); //y + // tilemapCache.push(0); //collision + } + } } -//TODO: lazily render -fn setTilemap(node: opaque, tilemap: [int]) { - assert tilemap, "provided tilemap is null (in setTilemap)"; - - print clock() + " - begin rendering map"; - +//lazily render +fn updateVisble(node: opaque, x: int, y: int, tilemap) { + print clock() + " - start updateVisible()"; var child: opaque = node.getChildNode(0); setRenderTarget(child); - //draw the tilemap to the child - for (var j = 0; j < MAP_GRID_HEIGHT; j++) { - for (var i = 0; i < MAP_GRID_WIDTH; i++) { - //don't recalculate this every time - var ITERATION: int const = j * MAP_GRID_WIDTH * 3 + i * 3; - - //don't render empty tiles - if (tilemap[ITERATION] < 0 || tilemap[ITERATION + 1] < 0) { - continue; - } - - //set the rect of the node on the tilesheet - the "tilemap" var is a single blob of data - node.setNodeRect( - tilemap[ITERATION] * TILE_PIXEL_WIDTH, - tilemap[ITERATION + 1] * TILE_PIXEL_HEIGHT, - TILE_PIXEL_WIDTH, TILE_PIXEL_HEIGHT - ); - - //draw to the screen - node.drawNode( - (i * TILE_PIXEL_WIDTH), - (j * TILE_PIXEL_HEIGHT), - TILE_PIXEL_WIDTH, - TILE_PIXEL_HEIGHT - ); - } - } + //divide the regions into octants & determine what is visible + node.shadowCastOctant(x, y, 1, 1, 10, tilemap); + node.shadowCastOctant(x, y, -1, 1, 10, tilemap); + node.shadowCastOctant(x, y, 1, -1, 10, tilemap); + node.shadowCastOctant(x, y, -1, -1, 10, tilemap); //reset the render target to the screen setRenderTarget(null); - print clock() + " - end rendering map"; + print clock() + " - end updateVisible()"; } + +fn shadowCastOctant(node: opaque, x: int, y: int, dirX: int, dirY: int, depth: int, tilemap) { + var shadows = []; + + //for each tile, cast its shadow, and see if its already obscured + for (var j: int = 0; abs(j) < depth; j += dirY) { + for (var i: int = 0; abs(i) < depth; i += dirX) { + //make sure this tile can actually cast a shadow + var CACHE_ITERATION: int const = (y + j) * MAP_GRID_WIDTH * 2 + (x + i) * 2; + var ITERATION: int const = (y + j) * MAP_GRID_WIDTH * 3 + (x + i) * 3; + + //don't render empty tiles + if (ITERATION < 0 || ITERATION >= MAP_GRID_WIDTH * MAP_GRID_HEIGHT * 3 || tilemap[ITERATION] < 0 || tilemap[ITERATION + 1] < 0) { + continue; //break? + } + + //don't re-render already drawn tiles + if (tilemapCache[CACHE_ITERATION] == tilemap[ITERATION] && tilemapCache[CACHE_ITERATION + 1] == tilemap[ITERATION + 1]) { + continue; + } + + //cast the shadow + var shadow = shadowCastTile(i + x, j + y, dirX, dirY, depth); + + //merge shadows if needed + var index: int = 0; + + //find the insertion point + for (; index < shadows.length(); index++) { + if (shadows[index][0] >= shadow[0]) break; + } + + print shadows.toString() + " " + string index; + + //check to see if the tile should be rendered (prev iterations would have merged shadows) + if (shadows.length() == 0 || (shadows.length() > index && (index == 0 || shadows[index-1][1] <= shadow[1])) || (shadows.length() > index && (shadows.length() == index + 1 || shadows[index][0] >= shadow[0]) )) { + print "first bracket"; + tilemapCache[CACHE_ITERATION] = tilemap[ITERATION]; + tilemapCache[CACHE_ITERATION + 1] = tilemap[ITERATION + 1]; + + //set the rect of the node on the tilesheet - the "tilemap" var is a single blob of data + node.setNodeRect( + tilemap[ITERATION] * TILE_PIXEL_WIDTH, + tilemap[ITERATION + 1] * TILE_PIXEL_HEIGHT, + TILE_PIXEL_WIDTH, TILE_PIXEL_HEIGHT + ); + + print string((x + i) * TILE_PIXEL_WIDTH) + ", " + string((y + j) * TILE_PIXEL_HEIGHT); + + //draw to the screen + node.drawNode( + ((x + i) * TILE_PIXEL_WIDTH), + ((y + j) * TILE_PIXEL_HEIGHT), + TILE_PIXEL_WIDTH, + TILE_PIXEL_HEIGHT + ); + } + + var overlapping = false; + + //see if the new shadow overlaps the previous + if (index > 0 && shadows.length() > index && shadows[index -1][1] > shadow[0]) { + print "second bracket"; + overlapping = true; + shadows[index-1][1] = shadow[1]; //extend the prev shadow + + //see if the newly extended prev overlaps the shadow at "index" + if (shadows[index-1][1] >= shadows[index][0]) { + //merge the two + shadows[index-1][1] = shadows[index][1]; + shadows = shadows.remove(index); + } + } + + //see if the new shadow overlaps the one at "index" + else if (shadows.length() > index && shadows[index][1] <= shadow[0]) { + print "third bracket"; + overlapping = true; + //extend the next shadow + shadows[index][1] = shadow[0]; + } + + if (!overlapping) { + print "fourth bracket"; + //insert at this position + shadows = shadows.insert(index, shadow); + } + } + } +} + +fn shadowCastTile(x: int, y: int, dirX: int, dirY: int, depth: int) { + //top-left corner & bottom-right corner forms a triangle with the player + + //top-left = x & y + //bottom-right = x + sign(dirX) & y + sign(dirY) + + var start = abs(shadowCastPoint(x, y, depth)); + var end = abs(shadowCastPoint(x + sign(dirX), y + sign(dirY), depth)); + + return [start, end]; +} + +fn shadowCastPoint(x: float, y: float, depth: int) { +// if (y == 0) { +// return sin(tan(INFINITY)); +// } + return sin(tan(x/y)) * depth; +} + +//polyfill the insert function +fn insert(self, k, v) { + //eew + var tmp1 = v; + var tmp2; + for (var i = k; i < self.length(); i++) { + tmp2 = self[i]; + self[i] = tmp1; + tmp1 = tmp2; + } + + self.push(tmp1); + return self; +} + +//polyfill the remove function +fn remove(self, k) { + //double eew + var result = []; + + for (var i = 0; i <= k - 1; i++) { + result.push( self[i] ); + } + + for (var i = k + 1; i < self.length(); i++) { + result.push( self[i] ); + } + + return result; +} \ No newline at end of file