fix(babylon_setup.js): fix exit not generating properly
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -126,6 +126,7 @@ let chestMap = new Map(); // key: "x,y" -> {mesh, opened}
|
|||||||
let keyChestKey = null;
|
let keyChestKey = null;
|
||||||
let exitBox = null;
|
let exitBox = null;
|
||||||
let exitGridPos = null; // track exit grid position for collision checking
|
let exitGridPos = null; // track exit grid position for collision checking
|
||||||
|
let spawnGridPos = null; // track spawn grid position for validation
|
||||||
let spawnMarker = null;
|
let spawnMarker = null;
|
||||||
const cellSize = 2;
|
const cellSize = 2;
|
||||||
|
|
||||||
@@ -139,6 +140,7 @@ function clearLevelMeshes() {
|
|||||||
if (exitBox) { try { exitBox.dispose(); } catch(e){}; exitBox = null; }
|
if (exitBox) { try { exitBox.dispose(); } catch(e){}; exitBox = null; }
|
||||||
if (spawnMarker) { try { spawnMarker.dispose(); } catch(e){}; spawnMarker = null; }
|
if (spawnMarker) { try { spawnMarker.dispose(); } catch(e){}; spawnMarker = null; }
|
||||||
exitGridPos = null;
|
exitGridPos = null;
|
||||||
|
spawnGridPos = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple seeded RNG
|
// Simple seeded RNG
|
||||||
@@ -209,6 +211,26 @@ function findDeadEnds(grid) {
|
|||||||
return dead;
|
return dead;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function gridCellToWorld(grid, x, y) {
|
||||||
|
const halfW = (grid[0].length * cellSize) / 2;
|
||||||
|
const halfH = (grid.length * cellSize) / 2;
|
||||||
|
return new BABYLON.Vector3(
|
||||||
|
x * cellSize - halfW + cellSize / 2,
|
||||||
|
0,
|
||||||
|
y * cellSize - halfH + cellSize / 2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isWalkableCell(grid, x, y) {
|
||||||
|
return y >= 0 && y < grid.length && x >= 0 && x < grid[0].length && grid[y][x] === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isReservedCell(x, y) {
|
||||||
|
if (chestMap.has(`${x},${y}`)) return true;
|
||||||
|
if (exitGridPos && exitGridPos.x === x && exitGridPos.y === y) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function buildLevelFromGrid(grid) {
|
function buildLevelFromGrid(grid) {
|
||||||
clearLevelMeshes();
|
clearLevelMeshes();
|
||||||
const h = grid.length;
|
const h = grid.length;
|
||||||
@@ -295,48 +317,78 @@ function placeExit(grid, seed) {
|
|||||||
exitGridPos = { x, y };
|
exitGridPos = { x, y };
|
||||||
}
|
}
|
||||||
|
|
||||||
const [x,y] = exitGridPos;
|
const [x,y] = [exitGridPos.x, exitGridPos.y];
|
||||||
const halfW = (grid[0].length * cellSize) / 2;
|
if (!isWalkableCell(grid, x, y)) {
|
||||||
const halfH = (grid.length * cellSize) / 2;
|
console.warn("Exit selected on non-walkable cell", { x, y });
|
||||||
const ex = x*cellSize - halfW + cellSize/2;
|
return;
|
||||||
const ez = y*cellSize - halfH + cellSize/2;
|
}
|
||||||
|
const exitWorld = gridCellToWorld(grid, x, y);
|
||||||
|
const ex = exitWorld.x;
|
||||||
|
const ez = exitWorld.z;
|
||||||
|
const worldSpan = Math.max(grid[0].length, grid.length) * cellSize;
|
||||||
|
|
||||||
// Ground plane indicator
|
// Ground plane indicator (larger + slightly raised to avoid z-fighting)
|
||||||
const plane = BABYLON.MeshBuilder.CreateGround('exitZone', { width: cellSize*0.9, height: cellSize*0.9 }, scene);
|
const plane = BABYLON.MeshBuilder.CreateGround('exitZone', { width: cellSize*1.4, height: cellSize*1.4 }, scene);
|
||||||
plane.material = new BABYLON.StandardMaterial('exitMat', scene);
|
const exitMat = new BABYLON.StandardMaterial('exitMat', scene);
|
||||||
plane.material.emissiveColor = new BABYLON.Color3(0.9, 0.8, 0.2);
|
exitMat.diffuseColor = new BABYLON.Color3(0.25, 0.2, 0.03);
|
||||||
plane.position = new BABYLON.Vector3(ex, 0.01, ez);
|
exitMat.emissiveColor = new BABYLON.Color3(1.0, 0.85, 0.15);
|
||||||
|
exitMat.disableLighting = true;
|
||||||
|
plane.material = exitMat;
|
||||||
|
plane.position = new BABYLON.Vector3(ex, 0.08, ez);
|
||||||
exitBox = plane;
|
exitBox = plane;
|
||||||
levelMeshes.push(plane);
|
levelMeshes.push(plane);
|
||||||
|
|
||||||
// Tall pillar for visibility
|
// Very tall beacon for visibility over maze walls
|
||||||
const pillar = BABYLON.MeshBuilder.CreateCylinder('exitPillar', { diameter: cellSize*0.3, height: cellSize*3.0 }, scene);
|
const beaconHeight = Math.max(18, worldSpan * 0.8);
|
||||||
|
const pillar = BABYLON.MeshBuilder.CreateCylinder('exitPillar', { diameter: cellSize*0.55, height: beaconHeight }, scene);
|
||||||
const pillarMat = new BABYLON.StandardMaterial('exitPillarMat', scene);
|
const pillarMat = new BABYLON.StandardMaterial('exitPillarMat', scene);
|
||||||
pillarMat.diffuseColor = new BABYLON.Color3(0.95, 0.8, 0.1);
|
pillarMat.diffuseColor = new BABYLON.Color3(0.95, 0.8, 0.1);
|
||||||
pillarMat.emissiveColor = new BABYLON.Color3(0.3, 0.25, 0.05);
|
pillarMat.emissiveColor = new BABYLON.Color3(1.0, 0.7, 0.1);
|
||||||
|
pillarMat.disableLighting = true;
|
||||||
pillar.material = pillarMat;
|
pillar.material = pillarMat;
|
||||||
pillar.position = new BABYLON.Vector3(ex, cellSize*0.75, ez);
|
pillar.position = new BABYLON.Vector3(ex, beaconHeight * 0.5, ez);
|
||||||
levelMeshes.push(pillar);
|
levelMeshes.push(pillar);
|
||||||
|
|
||||||
|
// Bright orb at the top of beacon
|
||||||
|
const orb = BABYLON.MeshBuilder.CreateSphere('exitOrb', { diameter: cellSize * 1.0 }, scene);
|
||||||
|
const orbMat = new BABYLON.StandardMaterial('exitOrbMat', scene);
|
||||||
|
orbMat.diffuseColor = new BABYLON.Color3(1.0, 0.9, 0.3);
|
||||||
|
orbMat.emissiveColor = new BABYLON.Color3(1.0, 0.9, 0.35);
|
||||||
|
orbMat.disableLighting = true;
|
||||||
|
orb.material = orbMat;
|
||||||
|
orb.position = new BABYLON.Vector3(ex, beaconHeight + cellSize * 0.7, ez);
|
||||||
|
levelMeshes.push(orb);
|
||||||
}
|
}
|
||||||
|
|
||||||
function spawnCameraAt(grid) {
|
function spawnCameraAt(grid) {
|
||||||
const h = grid.length, w = grid[0].length;
|
const h = grid.length;
|
||||||
|
const w = grid[0].length;
|
||||||
|
let bestCell = null;
|
||||||
|
let bestDist = -1;
|
||||||
|
|
||||||
|
// Choose a valid spawn that is far from exit when possible.
|
||||||
for (let y = 1; y < h - 1; y++) {
|
for (let y = 1; y < h - 1; y++) {
|
||||||
for (let x = 1; x < w - 1; x++) {
|
for (let x = 1; x < w - 1; x++) {
|
||||||
// Skip walls
|
if (!isWalkableCell(grid, x, y)) continue;
|
||||||
if (grid[y][x] !== 0) continue;
|
if (isReservedCell(x, y)) continue;
|
||||||
|
|
||||||
// Skip chests
|
const d = exitGridPos ? Math.hypot(x - exitGridPos.x, y - exitGridPos.y) : 0;
|
||||||
if (chestMap.has(`${x},${y}`)) continue;
|
if (d > bestDist) {
|
||||||
|
bestDist = d;
|
||||||
|
bestCell = { x, y };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Skip exit
|
if (!bestCell) {
|
||||||
if (exitGridPos && exitGridPos.x === x && exitGridPos.y === y) continue;
|
console.warn("No valid spawn cell found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Found valid spawn point
|
spawnGridPos = bestCell;
|
||||||
const halfW = (w * cellSize) / 2;
|
const spawnWorld = gridCellToWorld(grid, bestCell.x, bestCell.y);
|
||||||
const halfH = (h * cellSize) / 2;
|
const px = spawnWorld.x;
|
||||||
const px = x*cellSize - halfW + cellSize/2;
|
const pz = spawnWorld.z;
|
||||||
const pz = y*cellSize - halfH + cellSize/2;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (camera && camera.position) {
|
if (camera && camera.position) {
|
||||||
@@ -344,7 +396,6 @@ function spawnCameraAt(grid) {
|
|||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
// Add spawn marker sphere
|
|
||||||
if (spawnMarker) {
|
if (spawnMarker) {
|
||||||
try { spawnMarker.dispose(); } catch(e) {}
|
try { spawnMarker.dispose(); } catch(e) {}
|
||||||
}
|
}
|
||||||
@@ -356,10 +407,6 @@ function spawnCameraAt(grid) {
|
|||||||
marker.position = new BABYLON.Vector3(px, cellSize*0.2, pz);
|
marker.position = new BABYLON.Vector3(px, cellSize*0.2, pz);
|
||||||
spawnMarker = marker;
|
spawnMarker = marker;
|
||||||
levelMeshes.push(marker);
|
levelMeshes.push(marker);
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateLevel() {
|
function generateLevel() {
|
||||||
@@ -374,7 +421,20 @@ function generateLevel() {
|
|||||||
placeChestsOnDeadEnds(grid, dead, cfg.minChestDeadEnds, seed + cfg.level);
|
placeChestsOnDeadEnds(grid, dead, cfg.minChestDeadEnds, seed + cfg.level);
|
||||||
placeExit(grid, seed + cfg.level);
|
placeExit(grid, seed + cfg.level);
|
||||||
spawnCameraAt(grid);
|
spawnCameraAt(grid);
|
||||||
sharedState.runtime.message = `Level ${cfg.level} generated.`;
|
|
||||||
|
const placementValid =
|
||||||
|
!!exitGridPos &&
|
||||||
|
!!spawnGridPos &&
|
||||||
|
isWalkableCell(grid, exitGridPos.x, exitGridPos.y) &&
|
||||||
|
isWalkableCell(grid, spawnGridPos.x, spawnGridPos.y) &&
|
||||||
|
!(exitGridPos.x === spawnGridPos.x && exitGridPos.y === spawnGridPos.y);
|
||||||
|
|
||||||
|
if (!placementValid) {
|
||||||
|
sharedState.runtime.message = `Placement warning: spawn/exit invalid on level ${cfg.level}.`;
|
||||||
|
console.warn("Invalid spawn/exit placement", { exitGridPos, spawnGridPos });
|
||||||
|
} else {
|
||||||
|
sharedState.runtime.message = `Level ${cfg.level} generated (spawn ${spawnGridPos.x},${spawnGridPos.y} / exit ${exitGridPos.x},${exitGridPos.y}).`;
|
||||||
|
}
|
||||||
window.requestAnimationFrame(()=>{ /* let scene update */ });
|
window.requestAnimationFrame(()=>{ /* let scene update */ });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user