Endless backrooms in Babylon.js!

I made backrooms on the babylon engine, move using the arrows ←, →, ↑, ↓ on the keyboard and mouse.

PC version

Original backrooms

In the zero version, I added an infinite procedurally generated carpet.

Backrooms in Babylon.js (Version 0) | Babylon.js Playground

In the first version, I added randomly generated and randomly rotated walls to this procedurally generated carpet.

Backrooms in Babylon.js (Version 1) | Babylon.js Playground

In the second version, I added an infinite procedurally generated ceiling.

Backrooms in Babylon.js (Version 2) | Babylon.js Playground

In the third version, I added infinitely procedurally generated evenly spaced lamps.

Backrooms in Babylon.js (Version 3) | Babylon.js Playground

Kane Pixel’s version

In the first version, I added the ability to add structure generation based on the faces and vertex arrays. You just need to fly a little to the side to see the generation.

Kane Pixel’s Backrooms in Babylon.js (Version 0) | Babylon.js Playground

In the second version, I added different biomes, and now not one biome is generated, but many.

Kane Pixel’s Backrooms in Babylon.js (Version 1 - multybiom generation) | Babylon.js Playground

But something is still missing, because Kane Pixel’s backrooms are an endless layered multi-storey pie of randomly procedurally generated, interconnected liminal spaces.

Kane Pixel’s Backrooms in Babylon.js (Version 2 - Multistory multibiome generation) | Babylon.js Playground

Mobile version

Original mobile backrooms

But while browsing my backrooms from my smartphone, I found a problem that I can’t navigate on my phone, and therefore in the mobile version of backrooms I added arrows with which I can navigate.

Backrooms on Babylon.js (Mobile version 3) | Babylon.js Playground

Kane Pixel’s mobile version

Kane Pixel’s Backrooms on Babylon.js (Mobile version 2) | Babylon.js Playground

P.S.

Now it’s a full-fledged backroom space! Maybe someone will come up with some new projects and the project will develop.

11 Likes

Have a try with Instances as a replacement of MeshBuilder, create once, add instances then。

The point is not that, but that the structures are generated in each other and do not merge when they intersect! There was an idea to rewrite the union function python on babylon, but nothing good came of it…

function createChunk(position, rotation, scene) {
    const vertices = [
        [-6.0, -2.0, -6.0], [6.0, -2.0, -6.0], [6.0, -2.0, -0.75],
        [-6.0, -2.0, 6.0], [6.0, -2.0, 0.75], [6.0, -2.0, 6.0],
        [-6.0, 2.0, -6.0], [6.0, 0.0, -0.75], [6.0, 2.0, -6.0],
        [-6.0, 2.0, 6.0], [6.0, 0.0, 0.75], [6.0, 2.0, 6.0],
        [25.0, -2.0, -0.75], [25.0, -2.0, 0.75], [25.0, 0.0, -0.75],
        [25.0, 0.0, 0.75]
    ];

    const faces = [
        [0, 1, 3], [1, 0, 6], [2, 3, 1],
        [2, 1, 8], [3, 9, 0], [4, 3, 2],
        [4, 10, 5], [3, 4, 5], [11, 3, 5],
        [6, 0, 9], [1, 6, 8], [7, 2, 8],
        [7, 8, 10], [8, 6, 11], [9, 3, 11],
        [9, 11, 6], [11, 5, 10], [8, 11, 10],
        [2, 12, 4], [7, 14, 2], [12, 2, 14],
        [12, 14, 13], [4, 13, 10], [4, 12, 13],
        [15, 10, 13], [15, 13, 14], [7, 15, 14],
        [10, 15, 7]
    ];

    return createMeshFromArrays(vertices, faces, scene, position, rotation);
}

function createMeshFromArrays(vertices, faces, scene, position, rotation) {
    const positions = [];
    const indices = [];
    const uvs = [];

    vertices.forEach(vertex => {
        positions.push(vertex[0], vertex[1], vertex[2]);

        // Add UV coordinates for each vertex
        uvs.push(vertex[0] / 12 + 0.5, vertex[2] / 12 + 0.5);
    });

    faces.forEach(face => {
        indices.push(face[0], face[1], face[2]);
        indices.push(face[2], face[1], face[0]);
    });

    const mesh = new BABYLON.Mesh("customMesh", scene);
    const vertexData = new BABYLON.VertexData();

    vertexData.positions = positions;
    vertexData.indices = indices;
    vertexData.uvs = uvs;

    vertexData.applyToMesh(mesh);

    const material = new BABYLON.StandardMaterial("material", scene);
    material.diffuseTexture = new BABYLON.Texture("https://i.postimg.cc/Pq7xgZY0/wallpaper.jpg", scene);
    material.backFaceCulling = false;

    mesh.material = material;

    // Устанавливаем положение и поворот
    mesh.position = new BABYLON.Vector3(position[0], position[1], position[2]);
    mesh.rotation.y = rotation; // Поворот вокруг оси Y

    return mesh;
}

