3D Maze
Press the left mouse button (or space) to move forward. Move the mouse turn the camera.
Try to reach the green-colored exit. The green line points the right direction.
const size = 25
let grid
let finish
let gridModel
let speed = 0
let won = false
let font
let turning = false
const accel = 0.035
const speedDecay = 0.92
const uiScale = size / 2 * 1.1
const mouseDeadzone = 0.05
const G_UNEXPLORED = 0
const G_PATH = 1
const G_VISITED = 2
const G_QUEUED = 3
const G_WALL = 4
const G_FINISH = 5
function preload() {
font = loadFont("/Raleway-VariableFont_wght.ttf")
}
function setup() {
let c = createCanvas(600, 600, WEBGL);
//smooth()
textFont(font)
createGrid()
gridModel = buildGeometry(drawGrid)
mouseX = width / 2
mouseY = height / 2
}
function createGrid() {
won = false
grid = Array(10).fill().map(() => Array(10).fill().map(() => Array(10).fill(G_UNEXPLORED)))
let stack = []
stack.push([3, 4, 5])
resetMatrix()
translate(-3*size,-4*size,-5*size)
storedMatrix = saveMatrix()
let putFinish = false
while (stack.length) {
let [i, j, k, queue] = stack[stack.length - 1]
grid[i][j][k] = G_PATH
if (queue === undefined) {
let nexts = []
if (i > 0) nexts.push([i - 1, j, k])
if (j > 0) nexts.push([i, j - 1, k])
if (k > 0) nexts.push([i, j, k - 1])
if (i < grid.length - 1) nexts.push([i + 1, j, k])
if (j < grid[i].length - 1) nexts.push([i, j + 1, k])
if (k < grid[i][j].length - 1) nexts.push([i, j, k + 1])
queue = []
for (let [i, j, k] of nexts) {
if (grid[i][j][k] == G_QUEUED) {
grid[i][j][k] = G_WALL
}
if (grid[i][j][k] == G_UNEXPLORED) {
grid[i][j][k] = G_QUEUED
queue.push([i, j, k])
}
}
queue = shuffle(queue)
}
stack[stack.length - 1] = [i, j, k, queue]
let found = false
while (queue.length) {
let [i, j, k] = queue.pop()
if (grid[i][j][k] == G_QUEUED) {
stack.push([i, j, k])
found = true
break;
}
}
if (!found) {
if (!putFinish) {
putFinish = true
grid[i][j][k] = G_FINISH
finish = [i, j, k]
}
stack.pop()
}
}
}
function drawGrid() {
for (let i = -1; i <= grid.length; i ++) {
for (let j = -1; j <= grid[0].length; j ++) {
for (let k = -1; k <= grid[0][0].length; k ++) {
if (i < 0 || j < 0 || k < 0 || i >= grid.length || j >= grid[i].length || k >= grid[i][j].length || grid[i][j][k] == G_WALL) {
push()
translate(i * size, j * size, k * size)
fill(100)
box(size)
pop()
}
}
}
}
}
function drawPath() {
for (let i = 0; i < grid.length; i ++) {
for (let j = 0; j < grid[0].length; j ++) {
for (let k = 0; k < grid[0][0].length; k ++) {
if (grid[i][j][k] == G_FINISH) {
push()
translate(i * size, j * size, k * size)
fill(0, 255, 0)
noStroke()
box(size * 0.1)
pop()
} else if (grid[i][j][k] == G_PATH || grid[i][j][k] == G_VISITED) {
push()
translate(i * size, j * size, k * size)
if (grid[i][j][k] == G_PATH) {
fill(255, 0, 0)
} else {
fill(100, 0, 100)
}
noStroke()
if (!keyIsDown(65)) {
box(size * 0.03)
} else {
box(size * 0.03, size * 0.03, size)
box(size, size * 0.03, size * 0.03)
box(size * 0.03, size, size * 0.03)
}
pop()
}
}
}
}
}
function saveMatrix() {
return Array.from(_renderer.uModelMatrix.mat4)
}
let storedMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
function draw() {
background(220);
if (frameCount == 1) {
camera(0, 0, size/2, 0, 0, 0, 0, 1, 0)
perspective(1.0, width / height, 3, 800)
}
resetMatrix()
let ry = mouseY / height - 0.5
let rx = mouseX / width - 0.5
let rd = mag(rx, ry)
if (rd > mouseDeadzone && turning) {
rotate(mag(ry, rx) * 0.1, [-ry, rx, 0])
}
if (mouseIsPressed || keyIsDown(32)) {
turning = true
speed += accel
}
translate(0, 0, speed)
speed *= speedDecay
applyMatrix(storedMatrix)
storedMatrix = saveMatrix()
ambientLight(255, 255, 255)
directionalLight(255, 255, 255, 1, 0.1, 0.4)
directionalLight(255, 255, 255, -1, -0.1, -0.4)
let inverse = new p5.Matrix()
inverse.invert(storedMatrix)
let pos = inverse.multiplyVec4(0,0,0,1)
push()
translate(pos[0], pos[1], pos[2])
noStroke()
fill(140, 140, 0)
box(1)
pop()
let [i, j, k] = pos.map(x => round(x/size))
if (i >= 0 && j >= 0 && k >= 0 && i < grid.length && j < grid[i].length && k < grid[i][j].length) {
if (grid[i][j][k] == G_WALL) {
speed = -speed / speedDecay - accel
} else if (grid[i][j][k] == G_PATH) {
grid[i][j][k] = G_VISITED
} else if (grid[i][j][k] == G_FINISH) {
won = true
}
}
if (!keyIsDown(65)) {
model(gridModel)
}
drawPath()
resetMatrix()
push()
clearDepth()
strokeWeight(0.015)
stroke(0)
line(-mouseDeadzone * uiScale, 0, 0, mouseDeadzone * uiScale, 0, 0)
line(0, -mouseDeadzone * uiScale, 0, 0, mouseDeadzone * uiScale, 0)
line(0, 0, rx * uiScale, ry * uiScale)
if (rd > mouseDeadzone) {
push()
strokeWeight(0.03)
stroke(255, 0, 0)
line(ry * uiScale / 2, -rx * uiScale / 2, -ry * uiScale / 2, rx * uiScale / 2)
pop()
}
let mat = new p5.Matrix(storedMatrix)
let pt = mat.multiplyVec4(i*size, j*size, k*size, 1)
stroke(100, 0, 100)
line(0, 0, 0, pt[0], pt[1], pt[2])
let pt2 = mat.multiplyVec4(finish[0]*size, finish[1]*size, finish[2]*size, 1)
pt2 = createVector(...pt2).normalize()
stroke(0, 255, 0)
line(0, 0, 0, pt2.x, pt2.y, pt2.z)
if (won) {
noStroke()
fill(0, 0, 255)
textAlign(CENTER, CENTER)
textSize(uiScale * 0.1)
text("You won!\nR to restart", 0, 0)
}
pop()
if (won && keyIsDown(82)) {
createGrid()
gridModel = buildGeometry(drawGrid)
}
}(Originally seen at https://editor.p5js.org/bojidar-bg/sketches/53Jwx39j4)
Browse more articles?
← Gold-rimmed veil Experiments tagged p5 (67/85) X-Y Mover →
← Block-packing game Experiments tagged game (9/11) X-Y Mover →
← Gold-rimmed veil Experiments on this site (67/85) X-Y Mover →