From bc547522564823ba12c2cab5fc120b3b7fe00c9c Mon Sep 17 00:00:00 2001 From: George FunBook Date: Fri, 10 Jan 2025 13:11:16 -0600 Subject: [PATCH 1/7] add various forEachInRay helpers --- flixel/tile/FlxBaseTilemap.hx | 18 +++ flixel/tile/FlxTilemap.hx | 278 +++++++++++++++++++++++++++------- 2 files changed, 245 insertions(+), 51 deletions(-) diff --git a/flixel/tile/FlxBaseTilemap.hx b/flixel/tile/FlxBaseTilemap.hx index f60d6eeb08..af98c2b069 100644 --- a/flixel/tile/FlxBaseTilemap.hx +++ b/flixel/tile/FlxBaseTilemap.hx @@ -221,6 +221,24 @@ class FlxBaseTilemap extends FlxObject return false; } + /** + * Shoots a ray from the start point to the end point. + * If/when it passes through a tile, it stores that point and returns false. + * + * **Note:** In flixel 5.0.0, this was redone, the old method is now `rayStep` + * + * @param start The world coordinates of the start of the ray. + * @param end The world coordinates of the end of the ray. + * @param result Optional result vector, to avoid creating a new instance to be returned. + * Only returned if the line enters the rect. + * @return Returns true if the ray made it from Start to End without hitting anything. + * Returns false and fills Result if a tile was hit. + */ + public function forEachOverlappingRay(start:FlxPoint, end:FlxPoint, ?func:FlxPoint):Void + { + throw "ray must be implemented"; + } + /** * Shoots a ray from the start point to the end point. * If/when it passes through a tile, it stores that point and returns false. diff --git a/flixel/tile/FlxTilemap.hx b/flixel/tile/FlxTilemap.hx index b281c742a7..c81dda1391 100644 --- a/flixel/tile/FlxTilemap.hx +++ b/flixel/tile/FlxTilemap.hx @@ -893,67 +893,127 @@ class FlxTypedTilemap extends FlxBaseTilemap updateWorld ); } - + /** * Shoots a ray from the start point to the end point. * If/when it passes through a tile, it stores that point and returns false. - * Note: In flixel 5.0.0, this was redone, the old method is now `rayStep` - * - * @param start The world coordinates of the start of the ray. - * @param end The world coordinates of the end of the ray. - * @param result Optional result vector, to avoid creating a new instance to be returned. - * Only returned if the line enters the rect. + * + * **Note:** In flixel 5.0.0, this was redone, the old method is now `rayStep` + * + * @param start The world coordinates of the start of the ray + * @param end The world coordinates of the end of the ray + * @param result Optional result vector, indicating where the ray hit a wall * @return Returns true if the ray made it from Start to End without hitting anything. - * Returns false and fills Result if a tile was hit. + * Returns false and fills `result` if a tile was hit. */ override function ray(start:FlxPoint, end:FlxPoint, ?result:FlxPoint):Bool + { + return -1 < findIndexInRayI(start, end, (_, tile)->tile != null && tile.solid, result); + } + + /** + * Calls `func` on all tiles overlapping a ray from `start` to `end` + * + * **Note:** This skips any tiles with no instance + * + * @param start The world coordinates of the start of the ray + * @param end The world coordinates of the end of the ray + * @param func The function, where `tile` is the tile instance for that location + */ + inline public function forEachInRay(start:FlxPoint, end:FlxPoint, func:(Tile)->Void) + { + findIndexInRayI(start, end, voidFindIgnoreIndex(func)); + } + + /** + * Calls `func` on all tiles overlapping a ray from `start` to `end` + * + * @param start The world coordinates of the start of the ray + * @param end The world coordinates of the end of the ray + * @param func The condition, where `index` is the tile's map index, and `tile` is + * the tile instance for that location, or `null` if there is no instance + */ + inline public function forEachInRayI(start:FlxPoint, end:FlxPoint, func:(index:Int, tile:Null)->Void) + { + findIndexInRayI(start, end, voidFind(func)); + } + + /** + * Checks all tiles overlapping a ray from `start` to `end`, + * finds the first tile that satisfies to condition of `func` and returns its index + * + * **Note:** This skips any tiles with no instance + * + * @param start The world coordinates of the start of the ray + * @param end The world coordinates of the end of the ray + * @param func The condition, where `tile` is the tile instance for that location + * @param result Optional result vector, indicating where the ray hit the found tile + * @return The index of the found tile + */ + inline public function findIndexInRay(start:FlxPoint, end:FlxPoint, func:(Tile)->Bool, ?result:FlxPoint):Int + { + return findIndexInRayI(start, end, ignoreIndex(func), result); + } + + /** + * Checks all tile indices overlapping a ray from `start` to `end`, + * finds the first tile that satisfies to condition of `func` and returns its index + * + * @param start The world coordinates of the start of the ray + * @param end The world coordinates of the end of the ray + * @param func The condition, where `index` is the tile's map index, and `tile` is + * the tile instance for that location, or `null` if there is no instance + * @param result Optional result vector, indicating where the ray hit the found tile + * @return The index of the found tile + */ + public function findIndexInRayI(start:FlxPoint, end:FlxPoint, func:(index:Int, tile:Null)->Bool, ?result:FlxPoint):Int { // trim the line to the parts inside the map final trimmedStart = calcRayEntry(start, end); final trimmedEnd = calcRayExit(start, end); - + start.putWeak(); end.putWeak(); - + if (trimmedStart == null || trimmedEnd == null) { FlxDestroyUtil.put(trimmedStart); FlxDestroyUtil.put(trimmedEnd); - return true; + return -1; } - + start = trimmedStart; end = trimmedEnd; - + inline function clearRefs() { trimmedStart.put(); trimmedEnd.put(); } - + final startIndex = getMapIndex(start); final endIndex = getMapIndex(end); - + // If the starting tile is solid, return the starting position final tile = getTileData(startIndex); if (tile != null && tile.solid) { if (result != null) result.copyFrom(start); - + clearRefs(); - return false; + return startIndex; } - + final startTileX = getColumn(startIndex); final startTileY = getRow(startIndex); final endTileX = getColumn(endIndex); final endTileY = getRow(endIndex); var hitIndex = -1; - + if (start.x == end.x) { - hitIndex = checkColumn(startTileX, startTileY, endTileY); + hitIndex = findIndexInColumnI(startTileX, startTileY, endTileY, func); if (hitIndex != -1 && result != null) { // check the bottom @@ -969,28 +1029,28 @@ class FlxTypedTilemap extends FlxBaseTilemap final m = (start.y - end.y) / (start.x - end.x); // y - mx = b final b = start.y - m * start.x; - + final movesRight = start.x < end.x; final inc = movesRight ? 1 : -1; final offset = movesRight ? 1 : 0; var tileX = startTileX; var lastTileY = startTileY; - + while (tileX != endTileX) { final xPos = getColumnPos(tileX + offset); final yPos = m * getColumnPos(tileX + offset) + b; final tileY = getRowAt(yPos); - hitIndex = checkColumn(tileX, lastTileY, tileY); + hitIndex = findIndexInColumnI(tileX, lastTileY, tileY, func); if (hitIndex != -1) break; lastTileY = tileY; tileX += inc; } - + if (hitIndex == -1) - hitIndex = checkColumn(endTileX, lastTileY, endTileY); - + hitIndex = findIndexInColumnI(endTileX, lastTileY, endTileY, func); + if (hitIndex != -1 && result != null) { result.copyFrom(getTilePos(hitIndex)); @@ -998,9 +1058,9 @@ class FlxTypedTilemap extends FlxBaseTilemap { if (start.x > end.x) result.x += scaledTileWidth; - + // set result to left side - result.y = m * result.x + b;//mx + b + result.y = m * result.x + b; // mx + b } else { @@ -1011,44 +1071,160 @@ class FlxTypedTilemap extends FlxBaseTilemap result.y += scaledTileHeight; } // otherwise result is top - + // x = (y - b)/m result.x = (result.y - b) / m; } } } - + clearRefs(); - return hitIndex == -1; + return hitIndex; } - - function checkColumn(x:Int, startY:Int, endY:Int):Int + + /** + * Calls `func` on all tiles in the `column` between the specified `startRow` and `endRow` + * + * **Note:** This skips any tiles with no instance + * + * @param column The column to check + * @param startRow The row to check from + * @param endRow The row to check to + * @param func The function, where `tile` is the tile instance for that location + */ + inline public function forEachIndexInColumn(column:Int, startRow:Int, endRow:Int, func:(Tile)->Void) { - if (startY < 0) - startY = 0; - - if (endY < 0) - endY = 0; - - if (startY > heightInTiles - 1) - startY = heightInTiles - 1; + findIndexInColumnI(column, startRow, endRow, voidFindIgnoreIndex(func)); + } + + /** + * Calls `func` on all tiles in the `column` between the specified `startRow` and `endRow` + * + * @param column The column to check + * @param startRow The row to check from + * @param endRow The row to check to + * @param func The function, where `index` is the tile's map index, and `tile` is + * the tile instance for that location, or `null` if there is no instance + */ + inline public function forEachIndexInColumnI(column:Int, startRow:Int, endRow:Int, func:(index:Int, tile:Null)->Bool) + { + findIndexInColumnI(column, startRow, endRow, voidFind(func)); + } + + /** + * Helper to convert `(Int, Null)->Bool` to `(Tile>)->Void`. + * Checks null, ignores index, always returns false + */ + inline function voidFindIgnoreIndex(func:(Tile)->Void):(Int, Null)->Bool + { + return function (_, t) + { + if (t != null) + func(t); + + return false; + }; + } + + /** + * Helper to convert `(Int, Null)->Bool` to `(Int, Null)->Void`. + * Always returns false + */ + inline function voidFind(func:(Int, Null)->Void):(index:Int, tile:Null)->Bool + { + return (_, t)->{ func(_, t); return false; }; + } + + + /** + * Checks all tiles in the `column` between the specified `startRow` and `endRow`, + * Retrieves the first tile that satisfies to condition of `func` and returns it + * + * **Note:** This skips any tiles with no instance + * + * @param column The column to check + * @param startRow The row to check from + * @param endRow The row to check to + * @param func The condition, where `tile` is the tile instance for that location + * @return The found tile + */ + public function findInColumn(column:Int, startRow:Int, endRow:Int, func:(Tile)->Bool):Tile + { + final index = findIndexInColumnI(column, startRow, endRow, ignoreIndex(func)); + if (index < 0) + return null; - if (endY > heightInTiles - 1) - endY = heightInTiles - 1; + final data = getTileData(index); + if (data == null) + throw 'Unexpected null tile at $index'; // internal error - var y = startY; - final step = startY <= endY ? 1 : -1; + return data; + } + + /** + * Checks all tiles in the `column` between the specified `startRow` and `endRow`, + * finds the first tile that satisfies to condition of `func` and returns its index + * + * **Note:** This skips any tiles with no instance + * + * @param column The column to check + * @param startRow The row to check from + * @param endRow The row to check to + * @param func The condition, where `tile` is the tile instance for that location + * @return The index of the found tile + */ + inline public function findIndexInColumn(column:Int, startRow:Int, endRow:Int, func:(Tile)->Bool):Int + { + return findIndexInColumnI(column, startRow, endRow, ignoreIndex(func)); + } + + /** + * Helper to convert `(Int, Null)->Bool` to `(Tile)->Bool`. + * Checks null, ignores index + */ + inline function ignoreIndex(func:(Tile)->Bool):(Int, Null)->Bool + { + return (_, t)->t != null && func(t); + } + + /** + * Checks all tile indices in the `column` between the specified `startRow` and `endRow`, + * finds the first tile that satisfies to condition of `func` and returns its index + * + * @param column The column to check + * @param startRow The row to check from + * @param endRow The row to check to + * @param func The condition, where `index` is the tile's map index, and `tile` is + * the tile instance for that location, or `null` if there is no instance + * @return The index of the found tile + */ + public function findIndexInColumnI(column:Int, startRow:Int, endRow:Int, func:(index:Int, tile:Null)->Bool):Int + { + if (startRow < 0) + startRow = 0; + + if (endRow < 0) + endRow = 0; + + if (startRow > heightInTiles - 1) + startRow = heightInTiles - 1; + + if (endRow > heightInTiles - 1) + endRow = heightInTiles - 1; + + var row = startRow; + final step = startRow <= endRow ? 1 : -1; while (true) { - final index = getMapIndex(x, y); + final index = getMapIndex(column, row); final tile = getTileData(index); - if (tile != null && tile.solid) + if (func(index, tile)) return index; - - if (y == endY) + + if (row == endRow) break; - - y += step; + + row += step; } return -1; From 877a893197a5befb01f6fce8e3e9b6f2aa27d7ef Mon Sep 17 00:00:00 2001 From: George FunBook Date: Fri, 10 Jan 2025 13:36:51 -0600 Subject: [PATCH 2/7] move it all to the base class --- flixel/tile/FlxBaseTilemap.hx | 334 +++++++++++++++++++++++++++++++-- flixel/tile/FlxTilemap.hx | 336 ---------------------------------- 2 files changed, 319 insertions(+), 351 deletions(-) diff --git a/flixel/tile/FlxBaseTilemap.hx b/flixel/tile/FlxBaseTilemap.hx index af98c2b069..e4569f05e7 100644 --- a/flixel/tile/FlxBaseTilemap.hx +++ b/flixel/tile/FlxBaseTilemap.hx @@ -6,6 +6,7 @@ import flixel.math.FlxPoint; import flixel.math.FlxRect; import flixel.path.FlxPathfinder; import flixel.system.FlxAssets; +import flixel.util.FlxDestroyUtil; import flixel.util.FlxArrayUtil; import flixel.util.FlxCollision; import flixel.util.FlxColor; @@ -207,18 +208,16 @@ class FlxBaseTilemap extends FlxObject * If/when it passes through a tile, it stores that point and returns false. * * **Note:** In flixel 5.0.0, this was redone, the old method is now `rayStep` - * - * @param start The world coordinates of the start of the ray. - * @param end The world coordinates of the end of the ray. - * @param result Optional result vector, to avoid creating a new instance to be returned. - * Only returned if the line enters the rect. + * + * @param start The world coordinates of the start of the ray + * @param end The world coordinates of the end of the ray + * @param result Optional result vector, indicating where the ray hit a wall * @return Returns true if the ray made it from Start to End without hitting anything. - * Returns false and fills Result if a tile was hit. + * Returns false and fills `result` if a tile was hit. */ public function ray(start:FlxPoint, end:FlxPoint, ?result:FlxPoint):Bool { throw "ray must be implemented"; - return false; } /** @@ -227,18 +226,323 @@ class FlxBaseTilemap extends FlxObject * * **Note:** In flixel 5.0.0, this was redone, the old method is now `rayStep` * - * @param start The world coordinates of the start of the ray. - * @param end The world coordinates of the end of the ray. - * @param result Optional result vector, to avoid creating a new instance to be returned. - * Only returned if the line enters the rect. - * @return Returns true if the ray made it from Start to End without hitting anything. - * Returns false and fills Result if a tile was hit. + * @param start The world coordinates of the start of the ray + * @param end The world coordinates of the end of the ray + * @param func The condition, where `tile` is the tile instance for that location */ - public function forEachOverlappingRay(start:FlxPoint, end:FlxPoint, ?func:FlxPoint):Void + inline public function forEachInRay(start:FlxPoint, end:FlxPoint, ?func:(Tile)->Void):Void { - throw "ray must be implemented"; + findIndexInRayI(start, end, voidFindIgnoreIndex(func)); + } + + /** + * Calls `func` on all tiles overlapping a ray from `start` to `end` + * + * @param start The world coordinates of the start of the ray + * @param end The world coordinates of the end of the ray + * @param func The condition, where `index` is the tile's map index, and `tile` is + * the tile instance for that location, or `null` if there is no instance + */ + inline public function forEachInRayI(start:FlxPoint, end:FlxPoint, func:(index:Int, tile:Null) -> Void) + { + findIndexInRayI(start, end, voidFind(func)); + } + + /** + * Checks all tiles overlapping a ray from `start` to `end`, + * finds the first tile that satisfies to condition of `func` and returns its index + * + * **Note:** This skips any tiles with no instance + * + * @param start The world coordinates of the start of the ray + * @param end The world coordinates of the end of the ray + * @param func The condition, where `tile` is the tile instance for that location + * @param result Optional result vector, indicating where the ray hit the found tile + * @return The index of the found tile + */ + inline public function findIndexInRay(start:FlxPoint, end:FlxPoint, func:(Tile) -> Bool, ?result:FlxPoint):Int + { + return findIndexInRayI(start, end, ignoreIndex(func), result); + } + + /** + * Checks all tile indices overlapping a ray from `start` to `end`, + * finds the first tile that satisfies to condition of `func` and returns its index + * + * @param start The world coordinates of the start of the ray + * @param end The world coordinates of the end of the ray + * @param func The condition, where `index` is the tile's map index, and `tile` is + * the tile instance for that location, or `null` if there is no instance + * @param result Optional result vector, indicating where the ray hit the found tile + * @return The index of the found tile + */ + public function findIndexInRayI(start:FlxPoint, end:FlxPoint, func:(index:Int, tile:Null) -> Bool, ?result:FlxPoint):Int + { + // trim the line to the parts inside the map + final trimmedStart = calcRayEntry(start, end); + final trimmedEnd = calcRayExit(start, end); + + start.putWeak(); + end.putWeak(); + + if (trimmedStart == null || trimmedEnd == null) + { + FlxDestroyUtil.put(trimmedStart); + FlxDestroyUtil.put(trimmedEnd); + return -1; + } + + start = trimmedStart; + end = trimmedEnd; + + inline function clearRefs() + { + trimmedStart.put(); + trimmedEnd.put(); + } + + final startIndex = getMapIndex(start); + final endIndex = getMapIndex(end); + + // If the starting tile is solid, return the starting position + final tile = getTileData(startIndex); + if (tile != null && tile.solid) + { + if (result != null) + result.copyFrom(start); + + clearRefs(); + return startIndex; + } + + final startTileX = getColumn(startIndex); + final startTileY = getRow(startIndex); + final endTileX = getColumn(endIndex); + final endTileY = getRow(endIndex); + + final scaledTileWidth = getColumnPos(1) - getColumnPos(0); + final scaledTileHeight = getRowPos(1) - getRowPos(0); + var hitIndex = -1; + + if (start.x == end.x) + { + hitIndex = findIndexInColumnI(startTileX, startTileY, endTileY, func); + if (hitIndex != -1 && result != null) + { + // check the bottom + result.copyFrom(getTilePos(hitIndex)); + result.x = start.x; + if (start.y > end.y) + result.y += scaledTileHeight; + } + } + else + { + // Use y = mx + b formula + final m = (start.y - end.y) / (start.x - end.x); + // y - mx = b + final b = start.y - m * start.x; + + final movesRight = start.x < end.x; + final inc = movesRight ? 1 : -1; + final offset = movesRight ? 1 : 0; + var tileX = startTileX; + var lastTileY = startTileY; + + while (tileX != endTileX) + { + final xPos = getColumnPos(tileX + offset); + final yPos = m * getColumnPos(tileX + offset) + b; + final tileY = getRowAt(yPos); + hitIndex = findIndexInColumnI(tileX, lastTileY, tileY, func); + if (hitIndex != -1) + break; + lastTileY = tileY; + tileX += inc; + } + + if (hitIndex == -1) + hitIndex = findIndexInColumnI(endTileX, lastTileY, endTileY, func); + + if (hitIndex != -1 && result != null) + { + result.copyFrom(getTilePos(hitIndex)); + if (Std.int(hitIndex / widthInTiles) == lastTileY) + { + if (start.x > end.x) + result.x += scaledTileWidth; + + // set result to left side + result.y = m * result.x + b; // mx + b + } + else + { + // if ascending + if (start.y > end.y) + { + // change result to bottom + result.y += scaledTileHeight; + } + // otherwise result is top + + // x = (y - b)/m + result.x = (result.y - b) / m; + } + } + } + + clearRefs(); + return hitIndex; } + /** + * Calls `func` on all tiles in the `column` between the specified `startRow` and `endRow` + * + * **Note:** This skips any tiles with no instance + * + * @param column The column to check + * @param startRow The row to check from + * @param endRow The row to check to + * @param func The function, where `tile` is the tile instance for that location + */ + inline public function forEachIndexInColumn(column:Int, startRow:Int, endRow:Int, func:(Tile)->Void) + { + findIndexInColumnI(column, startRow, endRow, voidFindIgnoreIndex(func)); + } + + /** + * Calls `func` on all tiles in the `column` between the specified `startRow` and `endRow` + * + * @param column The column to check + * @param startRow The row to check from + * @param endRow The row to check to + * @param func The function, where `index` is the tile's map index, and `tile` is + * the tile instance for that location, or `null` if there is no instance + */ + inline public function forEachIndexInColumnI(column:Int, startRow:Int, endRow:Int, func:(index:Int, tile:Null)->Bool) + { + findIndexInColumnI(column, startRow, endRow, voidFind(func)); + } + + /** + * Helper to convert `(Int, Null)->Bool` to `(Tile>)->Void`. + * Checks null, ignores index, always returns false + */ + inline function voidFindIgnoreIndex(func:(Tile)->Void):(Int, Null)->Bool + { + return function (_, t) + { + if (t != null) + func(t); + + return false; + }; + } + + /** + * Helper to convert `(Int, Null)->Bool` to `(Int, Null)->Void`. + * Always returns false + */ + inline function voidFind(func:(Int, Null)->Void):(index:Int, tile:Null)->Bool + { + return (_, t)->{ func(_, t); return false; }; + } + + + /** + * Checks all tiles in the `column` between the specified `startRow` and `endRow`, + * Retrieves the first tile that satisfies to condition of `func` and returns it + * + * **Note:** This skips any tiles with no instance + * + * @param column The column to check + * @param startRow The row to check from + * @param endRow The row to check to + * @param func The condition, where `tile` is the tile instance for that location + * @return The found tile + */ + public function findInColumn(column:Int, startRow:Int, endRow:Int, func:(Tile)->Bool):Tile + { + final index = findIndexInColumnI(column, startRow, endRow, ignoreIndex(func)); + if (index < 0) + return null; + + final data = getTileData(index); + if (data == null) + throw 'Unexpected null tile at $index'; // internal error + + return data; + } + + /** + * Checks all tiles in the `column` between the specified `startRow` and `endRow`, + * finds the first tile that satisfies to condition of `func` and returns its index + * + * **Note:** This skips any tiles with no instance + * + * @param column The column to check + * @param startRow The row to check from + * @param endRow The row to check to + * @param func The condition, where `tile` is the tile instance for that location + * @return The index of the found tile + */ + inline public function findIndexInColumn(column:Int, startRow:Int, endRow:Int, func:(Tile)->Bool):Int + { + return findIndexInColumnI(column, startRow, endRow, ignoreIndex(func)); + } + + /** + * Helper to convert `(Int, Null)->Bool` to `(Tile)->Bool`. + * Checks null, ignores index + */ + inline function ignoreIndex(func:(Tile)->Bool):(Int, Null)->Bool + { + return (_, t)->t != null && func(t); + } + + /** + * Checks all tile indices in the `column` between the specified `startRow` and `endRow`, + * finds the first tile that satisfies to condition of `func` and returns its index + * + * @param column The column to check + * @param startRow The row to check from + * @param endRow The row to check to + * @param func The condition, where `index` is the tile's map index, and `tile` is + * the tile instance for that location, or `null` if there is no instance + * @return The index of the found tile + */ + public function findIndexInColumnI(column:Int, startRow:Int, endRow:Int, func:(index:Int, tile:Null)->Bool):Int + { + if (startRow < 0) + startRow = 0; + + if (endRow < 0) + endRow = 0; + + if (startRow > heightInTiles - 1) + startRow = heightInTiles - 1; + + if (endRow > heightInTiles - 1) + endRow = heightInTiles - 1; + + var row = startRow; + final step = startRow <= endRow ? 1 : -1; + while (true) + { + final index = getMapIndex(column, row); + final tile = getTileData(index); + if (func(index, tile)) + return index; + + if (row == endRow) + break; + + row += step; + } + + return -1; + } + /** * Shoots a ray from the start point to the end point. * If/when it passes through a tile, it stores that point and returns false. diff --git a/flixel/tile/FlxTilemap.hx b/flixel/tile/FlxTilemap.hx index c81dda1391..871656a6a4 100644 --- a/flixel/tile/FlxTilemap.hx +++ b/flixel/tile/FlxTilemap.hx @@ -894,342 +894,6 @@ class FlxTypedTilemap extends FlxBaseTilemap ); } - /** - * Shoots a ray from the start point to the end point. - * If/when it passes through a tile, it stores that point and returns false. - * - * **Note:** In flixel 5.0.0, this was redone, the old method is now `rayStep` - * - * @param start The world coordinates of the start of the ray - * @param end The world coordinates of the end of the ray - * @param result Optional result vector, indicating where the ray hit a wall - * @return Returns true if the ray made it from Start to End without hitting anything. - * Returns false and fills `result` if a tile was hit. - */ - override function ray(start:FlxPoint, end:FlxPoint, ?result:FlxPoint):Bool - { - return -1 < findIndexInRayI(start, end, (_, tile)->tile != null && tile.solid, result); - } - - /** - * Calls `func` on all tiles overlapping a ray from `start` to `end` - * - * **Note:** This skips any tiles with no instance - * - * @param start The world coordinates of the start of the ray - * @param end The world coordinates of the end of the ray - * @param func The function, where `tile` is the tile instance for that location - */ - inline public function forEachInRay(start:FlxPoint, end:FlxPoint, func:(Tile)->Void) - { - findIndexInRayI(start, end, voidFindIgnoreIndex(func)); - } - - /** - * Calls `func` on all tiles overlapping a ray from `start` to `end` - * - * @param start The world coordinates of the start of the ray - * @param end The world coordinates of the end of the ray - * @param func The condition, where `index` is the tile's map index, and `tile` is - * the tile instance for that location, or `null` if there is no instance - */ - inline public function forEachInRayI(start:FlxPoint, end:FlxPoint, func:(index:Int, tile:Null)->Void) - { - findIndexInRayI(start, end, voidFind(func)); - } - - /** - * Checks all tiles overlapping a ray from `start` to `end`, - * finds the first tile that satisfies to condition of `func` and returns its index - * - * **Note:** This skips any tiles with no instance - * - * @param start The world coordinates of the start of the ray - * @param end The world coordinates of the end of the ray - * @param func The condition, where `tile` is the tile instance for that location - * @param result Optional result vector, indicating where the ray hit the found tile - * @return The index of the found tile - */ - inline public function findIndexInRay(start:FlxPoint, end:FlxPoint, func:(Tile)->Bool, ?result:FlxPoint):Int - { - return findIndexInRayI(start, end, ignoreIndex(func), result); - } - - /** - * Checks all tile indices overlapping a ray from `start` to `end`, - * finds the first tile that satisfies to condition of `func` and returns its index - * - * @param start The world coordinates of the start of the ray - * @param end The world coordinates of the end of the ray - * @param func The condition, where `index` is the tile's map index, and `tile` is - * the tile instance for that location, or `null` if there is no instance - * @param result Optional result vector, indicating where the ray hit the found tile - * @return The index of the found tile - */ - public function findIndexInRayI(start:FlxPoint, end:FlxPoint, func:(index:Int, tile:Null)->Bool, ?result:FlxPoint):Int - { - // trim the line to the parts inside the map - final trimmedStart = calcRayEntry(start, end); - final trimmedEnd = calcRayExit(start, end); - - start.putWeak(); - end.putWeak(); - - if (trimmedStart == null || trimmedEnd == null) - { - FlxDestroyUtil.put(trimmedStart); - FlxDestroyUtil.put(trimmedEnd); - return -1; - } - - start = trimmedStart; - end = trimmedEnd; - - inline function clearRefs() - { - trimmedStart.put(); - trimmedEnd.put(); - } - - final startIndex = getMapIndex(start); - final endIndex = getMapIndex(end); - - // If the starting tile is solid, return the starting position - final tile = getTileData(startIndex); - if (tile != null && tile.solid) - { - if (result != null) - result.copyFrom(start); - - clearRefs(); - return startIndex; - } - - final startTileX = getColumn(startIndex); - final startTileY = getRow(startIndex); - final endTileX = getColumn(endIndex); - final endTileY = getRow(endIndex); - var hitIndex = -1; - - if (start.x == end.x) - { - hitIndex = findIndexInColumnI(startTileX, startTileY, endTileY, func); - if (hitIndex != -1 && result != null) - { - // check the bottom - result.copyFrom(getTilePos(hitIndex)); - result.x = start.x; - if (start.y > end.y) - result.y += scaledTileHeight; - } - } - else - { - // Use y = mx + b formula - final m = (start.y - end.y) / (start.x - end.x); - // y - mx = b - final b = start.y - m * start.x; - - final movesRight = start.x < end.x; - final inc = movesRight ? 1 : -1; - final offset = movesRight ? 1 : 0; - var tileX = startTileX; - var lastTileY = startTileY; - - while (tileX != endTileX) - { - final xPos = getColumnPos(tileX + offset); - final yPos = m * getColumnPos(tileX + offset) + b; - final tileY = getRowAt(yPos); - hitIndex = findIndexInColumnI(tileX, lastTileY, tileY, func); - if (hitIndex != -1) - break; - lastTileY = tileY; - tileX += inc; - } - - if (hitIndex == -1) - hitIndex = findIndexInColumnI(endTileX, lastTileY, endTileY, func); - - if (hitIndex != -1 && result != null) - { - result.copyFrom(getTilePos(hitIndex)); - if (Std.int(hitIndex / widthInTiles) == lastTileY) - { - if (start.x > end.x) - result.x += scaledTileWidth; - - // set result to left side - result.y = m * result.x + b; // mx + b - } - else - { - // if ascending - if (start.y > end.y) - { - // change result to bottom - result.y += scaledTileHeight; - } - // otherwise result is top - - // x = (y - b)/m - result.x = (result.y - b) / m; - } - } - } - - clearRefs(); - return hitIndex; - } - - /** - * Calls `func` on all tiles in the `column` between the specified `startRow` and `endRow` - * - * **Note:** This skips any tiles with no instance - * - * @param column The column to check - * @param startRow The row to check from - * @param endRow The row to check to - * @param func The function, where `tile` is the tile instance for that location - */ - inline public function forEachIndexInColumn(column:Int, startRow:Int, endRow:Int, func:(Tile)->Void) - { - findIndexInColumnI(column, startRow, endRow, voidFindIgnoreIndex(func)); - } - - /** - * Calls `func` on all tiles in the `column` between the specified `startRow` and `endRow` - * - * @param column The column to check - * @param startRow The row to check from - * @param endRow The row to check to - * @param func The function, where `index` is the tile's map index, and `tile` is - * the tile instance for that location, or `null` if there is no instance - */ - inline public function forEachIndexInColumnI(column:Int, startRow:Int, endRow:Int, func:(index:Int, tile:Null)->Bool) - { - findIndexInColumnI(column, startRow, endRow, voidFind(func)); - } - - /** - * Helper to convert `(Int, Null)->Bool` to `(Tile>)->Void`. - * Checks null, ignores index, always returns false - */ - inline function voidFindIgnoreIndex(func:(Tile)->Void):(Int, Null)->Bool - { - return function (_, t) - { - if (t != null) - func(t); - - return false; - }; - } - - /** - * Helper to convert `(Int, Null)->Bool` to `(Int, Null)->Void`. - * Always returns false - */ - inline function voidFind(func:(Int, Null)->Void):(index:Int, tile:Null)->Bool - { - return (_, t)->{ func(_, t); return false; }; - } - - - /** - * Checks all tiles in the `column` between the specified `startRow` and `endRow`, - * Retrieves the first tile that satisfies to condition of `func` and returns it - * - * **Note:** This skips any tiles with no instance - * - * @param column The column to check - * @param startRow The row to check from - * @param endRow The row to check to - * @param func The condition, where `tile` is the tile instance for that location - * @return The found tile - */ - public function findInColumn(column:Int, startRow:Int, endRow:Int, func:(Tile)->Bool):Tile - { - final index = findIndexInColumnI(column, startRow, endRow, ignoreIndex(func)); - if (index < 0) - return null; - - final data = getTileData(index); - if (data == null) - throw 'Unexpected null tile at $index'; // internal error - - return data; - } - - /** - * Checks all tiles in the `column` between the specified `startRow` and `endRow`, - * finds the first tile that satisfies to condition of `func` and returns its index - * - * **Note:** This skips any tiles with no instance - * - * @param column The column to check - * @param startRow The row to check from - * @param endRow The row to check to - * @param func The condition, where `tile` is the tile instance for that location - * @return The index of the found tile - */ - inline public function findIndexInColumn(column:Int, startRow:Int, endRow:Int, func:(Tile)->Bool):Int - { - return findIndexInColumnI(column, startRow, endRow, ignoreIndex(func)); - } - - /** - * Helper to convert `(Int, Null)->Bool` to `(Tile)->Bool`. - * Checks null, ignores index - */ - inline function ignoreIndex(func:(Tile)->Bool):(Int, Null)->Bool - { - return (_, t)->t != null && func(t); - } - - /** - * Checks all tile indices in the `column` between the specified `startRow` and `endRow`, - * finds the first tile that satisfies to condition of `func` and returns its index - * - * @param column The column to check - * @param startRow The row to check from - * @param endRow The row to check to - * @param func The condition, where `index` is the tile's map index, and `tile` is - * the tile instance for that location, or `null` if there is no instance - * @return The index of the found tile - */ - public function findIndexInColumnI(column:Int, startRow:Int, endRow:Int, func:(index:Int, tile:Null)->Bool):Int - { - if (startRow < 0) - startRow = 0; - - if (endRow < 0) - endRow = 0; - - if (startRow > heightInTiles - 1) - startRow = heightInTiles - 1; - - if (endRow > heightInTiles - 1) - endRow = heightInTiles - 1; - - var row = startRow; - final step = startRow <= endRow ? 1 : -1; - while (true) - { - final index = getMapIndex(column, row); - final tile = getTileData(index); - if (func(index, tile)) - return index; - - if (row == endRow) - break; - - row += step; - } - - return -1; - } - /** * Shoots a ray from the start point to the end point. * If/when it passes through a tile, it stores that point and returns false. From 44f71d8360c291583f279164977dca60065c8bcb Mon Sep 17 00:00:00 2001 From: George FunBook Date: Fri, 10 Jan 2025 14:14:59 -0600 Subject: [PATCH 3/7] move more stuff to base, add getTileWidth/height --- flixel/tile/FlxBaseTilemap.hx | 82 +++++++++++++++++++++++++---------- flixel/tile/FlxTilemap.hx | 28 ++---------- 2 files changed, 64 insertions(+), 46 deletions(-) diff --git a/flixel/tile/FlxBaseTilemap.hx b/flixel/tile/FlxBaseTilemap.hx index e4569f05e7..eaf448086d 100644 --- a/flixel/tile/FlxBaseTilemap.hx +++ b/flixel/tile/FlxBaseTilemap.hx @@ -146,7 +146,12 @@ class FlxBaseTilemap extends FlxObject */ public function getColumnAt(worldX:Float, bind = false):Int { - throw "getColumnAt must be implemented"; + final result = Math.floor((worldX - x) / getTileWidth()); + + if (bind) + return result < 0 ? 0 : (result >= widthInTiles ? widthInTiles - 1 : result); + + return result; } /** @@ -159,7 +164,12 @@ class FlxBaseTilemap extends FlxObject */ public function getRowAt(worldY:Float, bind = false):Int { - throw "getRowAt must be implemented"; + final result = Math.floor((worldY - y) / getTileWidth()); + + if (bind) + return result < 0 ? 0 : (result >= heightInTiles ? heightInTiles - 1 : result); + + return result; } /** @@ -169,11 +179,11 @@ class FlxBaseTilemap extends FlxObject * @param midpoint Whether to use the tile's midpoint, or upper left corner * @since 5.9.0 */ - public function getColumnPos(column:Float, midPoint = false):Float + public function getColumnPos(column:Int, midpoint = false):Float { - throw "getColumnPos must be implemented"; + return x + column * getTileWidth() + (midpoint ? getTileWidth() * 0.5 : 0); } - + /** * Get the world position of the specified row * @@ -181,9 +191,29 @@ class FlxBaseTilemap extends FlxObject * @param midpoint Whether to use the tile's midpoint, or upper left corner * @since 5.9.0 */ - public function getRowPos(row:Int, midPoint = false):Float + public function getRowPos(row:Int, midpoint = false):Float + { + return y + row * getTileHeight() + (midpoint ? getTileHeight() * 0.5 : 0); + } + + /** + * Get the size of a column, in world coordinates + * + * @since 5.10.0 + */ + public function getTileWidth():Float { - throw "getRowPos must be implemented"; + throw "getTileWidth must be implemented"; + } + + /** + * Get the size of a column, in world coordinates + * + * @since 5.10.0 + */ + public function getTileHeight():Float + { + throw "getTileHeight must be implemented"; } /** @@ -204,31 +234,31 @@ class FlxBaseTilemap extends FlxObject } /** - * Shoots a ray from the start point to the end point. - * If/when it passes through a tile, it stores that point and returns false. + * Determines whether the ray can travel from `start` to `end` without hitting a solid wall * * **Note:** In flixel 5.0.0, this was redone, the old method is now `rayStep` * * @param start The world coordinates of the start of the ray * @param end The world coordinates of the end of the ray - * @param result Optional result vector, indicating where the ray hit a wall - * @return Returns true if the ray made it from Start to End without hitting anything. - * Returns false and fills `result` if a tile was hit. + * @param result Optional result vector, indicating where the ray hit the first wall + * @return Whether the ray can travel from `start` to `end` without hitting a solid wall */ public function ray(start:FlxPoint, end:FlxPoint, ?result:FlxPoint):Bool { - throw "ray must be implemented"; + // TODO: only check for collisions in the direction needed + // This requires findIndexInRayI to pass intersection data back + return findIndexInRayI(start, end, (_, t)->t != null && t.solid, result); } /** - * Shoots a ray from the start point to the end point. - * If/when it passes through a tile, it stores that point and returns false. + * Calls `func` on all tiles overlapping a ray from `start` to `end` * * **Note:** In flixel 5.0.0, this was redone, the old method is now `rayStep` * * @param start The world coordinates of the start of the ray * @param end The world coordinates of the end of the ray - * @param func The condition, where `tile` is the tile instance for that location + * @param func The function, where `tile` is the tile instance for that location + * @since 5.10.0 */ inline public function forEachInRay(start:FlxPoint, end:FlxPoint, ?func:(Tile)->Void):Void { @@ -240,8 +270,9 @@ class FlxBaseTilemap extends FlxObject * * @param start The world coordinates of the start of the ray * @param end The world coordinates of the end of the ray - * @param func The condition, where `index` is the tile's map index, and `tile` is + * @param func The function, where `index` is the tile's map index, and `tile` is * the tile instance for that location, or `null` if there is no instance + * @since 5.10.0 */ inline public function forEachInRayI(start:FlxPoint, end:FlxPoint, func:(index:Int, tile:Null) -> Void) { @@ -259,6 +290,7 @@ class FlxBaseTilemap extends FlxObject * @param func The condition, where `tile` is the tile instance for that location * @param result Optional result vector, indicating where the ray hit the found tile * @return The index of the found tile + * @since 5.10.0 */ inline public function findIndexInRay(start:FlxPoint, end:FlxPoint, func:(Tile) -> Bool, ?result:FlxPoint):Int { @@ -275,6 +307,7 @@ class FlxBaseTilemap extends FlxObject * the tile instance for that location, or `null` if there is no instance * @param result Optional result vector, indicating where the ray hit the found tile * @return The index of the found tile + * @since 5.10.0 */ public function findIndexInRayI(start:FlxPoint, end:FlxPoint, func:(index:Int, tile:Null) -> Bool, ?result:FlxPoint):Int { @@ -320,8 +353,8 @@ class FlxBaseTilemap extends FlxObject final endTileX = getColumn(endIndex); final endTileY = getRow(endIndex); - final scaledTileWidth = getColumnPos(1) - getColumnPos(0); - final scaledTileHeight = getRowPos(1) - getRowPos(0); + final tileWidth = getTileWidth(); + final tileHeight = getTileHeight(); var hitIndex = -1; if (start.x == end.x) @@ -333,7 +366,7 @@ class FlxBaseTilemap extends FlxObject result.copyFrom(getTilePos(hitIndex)); result.x = start.x; if (start.y > end.y) - result.y += scaledTileHeight; + result.y += tileHeight; } } else @@ -370,7 +403,7 @@ class FlxBaseTilemap extends FlxObject if (Std.int(hitIndex / widthInTiles) == lastTileY) { if (start.x > end.x) - result.x += scaledTileWidth; + result.x += tileWidth; // set result to left side result.y = m * result.x + b; // mx + b @@ -381,7 +414,7 @@ class FlxBaseTilemap extends FlxObject if (start.y > end.y) { // change result to bottom - result.y += scaledTileHeight; + result.y += tileHeight; } // otherwise result is top @@ -404,6 +437,7 @@ class FlxBaseTilemap extends FlxObject * @param startRow The row to check from * @param endRow The row to check to * @param func The function, where `tile` is the tile instance for that location + * @since 5.10.0 */ inline public function forEachIndexInColumn(column:Int, startRow:Int, endRow:Int, func:(Tile)->Void) { @@ -418,6 +452,7 @@ class FlxBaseTilemap extends FlxObject * @param endRow The row to check to * @param func The function, where `index` is the tile's map index, and `tile` is * the tile instance for that location, or `null` if there is no instance + * @since 5.10.0 */ inline public function forEachIndexInColumnI(column:Int, startRow:Int, endRow:Int, func:(index:Int, tile:Null)->Bool) { @@ -460,6 +495,7 @@ class FlxBaseTilemap extends FlxObject * @param endRow The row to check to * @param func The condition, where `tile` is the tile instance for that location * @return The found tile + * @since 5.10.0 */ public function findInColumn(column:Int, startRow:Int, endRow:Int, func:(Tile)->Bool):Tile { @@ -485,6 +521,7 @@ class FlxBaseTilemap extends FlxObject * @param endRow The row to check to * @param func The condition, where `tile` is the tile instance for that location * @return The index of the found tile + * @since 5.10.0 */ inline public function findIndexInColumn(column:Int, startRow:Int, endRow:Int, func:(Tile)->Bool):Int { @@ -510,6 +547,7 @@ class FlxBaseTilemap extends FlxObject * @param func The condition, where `index` is the tile's map index, and `tile` is * the tile instance for that location, or `null` if there is no instance * @return The index of the found tile + * @since 5.10.0 */ public function findIndexInColumnI(column:Int, startRow:Int, endRow:Int, func:(index:Int, tile:Null)->Bool):Int { diff --git a/flixel/tile/FlxTilemap.hx b/flixel/tile/FlxTilemap.hx index 871656a6a4..4e829ded5f 100644 --- a/flixel/tile/FlxTilemap.hx +++ b/flixel/tile/FlxTilemap.hx @@ -830,36 +830,16 @@ class FlxTypedTilemap extends FlxBaseTilemap return results; } - override function getColumnAt(worldX:Float, bind = false):Int + override function getTileWidth() { - final result = Math.floor((worldX - x) / scaledTileWidth); - - if (bind) - return result < 0 ? 0 : (result >= widthInTiles ? widthInTiles - 1 : result); - - return result; + return scaledTileWidth; } - override function getRowAt(worldY:Float, bind = false):Int + override function getTileHeight() { - final result = Math.floor((worldY - y) / scaledTileHeight); - - if (bind) - return result < 0 ? 0 : (result >= heightInTiles ? heightInTiles -1 : result); - - return result; + return scaledTileHeight; } - override function getColumnPos(column:Float, midpoint = false):Float - { - return x + column * scaledTileWidth + (midpoint ? scaledTileWidth * 0.5 : 0); - } - - override function getRowPos(row:Int, midpoint = false):Float - { - return y + row * scaledTileHeight + (midpoint ? scaledTileHeight * 0.5 : 0); - } - /** * Returns a new array full of every coordinate of the requested tile type. * From 4d51bd9381a5f8f208f4ddb0795cdcb1ffdb73c1 Mon Sep 17 00:00:00 2001 From: George FunBook Date: Fri, 10 Jan 2025 17:25:37 -0600 Subject: [PATCH 4/7] fix typo --- flixel/tile/FlxBaseTilemap.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flixel/tile/FlxBaseTilemap.hx b/flixel/tile/FlxBaseTilemap.hx index eaf448086d..b41d113c07 100644 --- a/flixel/tile/FlxBaseTilemap.hx +++ b/flixel/tile/FlxBaseTilemap.hx @@ -247,7 +247,7 @@ class FlxBaseTilemap extends FlxObject { // TODO: only check for collisions in the direction needed // This requires findIndexInRayI to pass intersection data back - return findIndexInRayI(start, end, (_, t)->t != null && t.solid, result); + return -1 < findIndexInRayI(start, end, (_, t)->t != null && t.solid, result); } /** From c929076db15d98ffbcf685ad1027abcaac171d46 Mon Sep 17 00:00:00 2001 From: George FunBook Date: Fri, 10 Jan 2025 17:52:08 -0600 Subject: [PATCH 5/7] add more posAt helpers --- flixel/tile/FlxBaseTilemap.hx | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/flixel/tile/FlxBaseTilemap.hx b/flixel/tile/FlxBaseTilemap.hx index b41d113c07..d5f2121729 100644 --- a/flixel/tile/FlxBaseTilemap.hx +++ b/flixel/tile/FlxBaseTilemap.hx @@ -196,6 +196,30 @@ class FlxBaseTilemap extends FlxObject return y + row * getTileHeight() + (midpoint ? getTileHeight() * 0.5 : 0); } + /** + * Get the world position of the column at the specified location + * + * @param worldX An X coordinate in the world + * @param midpoint Whether to use the tile's midpoint, or left edge + * @since 5.10.0 + */ + public function getColumnPosAt(worldX:Float, midpoint = false):Float + { + return getColumnPos(getColumnAt(worldX), midPoint); + } + + /** + * Get the world position of the row at the specified location + * + * @param worldY An X coordinate in the world + * @param midpoint Whether to use the tile's midpoint, or upper edge + * @since 5.10.0 + */ + public function getRowPosAt(worldY:Float, midpoint = false):Float + { + return getRowPos(getRowAt(worldY), midPoint); + } + /** * Get the size of a column, in world coordinates * From 8702aa6d26e9214f2575b6eb4db5bd9967fb0e7d Mon Sep 17 00:00:00 2001 From: George FunBook Date: Fri, 10 Jan 2025 17:53:37 -0600 Subject: [PATCH 6/7] D'OH --- flixel/tile/FlxBaseTilemap.hx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flixel/tile/FlxBaseTilemap.hx b/flixel/tile/FlxBaseTilemap.hx index d5f2121729..05ea3d0bc0 100644 --- a/flixel/tile/FlxBaseTilemap.hx +++ b/flixel/tile/FlxBaseTilemap.hx @@ -205,7 +205,7 @@ class FlxBaseTilemap extends FlxObject */ public function getColumnPosAt(worldX:Float, midpoint = false):Float { - return getColumnPos(getColumnAt(worldX), midPoint); + return getColumnPos(getColumnAt(worldX), midpoint); } /** @@ -217,7 +217,7 @@ class FlxBaseTilemap extends FlxObject */ public function getRowPosAt(worldY:Float, midpoint = false):Float { - return getRowPos(getRowAt(worldY), midPoint); + return getRowPos(getRowAt(worldY), midpoint); } /** From ff04f6983e4f34e6c85b95261291f4be6e4786d6 Mon Sep 17 00:00:00 2001 From: George FunBook Date: Mon, 13 Jan 2025 12:27:42 -0600 Subject: [PATCH 7/7] add FlxRayResult and many helpers --- flixel/tile/FlxBaseTilemap.hx | 731 ++++++++++++++++++++++++++-------- 1 file changed, 554 insertions(+), 177 deletions(-) diff --git a/flixel/tile/FlxBaseTilemap.hx b/flixel/tile/FlxBaseTilemap.hx index 05ea3d0bc0..1e7e8c3915 100644 --- a/flixel/tile/FlxBaseTilemap.hx +++ b/flixel/tile/FlxBaseTilemap.hx @@ -10,11 +10,14 @@ import flixel.util.FlxDestroyUtil; import flixel.util.FlxArrayUtil; import flixel.util.FlxCollision; import flixel.util.FlxColor; +import flixel.util.FlxDirection; import flixel.util.FlxDirectionFlags; import flixel.util.FlxStringUtil; import openfl.display.BitmapData; using StringTools; +using flixel.tile.FlxBaseTilemap.RayTools; +using flixel.tile.FlxBaseTilemap.AmbiIntIterator; @:autoBuild(flixel.system.macros.FlxMacroUtil.deprecateOverride("overlapsWithCallback", "overlapsWithCallback is deprecated, use objectOverlapsTiles")) class FlxBaseTilemap extends FlxObject @@ -257,36 +260,97 @@ class FlxBaseTilemap extends FlxObject return getTilePos(mapIndex, midpoint); } + // ============================================================================= + //{ region Ray + Helpers + // ============================================================================= + /** - * Determines whether the ray can travel from `start` to `end` without hitting a solid wall + * Determines whether the ray can travel from `start` to `end` without hitting a solid wall. * * **Note:** In flixel 5.0.0, this was redone, the old method is now `rayStep` * + * @param start The world coordinates of the start of the ray + * @param end The world coordinates of the end of the ray + * @param result Optional result vector, indicating where the ray hit the first wall + * @param checkDir Whether this method honors the tiles' `allowCollisions` directions + * @return Whether the ray can travel from `start` to `end` without hitting a wall + */ + public function ray(start:FlxPoint, end:FlxPoint, ?result:FlxPoint, checkDir = false):Bool + { + final func = checkDir ? checkRayDirHelper(start, end) : (_, t:Tile, _)->t != null && t.solid; + switch findInRayHelper(start, end, func) + { + case END: + return true; + + case STOPPED(index, x, y, entry): + if (result != null) + result.set(x, y); + + return false; + } + } + + /** + * Ray func helper, checks tiles' directions compared to the ray direction + */ + function checkRayDirHelper(start:FlxPoint, end:FlxPoint):FindRayFuncI + { + return function (i:Int, tile:Null, entry:FlxRayEntry) + { + return tile != null && switch entry + { + case EDGE(dir): + tile.allowCollisions.has(dir); + + case START: tile.allowCollisions == ANY + || (tile.allowCollisions.left && start.x < end.x) + || (tile.allowCollisions.right && start.x > end.x) + || (tile.allowCollisions.up && start.y < end.y) + || (tile.allowCollisions.down && start.y > end.y); + } + }; + } + + /** + * Calls `func` on all tiles overlapping a ray from `start` to `end` + * * @param start The world coordinates of the start of the ray * @param end The world coordinates of the end of the ray - * @param result Optional result vector, indicating where the ray hit the first wall - * @return Whether the ray can travel from `start` to `end` without hitting a solid wall + * @param func The function, where `tile` is the tile data at that location + * @since 5.10.0 */ - public function ray(start:FlxPoint, end:FlxPoint, ?result:FlxPoint):Bool + inline overload extern public function forEachInRay(start, end, func:(Tile)->Void) { - // TODO: only check for collisions in the direction needed - // This requires findIndexInRayI to pass intersection data back - return -1 < findIndexInRayI(start, end, (_, t)->t != null && t.solid, result); + findIndexInRayHelper(start, end, func.toFindIE()); } - + /** * Calls `func` on all tiles overlapping a ray from `start` to `end` * - * **Note:** In flixel 5.0.0, this was redone, the old method is now `rayStep` + * @param start The world coordinates of the start of the ray + * @param end The world coordinates of the end of the ray + * @param func The function, where `index` is the tile's map index, and `tile` is + * the tile data at that location, if one exists + * @since 5.10.0 + */ + inline overload extern public function forEachInRay(start, end, func:(index:Int, tile:Null)->Bool) + { + findIndexInRayHelper(start, end, func.toFindIE()); + } + + /** + * Calls `func` on all tiles overlapping a ray from `start` to `end` * * @param start The world coordinates of the start of the ray * @param end The world coordinates of the end of the ray - * @param func The function, where `tile` is the tile instance for that location + * @param func The function, where `tile` is the tile data at that location and + * `entry` is how the ray entered the tile * @since 5.10.0 */ - inline public function forEachInRay(start:FlxPoint, end:FlxPoint, ?func:(Tile)->Void):Void + inline overload extern public function forEachInRay(start, end, func:(tile:Tile, entry:FlxRayEntry)->Void) { - findIndexInRayI(start, end, voidFindIgnoreIndex(func)); + findIndexInRayHelper(start, end, func.toFindIE()); } /** @@ -294,13 +358,13 @@ class FlxBaseTilemap extends FlxObject * * @param start The world coordinates of the start of the ray * @param end The world coordinates of the end of the ray - * @param func The function, where `index` is the tile's map index, and `tile` is - * the tile instance for that location, or `null` if there is no instance + * @param func The function, where `index` is the tile's map index, `tile` is the tile data + * at that location, if one exists and `entry` is how the ray entered the tile * @since 5.10.0 */ - inline public function forEachInRayI(start:FlxPoint, end:FlxPoint, func:(index:Int, tile:Null) -> Void) + inline overload extern public function forEachInRay(start, end, func:(index:Int, tile:Null, entry:FlxRayEntry)->Bool) { - findIndexInRayI(start, end, voidFind(func)); + findIndexInRayHelper(start, end, func.toFindIE()); } /** @@ -311,14 +375,13 @@ class FlxBaseTilemap extends FlxObject * * @param start The world coordinates of the start of the ray * @param end The world coordinates of the end of the ray - * @param func The condition, where `tile` is the tile instance for that location - * @param result Optional result vector, indicating where the ray hit the found tile + * @param func The stopping condition, where `tile` is the tile data at that location * @return The index of the found tile * @since 5.10.0 */ - inline public function findIndexInRay(start:FlxPoint, end:FlxPoint, func:(Tile) -> Bool, ?result:FlxPoint):Int + inline overload extern public function findIndexInRay(start, end, func:(Tile)->Bool) { - return findIndexInRayI(start, end, ignoreIndex(func), result); + return findIndexInRayHelper(start, end, func.toFindIE()); } /** @@ -327,13 +390,99 @@ class FlxBaseTilemap extends FlxObject * * @param start The world coordinates of the start of the ray * @param end The world coordinates of the end of the ray - * @param func The condition, where `index` is the tile's map index, and `tile` is - * the tile instance for that location, or `null` if there is no instance - * @param result Optional result vector, indicating where the ray hit the found tile + * @param func The stopping condition, where `index` is the tile's map index, `tile` is the + * tile data at that location, if one exists * @return The index of the found tile * @since 5.10.0 */ - public function findIndexInRayI(start:FlxPoint, end:FlxPoint, func:(index:Int, tile:Null) -> Bool, ?result:FlxPoint):Int + inline overload extern public function findIndexInRay + (start, end, func:(index:Int, tile:Null)->Bool) + { + return findIndexInRayHelper(start, end, func.toFindIE()); + } + + /** + * Checks all tile indices overlapping a ray from `start` to `end`, + * finds the first tile that satisfies to condition of `func` and returns its index + * + * @param start The world coordinates of the start of the ray + * @param end The world coordinates of the end of the ray + * @param func The stopping condition, where `index` is the tile's map index, `tile` is the tile data + * at that location, if one exists, `entry` is how the ray entered the tile + * @return The index of the found tile + * @since 5.10.0 + */ + inline overload extern public function findIndexInRay + (start, end, func:(index:Int, tile:Null, entry:FlxRayEntry)->Bool) + { + return findIndexInRayHelper(start, end, func); + } + + function findIndexInRayHelper + (start, end, func:(index:Int, tile:Null, entry:FlxRayEntry)->Bool):Int + { + switch findInRayHelper(start, end, func) + { + case END: + return -1; + case STOPPED(mapIndex, _, _, _): + return mapIndex; + } + } + + /** + * Checks all tiles overlapping a ray from `start` to `end`, + * finds the first tile that satisfies to condition of `func` + * + * **Note:** This skips any tiles with no instance + * + * @param start The world coordinates of the start of the ray + * @param end The world coordinates of the end of the ray + * @param func The stopping condition, where `tile` is the tile data at that location + * @return The result of the ray, whether it reached the end or was stopped, and where + * @since 5.10.0 + */ + inline overload extern public function findInRay(start, end, func:(Tile)->Bool) + { + return findInRayHelper(start, end, func.toFindIE()); + } + + /** + * Checks all tile indices overlapping a ray from `start` to `end`, + * finds the first tile that satisfies to condition of `func` + * + * @param start The world coordinates of the start of the ray + * @param end The world coordinates of the end of the ray + * @param func The stopping condition, where `index` is the tile's map index, `tile` is the + * tile data at that location, if one exists + * @return The result of the ray, whether it reached the end or was stopped, and where + * @since 5.10.0 + */ + inline overload extern public function findInRay + (start, end, func:(index:Int, tile:Null)->Bool) + { + return findInRayHelper(start, end, func.toFindIE()); + } + + /** + * Checks all tile indices overlapping a ray from `start` to `end`, + * finds the first tile that satisfies to condition of `func` + * + * @param start The world coordinates of the start of the ray + * @param end The world coordinates of the end of the ray + * @param func The stopping condition, where `index` is the tile's map index, `tile` is the tile data + * at that location, if one exists, `entry` is how the ray entered the tile + * @return The result of the ray, whether it reached the end or was stopped, and where + * @since 5.10.0 + */ + inline overload extern public function findInRay + (start, end, func:(index:Int, tile:Null, entry:FlxRayEntry)->Bool) + { + return findInRayHelper(start, end, func); + } + + function findInRayHelper + (start:FlxPoint, end:FlxPoint, func:(index:Int, tile:Null, entry:FlxRayEntry)->Bool):FlxRayResult { // trim the line to the parts inside the map final trimmedStart = calcRayEntry(start, end); @@ -342,114 +491,134 @@ class FlxBaseTilemap extends FlxObject start.putWeak(); end.putWeak(); - if (trimmedStart == null || trimmedEnd == null) - { - FlxDestroyUtil.put(trimmedStart); - FlxDestroyUtil.put(trimmedEnd); - return -1; - } - - start = trimmedStart; - end = trimmedEnd; - - inline function clearRefs() - { - trimmedStart.put(); - trimmedEnd.put(); - } - - final startIndex = getMapIndex(start); - final endIndex = getMapIndex(end); + if (trimmedStart == null && trimmedEnd == null) + return END; - // If the starting tile is solid, return the starting position - final tile = getTileData(startIndex); - if (tile != null && tile.solid) - { - if (result != null) - result.copyFrom(start); - - clearRefs(); - return startIndex; - } + // Cache x/y in floats so we can put() them now + final wasStartTrimmed = trimmedStart.x != start.x || trimmedStart.y != start.y; + final startX = trimmedStart.x; + final startY = trimmedStart.y; + final endX = trimmedEnd.x; + final endY = trimmedEnd.y; + trimmedStart.put(); + trimmedEnd.put(); + final startIndex = getMapIndexAt(startX, startY); + final endIndex = getMapIndexAt(endX, endY); final startTileX = getColumn(startIndex); final startTileY = getRow(startIndex); final endTileX = getColumn(endIndex); final endTileY = getRow(endIndex); - final tileWidth = getTileWidth(); - final tileHeight = getTileHeight(); - var hitIndex = -1; - + // handle vertical line (infinite slope), first if (start.x == end.x) { - hitIndex = findIndexInColumnI(startTileX, startTileY, endTileY, func); - if (hitIndex != -1 && result != null) + // did the ray start here or from outside the map + final entry = wasStartTrimmed ? START : EDGE(start.y < end.y ? UP : DOWN); + final result = findIndexInColumnWithEntry(startTileX, startTileY, endTileY, func, entry); + if (result != null) { // check the bottom - result.copyFrom(getTilePos(hitIndex)); - result.x = start.x; - if (start.y > end.y) - result.y += tileHeight; + final resultX = start.x; + final resultY = getRowPos(getRow(result.index + (start.y > end.y ? 1 : 0))); + return STOPPED(result.index, resultX, resultY, result.entry); } + + return END; } - else + + // Use y = mx + b formula + final m = (start.y - end.y) / (start.x - end.x); + // y - mx = b + final b = start.y - m * start.x; + + final movesRight = start.x < end.x; + final inc = movesRight ? 1 : -1; + final offset = movesRight ? 1 : 0; + var entry = wasStartTrimmed ? START : EDGE(movesRight ? LEFT : RIGHT); + var lastTileY = startTileY; + + for (tileX in startTileX.iter(endTileX)) { - // Use y = mx + b formula - final m = (start.y - end.y) / (start.x - end.x); - // y - mx = b - final b = start.y - m * start.x; - - final movesRight = start.x < end.x; - final inc = movesRight ? 1 : -1; - final offset = movesRight ? 1 : 0; - var tileX = startTileX; - var lastTileY = startTileY; - - while (tileX != endTileX) - { - final xPos = getColumnPos(tileX + offset); - final yPos = m * getColumnPos(tileX + offset) + b; - final tileY = getRowAt(yPos); - hitIndex = findIndexInColumnI(tileX, lastTileY, tileY, func); - if (hitIndex != -1) - break; - lastTileY = tileY; - tileX += inc; - } + final xPos = getColumnPos(tileX + offset); + final yPos = ambiClamp(m * getColumnPos(tileX + offset) + b, startY, endY); + final tileY = getRowAt(yPos); + final result = findIndexInColumnWithEntry(tileX, lastTileY, tileY, func, entry); + if (result != null) + return calcRayResult(result, m, b, start); - if (hitIndex == -1) - hitIndex = findIndexInColumnI(endTileX, lastTileY, endTileY, func); - - if (hitIndex != -1 && result != null) - { - result.copyFrom(getTilePos(hitIndex)); - if (Std.int(hitIndex / widthInTiles) == lastTileY) - { - if (start.x > end.x) - result.x += tileWidth; - - // set result to left side - result.y = m * result.x + b; // mx + b - } - else - { - // if ascending - if (start.y > end.y) - { - // change result to bottom - result.y += tileHeight; - } - // otherwise result is top - - // x = (y - b)/m - result.x = (result.y - b) / m; - } - } + entry = EDGE(movesRight ? LEFT : RIGHT); + lastTileY = tileY; } - clearRefs(); - return hitIndex; + return END; + } + + /** + * Helper to clamp between to values, regardless of which is smaller + */ + function ambiClamp(value:Float, a:Float, b:Float):Float + { + if (a > b) + return ambiClamp(value, b, a); + + if (value < a) + return a; + + if (value > b) + return b; + + return value; + } + + /** + * Helper for retriving ray entry results + */ + static final _rayResultHelper = new RayResultHelper(); + /** + * Helper to add an `entry` to `findIndexInColumn` callbacks + * + * @param column The column to check + * @param startRow The row to check from + * @param endRow The row to check to + * @param func The function, where `tile` is the tile data at that location + * @param entry How the ray entered this column + */ + function findIndexInColumnWithEntry + (column, startRow, endRow, func:FindRayFuncI, entry:FlxRayEntry):RayResultHelper + { + final startI = getMapIndex(column, startRow); + final edge = EDGE(startRow < endRow ? UP : DOWN); + final f = (i, t)->func(i, t, i == startI ? entry : edge); + final index = findIndexInColumn(column, startRow, endRow, f); + if (index == -1) + return null; + return _rayResultHelper.set(index, index == startI ? entry : edge); + } + + function calcRayResult(data:RayResultHelper, m:Float, b:Float, start:FlxPoint):FlxRayResult + { + return switch data.entry + { + case START: + STOPPED(data.index, start.x, start.y, data.entry); + case EDGE(LEFT): + final x = getColumnPos(getColumn(data.index)); + final y = m * x + b; + STOPPED(data.index, x, y, data.entry); + case EDGE(RIGHT): + final x = getColumnPos(getColumn(data.index)) + getTileWidth(); + final y = m * x + b; + STOPPED(data.index, x, y, data.entry); + case EDGE(UP): + final y = getRowPos(getRow(data.index)); + final x = (y - b) / m; + STOPPED(data.index, x, y, data.entry); + case EDGE(DOWN): + final y = getRowPos(getRow(data.index)) + getTileHeight(); + final x = (y - b) / m; + STOPPED(data.index, x, y, data.entry); + } } /** @@ -460,12 +629,12 @@ class FlxBaseTilemap extends FlxObject * @param column The column to check * @param startRow The row to check from * @param endRow The row to check to - * @param func The function, where `tile` is the tile instance for that location + * @param func The function, where `tile` is the tile data at that location * @since 5.10.0 */ - inline public function forEachIndexInColumn(column:Int, startRow:Int, endRow:Int, func:(Tile)->Void) + inline overload extern public function forEachIndexInColumn(column, startRow, endRow, func:(Tile)->Void) { - findIndexInColumnI(column, startRow, endRow, voidFindIgnoreIndex(func)); + findIndexInColumnHelper(column, startRow, endRow, func.toFindI()); } /** @@ -475,39 +644,14 @@ class FlxBaseTilemap extends FlxObject * @param startRow The row to check from * @param endRow The row to check to * @param func The function, where `index` is the tile's map index, and `tile` is - * the tile instance for that location, or `null` if there is no instance + * the tile data at that location, if one exists * @since 5.10.0 */ - inline public function forEachIndexInColumnI(column:Int, startRow:Int, endRow:Int, func:(index:Int, tile:Null)->Bool) - { - findIndexInColumnI(column, startRow, endRow, voidFind(func)); - } - - /** - * Helper to convert `(Int, Null)->Bool` to `(Tile>)->Void`. - * Checks null, ignores index, always returns false - */ - inline function voidFindIgnoreIndex(func:(Tile)->Void):(Int, Null)->Bool - { - return function (_, t) - { - if (t != null) - func(t); - - return false; - }; - } - - /** - * Helper to convert `(Int, Null)->Bool` to `(Int, Null)->Void`. - * Always returns false - */ - inline function voidFind(func:(Int, Null)->Void):(index:Int, tile:Null)->Bool + inline overload extern public function forEachIndexInColumn(column, startRow, endRow, func:(index:Int, tile:Null)->Void) { - return (_, t)->{ func(_, t); return false; }; + findIndexInColumnHelper(column, startRow, endRow, func.toFindI()); } - /** * Checks all tiles in the `column` between the specified `startRow` and `endRow`, * Retrieves the first tile that satisfies to condition of `func` and returns it @@ -517,13 +661,18 @@ class FlxBaseTilemap extends FlxObject * @param column The column to check * @param startRow The row to check from * @param endRow The row to check to - * @param func The condition, where `tile` is the tile instance for that location + * @param func The stopping condition, where `tile` is the tile data at that location * @return The found tile * @since 5.10.0 */ - public function findInColumn(column:Int, startRow:Int, endRow:Int, func:(Tile)->Bool):Tile + inline overload extern public function findInColumn(column, startRow, endRow, func:(Tile)->Bool) { - final index = findIndexInColumnI(column, startRow, endRow, ignoreIndex(func)); + return findInColumnHelper(column, startRow, endRow, func); + } + + function findInColumnHelper(column:Int, startRow:Int, endRow:Int, func:(Tile)->Bool):Tile + { + final index = findIndexInColumnHelper(column, startRow, endRow, func.ignoreIndex()); if (index < 0) return null; @@ -543,22 +692,13 @@ class FlxBaseTilemap extends FlxObject * @param column The column to check * @param startRow The row to check from * @param endRow The row to check to - * @param func The condition, where `tile` is the tile instance for that location + * @param func The stopping condition, where `tile` is the tile data at that location * @return The index of the found tile * @since 5.10.0 */ - inline public function findIndexInColumn(column:Int, startRow:Int, endRow:Int, func:(Tile)->Bool):Int - { - return findIndexInColumnI(column, startRow, endRow, ignoreIndex(func)); - } - - /** - * Helper to convert `(Int, Null)->Bool` to `(Tile)->Bool`. - * Checks null, ignores index - */ - inline function ignoreIndex(func:(Tile)->Bool):(Int, Null)->Bool + inline overload extern public function findIndexInColumn(column, startRow, endRow, func:(Tile)->Bool) { - return (_, t)->t != null && func(t); + return findIndexInColumnHelper(column, startRow, endRow, func.ignoreIndex()); } /** @@ -568,38 +708,34 @@ class FlxBaseTilemap extends FlxObject * @param column The column to check * @param startRow The row to check from * @param endRow The row to check to - * @param func The condition, where `index` is the tile's map index, and `tile` is - * the tile instance for that location, or `null` if there is no instance + * @param func The stopping condition, where `index` is the tile's map index, and + * `tile` is the tile data at that location, if one exists * @return The index of the found tile * @since 5.10.0 */ - public function findIndexInColumnI(column:Int, startRow:Int, endRow:Int, func:(index:Int, tile:Null)->Bool):Int + inline overload extern public function findIndexInColumn(column, startRow, endRow, func:(index:Int, tile:Null)->Bool) + { + return findIndexInColumnHelper(column, startRow, endRow, func); + } + + function findIndexInColumnHelper(column:Int, startRow:Int, endRow:Int, func:(index:Int, tile:Null)->Bool):Int { if (startRow < 0) startRow = 0; + else if (startRow > heightInTiles - 1) + startRow = heightInTiles - 1; if (endRow < 0) endRow = 0; - - if (startRow > heightInTiles - 1) - startRow = heightInTiles - 1; - - if (endRow > heightInTiles - 1) + else if (endRow > heightInTiles - 1) endRow = heightInTiles - 1; - var row = startRow; - final step = startRow <= endRow ? 1 : -1; - while (true) + for (row in startRow.iter(endRow)) { final index = getMapIndex(column, row); final tile = getTileData(index); if (func(index, tile)) return index; - - if (row == endRow) - break; - - row += step; } return -1; @@ -670,6 +806,9 @@ class FlxBaseTilemap extends FlxObject return calcRayEntry(end, start, result); } + //} endregion Ray + Helpers + // ============================================================================= + /** * Searches all tiles near the object for any that satisfy the given filter. Stops searching * when the first overlapping tile that satisfies the condition is found @@ -2035,3 +2174,241 @@ enum FlxTilemapAutoTiling */ FULL; } + +// ============================================================================= +//{ region Ray + Helpers +// ============================================================================= + +/** + * `(index:Int, tile:Null, entry:FlxRayEntry) -> Bool` + * The callback function for helpers like `findIndexInRayI` + * + * @param index The index of the tile in the map, where 0 is the top-left + * @param tile The tile instance, if one exists + * @param entry How the ray entered the tile + * @return Whether this tile matches the search condition + */ +typedef FindRayFuncI = (index:Int, tile:Null, entry:FlxRayEntry) -> Bool; + +/** + * `(index:Int, tile:Null, entry:FlxRayEntry) -> Void` + * The callback function for helpers like `forEachIndexInRayI` + * + * @param index The index of the tile in the map, where 0 is the top-left + * @param tile The tile instance, if one exists + * @param entry How the ray entered the tile + */ +typedef ForEachRayFuncI = (index:Int, tile:Null, entry:FlxRayEntry) -> Void; + +/** + * `(tile:Tile, entry:FlxRayEntry) -> Bool` + * The callback function for helpers like `findIndexInRay` + * + * @param tile The tile instance + * @param entry How the ray entered the tile + * @return Whether this tile matches the search condition + */ +typedef FindRayFunc = (tile:Tile, entry:FlxRayEntry) -> Bool; + +/** + * `(tile:Tile, entry:FlxRayEntry) -> Void` + * The callback function for helpers like `forEachIndexInRay` + * + * @param tile The tile instance + * @param entry How the ray entered the tile + */ +typedef ForEachRayFunc = (tile:Tile, entry:FlxRayEntry) -> Void; + +/** + * `(index:Int, tile:Null) -> Bool` + * The callback function for helpers like `findIndexInRayI` + * + * @param index The index of the tile in the map, where 0 is the top-left + * @param tile The tile instance, if one exists + * @return Whether this tile matches the search condition + */ +typedef FindTileFuncI = (index:Int, tile:Null) -> Bool; + +/** + * `(index:Int, tile:Null) -> Void` + * The callback function for helpers like `forEachIndexInRayI` + * + * @param index The index of the tile in the map, where 0 is the top-left + * @param tile The tile instance, if one exists + */ +typedef ForEachTileFuncI = (index:Int, tile:Null) -> Void; + +/** + * `(tile:Tile) -> Bool` + * The callback function for helpers like `findIndexInRay` + * + * @param tile The tile instance + * @return Whether this tile matches the search condition + */ +typedef FindTileFunc = (tile:Tile) -> Bool; + +/** + * `(tile:Tile) -> Void` + * The callback function for helpers like `forEachIndexInRay` + * + * @param tile The tile instance + */ +typedef ForEachTileFunc = (tile:Tile) -> Void; + +enum FlxRayEntry +{ + EDGE(dir:FlxDirection); + /** + * The ray started in the tile + */ + START; +} + +private class RayResultHelper +{ + public var index:Int = 0; + public var entry:FlxRayEntry = START; + + public function new () {} + public function set(index, entry) + { + this.index = index; + this.entry = entry; + return this; + } +} + +enum FlxRayResult +{ + /** + * The ray reached a stopping tile + */ + STOPPED(mapIndex:Int, x:Float, y:Float, entry:FlxRayEntry); + + /** + * The ray reached the end without being stopped + */ + END; +} + +private class RayTools +{ + inline overload extern static public function ignoreIndex(func:FindTileFunc):FindTileFuncI + { + return (_, t)->t != null && func(t); + } + + inline overload extern static public function toFindIE(func:FindRayFunc):FindRayFuncI + { + return (_, t, e)->t != null && func(t, e); + } + + inline overload extern static public function toFindIE(func:FindTileFunc):FindRayFuncI + { + return (_, t, _)->t != null && func(t); + } + + /** + * Helper to convert `(Int, Null)->Bool` to `(Tile)->Void`. + * Checks null, ignores index, always returns false + */ + inline overload extern static public function toFindI(func:ForEachTileFunc):FindTileFuncI + { + return function (_, t) + { + if (t != null) + func(t); + + return false; + }; + } + + /** + * Helper to convert a `(Int, Null)->Bool` to `(Int, Null)->Void`. + * Always returns false + */ + inline overload extern static public function toFindI(func:ForEachTileFuncI):FindTileFuncI + { + return (i, t)->{ func(i, t); return false; }; + } + + /** + * Helper to convert `(Tile)->Void` to `(Int, Null, FlxRayEntry)->Bool`. + * Checks null, ignores index, always returns false + */ + inline overload extern static public function toFindIE(func:ForEachTileFunc):FindRayFuncI + { + return function (_, t, _) + { + if (t != null) + func(t); + + return false; + }; + } + + /** + * Helper to convert a `(Int, Null)->Bool` to `(Int, Null, FlxRayEntry)->Void`. + * Always returns false + */ + inline overload extern static public function toFindIE(func:ForEachTileFuncI):FindRayFuncI + { + return (i, t, _)->{ func(i, t); return false; }; + } + + /** + * Helper to convert `(Int, Null, FlxRayEntry)->Bool` to `(Tile, FlxRayEntry)->Void`. + * Checks null, ignores index, always returns false + */ + inline overload extern static public function toFindIE(func:ForEachRayFunc):FindRayFuncI + { + return function (_, t, e) + { + if (t != null) + func(t, e); + + return false; + }; + } + + /** + * Helper to convert `(Int, Null)->Bool` to `(Int, Null)->Void`. + * Always returns false + */ + inline overload extern static public function toFindIE(func:ForEachRayFuncI):FindRayFuncI + { + return (i, t, e)->{ func(i, t, e); return false; }; + } +} +private class AmbiIntIterator +{ + final start:Int; + final iterator:IntIterator; + final step:Int; + + inline public function new(start:Int, end:Int, inclusive = true) + { + this.start = start; + this.step = start < end ? 1 : -1; + final dis = (end - start) * step + (inclusive ? 1 : 0); + iterator = (0... dis); + } + + inline public function hasNext() + { + return iterator.hasNext(); + } + + inline public function next() + { + return start + iterator.next() * step; + } + + inline static public function iter(start:Int, end:Int, inclusive = true) + { + return new AmbiIntIterator(start, end, inclusive); + } +} + +//} endregion Ray + Helpers +// ============================================================================= \ No newline at end of file