function unionMeshes(mesh1, mesh2) {
    const positions1 = [];
    const indices1 = [];
    const uvs1 = [];

    mesh1.vertexData.positions.forEach((position, index) => {
        positions1.push(position);
        indices1.push(index);
    });

    mesh1.vertexData.uvs.forEach((uv, index) => {
        uvs1.push(uv);
    });

    const positions2 = [];
    const indices2 = [];
    const uvs2 = [];

    mesh2.vertexData.positions.forEach((position, index) => {
        positions2.push(position);
    });

    mesh2.vertexData.indices.forEach((index) => {
        indices2.push(index + mesh1.vertexData.positions.length / 3);
    });

    mesh2.vertexData.uvs.forEach((uv, index) => {
        uvs2.push(uv);
    });

    const combinedPositions = positions1.concat(positions2);
    const combinedIndices = indices1.concat(indices2);
    const combinedUvs = uvs1.concat(uvs2);

    const combinedMesh = new BABYLON.Mesh("combinedMesh", scene);
    const vertexData = new BABYLON.VertexData();

    vertexData.positions = combinedPositions;
    vertexData.indices = combinedIndices;
    vertexData.uvs = combinedUvs;

    vertexData.applyToMesh(combinedMesh);

    return combinedMesh;
}

var createScene = function () {
    var scene = new BABYLON.Scene(engine);
    var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene);
    camera.setTarget(BABYLON.Vector3.Zero());
    camera.attachControl(canvas, true);
    var light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
    light.intensity = 0.7;

    const numChunks = 10;
    let combinedMesh = null;

    for (let i = 0; i < numChunks; i++) {
        const position = [Math.random() * 30 - 15, 1, Math.random() * 30 - 15];
        const rotation = Math.random() * Math.PI * 2; // Случайный угол поворота

        const mesh = createChunk(position, rotation, scene);

        if (combinedMesh === null) {
            combinedMesh = mesh;
        } else {
            combinedMesh = unionMeshes(combinedMesh, mesh);
        }
    }

    return scene;
};

Do you have any ideas?

merging solid meshes? have a try with CSG2

It doesn’t work.

It doesn’t work | Babylon.js Playground

It doesn’t work. Example 2. | Babylon.js Playground

It doesn’t work. Example 3. | Babylon.js Playground

I also have no idea how to make different textures for different faces, I made several unsuccessful attempts, and if anyone has any ideas, please suggest them.

let existingChunks = {};
const CHUNK_SIZE = 100; // Размер чанка
const MAX_HEIGHT = 2; // Максимальная высота для генерации чанков

// Измените этот массив на свои биомы
const biomes = [
    {
        vertices: [[-11.140454, -1.0, 6.045034], [-5.874499, -1.000002, -25.147928], ...],
        verticalTexture: "https://i.postimg.cc/Pq7xgZY0/wallpaper.jpg",
        topHorizontalTexture: "https://i.postimg.cc/y83ChCs2/image.png",
        bottomHorizontalTexture: "https://i.postimg.cc/wvStLGvz/image.jpg"
    },
    {    
        vertices: [[-7.000378, 0.999999, -4.244266], [-6.353668, 0.999999, -5.322138], ...],
        faces: [[0, 2, 25], [25, 2, 1], ...],
        verticalTexture: "https://i.postimg.cc/Pq7xgZY0/wallpaper.jpg",
        topHorizontalTexture: "https://i.postimg.cc/y83ChCs2/image.png",
        bottomHorizontalTexture: "https://i.postimg.cc/wvStLGvz/image.jpg"
    }
];
// Функция для создания коридора
function createCorridor(chunkId, x, y, z, scene) {
    const biome = biomes[Math.floor(Math.random() * biomes.length)]; // Случайный выбор биома
    return createMeshFromArrays(biome.vertices, biome.faces, scene, chunkId, x, y, z, biome);
}

// Функция для создания сцены в Babylon.js
async function createScene() {
    const engine = new BABYLON.Engine(canvas, true);
    const scene = new BABYLON.Scene(engine);

    const camera = new BABYLON.FreeCamera("FreeCam", new BABYLON.Vector3(0, 10, -10), scene);
    camera.setTarget(BABYLON.Vector3.Zero());
    camera.attachControl(canvas, true);

    new BABYLON.HemisphericLight("Hemispheric Light", new BABYLON.Vector3(1, 1, 0), scene).intensity = 0.7;

    scene.registerBeforeRender(() => {
        GenerateChunks(camera.position, scene);
    });

    engine.runRenderLoop(() => {
        scene.render();
    });

    return scene;
}

// Функция для генерации чанков коридоров
function GenerateChunks(position, scene) {
    const chunkX = Math.floor(position.x / CHUNK_SIZE);
    const chunkZ = Math.floor(position.z / CHUNK_SIZE);
    const chunkY = Math.floor(position.y / CHUNK_SIZE); // Новая ось Y

    for (let x = chunkX - 2; x <= chunkX + 2; x++) {
        for (let z = chunkZ - 2; z <= chunkZ + 2; z++) {
            for (let y = chunkY - MAX_HEIGHT; y <= chunkY + MAX_HEIGHT; y++) { // Генерация по Y
                const chunkId = `${x},${y},${z}`; // Обновленный идентификатор чанка
                if (!existingChunks[chunkId]) {
                    createCorridor(chunkId, x, y * CHUNK_SIZE, z, scene); // Обновленный вызов
                    existingChunks[chunkId] = true; // Сохраняем существующий чанк
                }
            }
        }
    }
}

// Обновленная функция createMeshFromArrays для работы с коридорами
function createMeshFromArrays(vertices, faces, scene, chunkId, x, y, z, biome) {
    const positions = [];
    const indices = [];
    const uvs = [];

    vertices.forEach(vertex => {
        positions.push(vertex[0] + x * CHUNK_SIZE, vertex[1] + y, vertex[2] + z * CHUNK_SIZE); // Учитываем ось Y
    });

    let faceCount = 0;

    faces.forEach(face => {
        const startIndex = faceCount * 3; // Каждая грань содержит 3 индекса
        indices.push(face[0], face[1], face[2]); // Индексы вершин
        // Обратный порядок
        indices.push(face[2], face[1], face[0]);
        faceCount++;
    });

    const mesh = new BABYLON.Mesh(chunkId, scene);
    const vertexData = new BABYLON.VertexData();

    vertexData.positions = positions;
    vertexData.indices = indices;

    // Установка UV-кординат
    const uv = [0, 0, 1, 0, 1, 1, 0, 1];
    vertexData.uvs = new Array(indices.length).fill(0).map((_, i) => uv[i % 4]); // Повторяем UV для каждой грани
    vertexData.applyToMesh(mesh);

    // Создание материалов
    const verticalMaterial = new BABYLON.StandardMaterial(`verticalMaterial_${chunkId}`, scene);
    verticalMaterial.diffuseTexture = new BABYLON.Texture(biome.verticalTexture, scene);

    const topMaterial = new BABYLON.StandardMaterial(`topMaterial_${chunkId}`, scene);
    topMaterial.diffuseTexture = new BABYLON.Texture(biome.topHorizontalTexture, scene);

    const bottomMaterial = new BABYLON.StandardMaterial(`bottomMaterial_${chunkId}`, scene);
    bottomMaterial.diffuseTexture = new BABYLON.Texture(biome.bottomHorizontalTexture, scene);

    // Создаем подмеши для каждой грани
    let subMeshCount = faces.length; // Количество граней
    for (let i = 0; i < subMeshCount; i++) {
        const startIndex = i * 6; // Каждый подмеш содержит 2 треугольника (6 индексов)
        const endIndex = startIndex + 6;

        const subMesh = new BABYLON.SubMesh(i, 0, positions.length / 3, startIndex, endIndex, mesh);
        
        // Применяем правильный материал к каждой грани
        if (i === 0) { // Условие для верхней грани
            subMesh.materialIndex = topMaterial.id;
        } else if (i === 1) { // Условие для нижней грани
            subMesh.materialIndex = bottomMaterial.id;
        } else {
            subMesh.materialIndex = verticalMaterial.id; // Для остальных вертикальных граней
        }
    }

    return mesh;
}

// Запуск инициализации сцены
createScene();

Example 2

const CHUNK_SIZE = 100; // Размер чанка
const MAX_HEIGHT = 2; // Максимальная высота для генерации чанков

