Skip to content

Instantly share code, notes, and snippets.

@snuffyDev
Created June 28, 2023 00:59
Show Gist options
  • Save snuffyDev/076ea425f698b31d5a0b935e4e4626b1 to your computer and use it in GitHub Desktop.
Save snuffyDev/076ea425f698b31d5a0b935e4e4626b1 to your computer and use it in GitHub Desktop.
import type { ExtendedEntityV2 } from "$lib/types/core/map";
import type { Position2D } from "$lib/types/position";
type Portal = {
position: Position2D;
connectedRegion: number;
};
type Region = {
portals: Portal[];
connectedRegions: number[];
tiles: Position2D[];
};
function preprocessLevel(level: ExtendedEntityV2[][]): Region[] {
const regions: Region[] = [];
const visitedTiles: Set<string> = new Set();
function findConnectedRegion(tile: Position2D, regionIndex: number): void {
const region = regions[regionIndex];
const { x, z } = tile;
const key = `${x},${z}`;
if (visitedTiles.has(key)) {
return;
}
visitedTiles.add(key);
region.tiles.push(tile);
const adjacentTiles: Position2D[] = [
{ x: x, z: z - 1 }, // left
{ x: x, z: z + 1 }, // right
{ x: x - 1, z: z }, // front
{ x: x + 1, z: z } // back
];
for (const adjacentTile of adjacentTiles) {
const { x: adjX, z: adjZ } = adjacentTile;
if (adjX >= 0 && adjX < level.length && adjZ >= 0 && adjZ < level[adjZ].length) {
const adjacentTileData = level[adjZ][adjX];
const adjacentTileKey = `${adjX},${adjZ}`;
if (adjacentTileData.component !== "Object" || visitedTiles.has(adjacentTileKey)) {
continue;
}
if (
adjacentTileData.component === "Door" &&
adjacentTileData.attributes?.state === "closed"
) {
const connectedRegion = regionIndex;
region.portals.push({
position: adjacentTile,
connectedRegion
});
region.connectedRegions.push(connectedRegion);
} else if (adjacentTileData.blocking !== false) {
region.tiles.push(adjacentTileData.position);
visitedTiles.add(adjacentTileKey);
if (visitedTiles.has(adjacentTileKey)) {
continue;
}
findConnectedRegion(adjacentTile, regionIndex);
} else if (adjacentTileData.component) {
// if (visitedTiles.has(adjacentTileKey)) {
// continue;
// }
region.tiles.push(adjacentTileData.position);
visitedTiles.add(adjacentTileKey);
// findConnectedRegion(adjacentTile, regionIndex);
if (visitedTiles.has(adjacentTileKey)) {
continue;
}
}
}
}
}
function findPortalsInRegion(regionIndex: number): void {
const region = regions[regionIndex];
for (const portal of region.portals) {
const { position, connectedRegion } = portal;
findConnectedRegion(position, connectedRegion);
}
}
for (let x = 0; x < level.length; x++) {
for (let z = 0; z < level[x].length; z++) {
const tile = level[z][x];
if (!visitedTiles.has(`${x},${z}`) && tile.component === "Door") {
const region: Region = {
portals: [],
connectedRegions: [],
tiles: []
};
regions.push(region);
findConnectedRegion({ x, z }, regions.length - 1);
}
}
}
for (let i = 0; i < regions.length; i++) {
findPortalsInRegion(i);
}
return regions;
}
export { Portal, Region, preprocessLevel };
export function raycast(origin: Position2D, rotation: number): string[] {
const visiblePositions: string[] = [];
let stepSize = 0.5; // This determines the resolution of the raycast
let maxSteps = 10000; // This is a safeguard to prevent infinite loops in case of errors
forLoop: for (let step = 0; step < maxSteps; step++) {
let index = 0;
let x = origin.x + step * stepSize * Math.cos(rotation);
let z = origin.z + step * stepSize * Math.sin(rotation);
let tilePos: Position2D = { x: Math.floor(x + 0.5), z: Math.floor(z + 0.5) };
test: while (++index < 2) {
const entity = CurrentLevel.checkCollisionWithWorld(tilePos, true);
if (entity !== null) {
visiblePositions.push(`${tilePos.x}${tilePos.z}`);
if (entity) {
break forLoop;
}
continue test;
}
}
}
return visiblePositions;
}
export function castRays(
viewer: Position2D,
viewerRotation: number,
models: Model[],
fov: number,
callback: (model: Model, state: boolean) => void
): void {
// Calculate the rotation angles for the leftmost and rightmost rays
// Calculate the rotation angles for the leftmost and rightmost rays
const leftRotation = viewerRotation - fov / 2;
const rightRotation = viewerRotation + fov / 2;
// Calculate the number of rays to cast within the field of view
const numRays = ~~(Math.ceil((window.innerWidth / window.innerHeight) * 180) * Math.PI);
const rayVisibleEntities: string[] = [];
// Cast the rays
for (let i = 0; i < numRays; i++) {
// Interpolate the rotation angle for this ray
const rotation = normalizeAngle(
leftRotation + ((rightRotation - leftRotation) * i) / (numRays - 1)
);
// add the visible entities to the list
rayVisibleEntities.push(...raycast(viewer, rotation));
}
// Check each model's visibility
for (const entity of models) {
const position = entity.getLocalPosition();
const isVisible = rayVisibleEntities.includes(`${position.x}${position.z}`);
callback(entity, isVisible);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment