Files
Airport/assets/scripts/tilemap/renderer.toy
2023-08-15 05:13:39 +10:00

223 lines
6.0 KiB
Plaintext

import standard;
import engine;
import node;
import math;
//constants for generating maps
var TILE_PIXEL_WIDTH: int const = 16;
var TILE_PIXEL_HEIGHT: int const = 16;
var ROOM_MIN_WIDTH: int const = 4; //minimum safe value 4
var ROOM_MIN_HEIGHT: int const = 4;
var ROOM_MAX_WIDTH: int const = 12;
var ROOM_MAX_HEIGHT: int const = 12;
var CELL_WIDTH: int const = 16; //minimum safe value ROOM_MAX_* + 4
var CELL_HEIGHT: int const = 16;
var CELL_COUNT_X: int const = 3;
var CELL_COUNT_Y: int const = 3;
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
node.loadNodeTexture("sprites:/tileset.png");
//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
}
}
}
//lazily render
fn updateVisble(node: opaque, x: int, y: int, tilemap) {
print clock() + " - start updateVisible()";
var child: opaque = node.getChildNode(0);
setRenderTarget(child);
//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 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) < abs(j); 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);
if (shadow == null) {
continue;
}
//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;
print "MARK one";
shadows[index-1][1] = max(shadows[index-1][1], shadow[1]); //extend the prev shadow
print "MARK two";
//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] = max(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][0] = min(shadows[index][0], 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));
if (checkIsNaN(start) || checkIsNaN(end)) {
return null;
}
if (start < end) {
return [start, end];
}
else {
return [end, start];
}
}
fn shadowCastPoint(x: float, y: float, depth: int) {
if (y == 0) {
return NAN;
}
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;
}