// Измените этот массив на свои биомы
const biomes = [
    {
        vertices: [[-11.140454, -1.0, 6.045034], [-5.874499, -1.000002, -25.147928], ...],
        faces: [[10, 0, 35], [1, 3, 2], ...],
        verticalTexture: "https://i.postimg.cc/Pq7xgZY0/wallpaper.jpg",
        topHorizontalTexture: "https://i.postimg.cc/y83ChCs2/image.png",
        bottomHorizontalTexture: "https://i.postimg.cc/wvStLGvz/image.jpg"
    },
    {
        vertices: [[-7.000378, 0.999999, -4.244266], [-6.353668, 0.999999, -5.322138], ...],
        faces: [[0, 2, 25], [25, 2, 1], ...],
        verticalTexture: "https://i.postimg.cc/Pq7xgZY0/wallpaper.jpg",
        topHorizontalTexture: "https://i.postimg.cc/y83ChCs2/image.png",
        bottomHorizontalTexture: "https://i.postimg.cc/wvStLGvz/image.jpg"
    }
];

// Функция для создания коридора
function createCorridor(chunkId, x, y, z, scene) {
    const biome = biomes[Math.floor(Math.random() * biomes.length)]; // Случайный выбор биома
    return createMeshFromArrays(biome.vertices, biome.faces, scene, chunkId, x, y, z, biome);
}

// Функция для создания сцены в Babylon.js
async function createScene() {
    const engine = new BABYLON.Engine(canvas, true);
    const scene = new BABYLON.Scene(engine);

    const camera = new BABYLON.FreeCamera("FreeCam", new BABYLON.Vector3(0, 10, -10), scene);
    camera.setTarget(BABYLON.Vector3.Zero());
    camera.attachControl(canvas, true);

    new BABYLON.HemisphericLight("Hemispheric Light", new BABYLON.Vector3(1, 1, 0), scene).intensity = 0.7;

    scene.registerBeforeRender(() => {
        GenerateChunks(camera.position, scene);
    });

    engine.runRenderLoop(() => {
        scene.render();
    });

    return scene;
}

// Функция для генерации чанков коридоров
function GenerateChunks(position, scene) {
    const chunkX = Math.floor(position.x / CHUNK_SIZE);
    const chunkZ = Math.floor(position.z / CHUNK_SIZE);
    const chunkY = Math.floor(position.y / CHUNK_SIZE); // Новая ось Y

    for (let x = chunkX - 2; x <= chunkX + 2; x++) {
        for (let z = chunkZ - 2; z <= chunkZ + 2; z++) {
            for (let y = chunkY - MAX_HEIGHT; y <= chunkY + MAX_HEIGHT; y++) { // Генерация по Y
                const chunkId = `${x},${y},${z}`; // Обновленный идентификатор чанка
                if (!existingChunks[chunkId]) {
                    createCorridor(chunkId, x, y * CHUNK_SIZE, z, scene); // Обновленный вызов
                    existingChunks[chunkId] = true; // Сохраняем существующий чанк
                }
            }
        }
    }
}

// Обновленная функция createMeshFromArrays для работы с коридорами
function createMeshFromArrays(vertices, faces, scene, chunkId, x, y, z, biome) {
    const positions = [];
    const indices = [];
    const uvs = [];

    vertices.forEach(vertex => {
        positions.push(vertex[0] + x * CHUNK_SIZE, vertex[1] + y, vertex[2] + z * CHUNK_SIZE); // Учитываем ось Y
    });

    faces.forEach(face => {
        indices.push(face[0], face[1], face[2]); // Индексы вершин
        indices.push(face[2], face[1], face[0]); // Обратный порядок
    });

    // Установка UV-кординат
    const uv = [0, 0, 1, 0, 1, 1, 0, 1];
    uvs.push(...uv);

    const mesh = new BABYLON.Mesh(chunkId, scene);
    const vertexData = new BABYLON.VertexData();

    vertexData.positions = positions;
    vertexData.indices = indices;
    vertexData.uvs = uvs;

    vertexData.applyToMesh(mesh);

    const material = new BABYLON.StandardMaterial(`material_${chunkId}`, scene);

    // Установка текстур в зависимости от расположения
    if (y > 0) { // Для верхних горизонтальных границ
        material.diffuseTexture = new BABYLON.Texture(biome.topHorizontalTexture, scene);
    } else if (y < 0) { // Для нижних горизонтальных границ
        material.diffuseTexture = new BABYLON.Texture(biome.bottomHorizontalTexture, scene);
    } else { // Для вертикальных границ
        material.diffuseTexture = new BABYLON.Texture(biome.verticalTexture, scene);
    }

    material.backFaceCulling = false;
    mesh.material = material;

    return mesh;
}

// Запуск инициализации сцены
createScene();

But I’ll come back later and figure something out.

Tell me if textures are displayed on the mobile version of backrooms?