Skip to content

Instantly share code, notes, and snippets.

@audinue
Created February 11, 2022 20:15
Show Gist options
  • Select an option

  • Save audinue/276f802ff50fd273bdc0858fc4fb9a73 to your computer and use it in GitHub Desktop.

Select an option

Save audinue/276f802ff50fd273bdc0858fc4fb9a73 to your computer and use it in GitHub Desktop.
const renderTriangles = (triangles, options) => {
options = {
eye: [0, 1, 1],
center: [0, 0, 0],
mode: 'both',
orbit: true,
...options
}
const wireframe = ([a, b, c]) => [a, b, b, c, c, a]
const throwIfTruthy = message => {
if (message) {
throw new Error(message)
}
}
const createProgram = (gl, vs, fs) => {
const program = gl.createProgram()
const vertex = gl.createShader(gl.VERTEX_SHADER)
const fragment = gl.createShader(gl.FRAGMENT_SHADER)
gl.shaderSource(vertex, vs)
gl.shaderSource(fragment, fs)
gl.compileShader(vertex)
gl.compileShader(fragment)
throwIfTruthy(gl.getShaderInfoLog(vertex))
throwIfTruthy(gl.getShaderInfoLog(fragment))
gl.attachShader(program, vertex)
gl.attachShader(program, fragment)
gl.linkProgram(program)
return program
}
const main = () => {
const canvas = Object.assign(
document.createElement('canvas'),
{ width: 500, height: 500 }
)
const gl = canvas.getContext('webgl')
const triangleProgram = createProgram(gl, `
varying mediump vec3 interpolatedColor;
attribute vec3 position;
attribute vec3 color;
uniform mat4 mvp;
void main () {
gl_Position = mvp * vec4(position, 1);
interpolatedColor = color;
}
`, `
precision mediump float;
varying vec3 interpolatedColor;
void main () {
// gl_FragColor = vec4(floor(interpolatedColor * 20.0) / 20.0, 1);
gl_FragColor = vec4(interpolatedColor, 1);
}
`)
const wireframeProgram = createProgram(gl, `
attribute vec3 position;
uniform mat4 mvp;
void main () {
gl_Position = mvp * vec4(position, 1);
}
`, `
void main () {
gl_FragColor = vec4(1, 1, 1, 1);
}
`)
const triangleMvp = gl.getUniformLocation(triangleProgram, 'mvp')
const wireframeMvp = gl.getUniformLocation(wireframeProgram, 'mvp')
const triangleBuffer = gl.createBuffer()
const wireframeBuffer = gl.createBuffer()
const projection = glMatrix.mat4.create()
const view = glMatrix.mat4.create()
const mvp = glMatrix.mat4.create()
gl.enableVertexAttribArray(0)
gl.clearColor(0.15, 0.15, 0.15, 1)
gl.enable(gl.CULL_FACE)
gl.enable(gl.DEPTH_TEST)
gl.bindBuffer(gl.ARRAY_BUFFER, triangleBuffer)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(
triangles
.flatMap(triangle => triangle
.flatMap(vertex => vertex
.flatMap(value => value)))
), gl.STATIC_DRAW)
gl.bindBuffer(gl.ARRAY_BUFFER, wireframeBuffer)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(
triangles
.map(triangle => triangle
.map(vertex => vertex[0]))
.flatMap(wireframe)
.flatMap(position => position)
), gl.STATIC_DRAW)
glMatrix.mat4.perspective(projection, glMatrix.glMatrix.toRadian(90), 1, 0.001)
glMatrix.mat4.lookAt(view, options.eye, options.center, [0, 1, 0])
glMatrix.mat4.multiply(mvp, projection, view)
const animate = () => {
if (options.orbit) {
glMatrix.vec3.rotateY(options.eye, options.eye, options.center, glMatrix.glMatrix.toRadian(1))
glMatrix.mat4.lookAt(view, options.eye, options.center, [0, 1, 0])
glMatrix.mat4.multiply(mvp, projection, view)
}
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
if (options.mode === 'both' || options.mode === 'triangles') {
gl.useProgram(triangleProgram)
gl.enableVertexAttribArray(1)
gl.bindBuffer(gl.ARRAY_BUFFER, triangleBuffer)
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 6 * 4, 0 * 4)
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 6 * 4, 3 * 4)
gl.uniformMatrix4fv(triangleMvp, false, mvp)
gl.drawArrays(gl.TRIANGLES, 0, triangles.length * 3)
}
if (options.mode === 'both' || options.mode === 'wireframe') {
gl.useProgram(wireframeProgram)
gl.disableVertexAttribArray(1)
gl.bindBuffer(gl.ARRAY_BUFFER, wireframeBuffer)
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0)
gl.uniformMatrix4fv(wireframeMvp, false, mvp)
gl.drawArrays(gl.LINES, 0, triangles.length * 6)
}
requestAnimationFrame(animate)
}
const info = Object.assign(document.createElement('p'), {
textContent: `Triangle count: ${triangles.length}`,
style: 'font: 12pt sans-serif'
})
document.body.append(canvas, info)
requestAnimationFrame(animate)
}
if (document.body) {
main()
} else {
addEventListener('DOMContentLoaded', main)
}
}
<script src="gl-matrix-min.js"></script>
<script src="render-triangles.js"></script>
<script src="voxel.js"></script>
const getBlock = ({ data, width, height, depth }, [x, y, z]) => {
if (x < 0 || x >= width || y < 0 || y >= height || z < 0 || z >= depth) {
return undefined
}
return data[x + y * width + z * width * depth]
}
const getColor = value => [
[0, 0, 0],
[1, 0.5, 0],
[0, 1, 0],
[0, 0.75, 0],
[1, 1, 1],
][value]
const getBlockColor = (chunk, v) => getColor(getBlock(chunk, v))
const isFilled = value => value > 0
const isBlockFilled = (chunk, v) => isFilled(getBlock(chunk, v))
const getFront = ([x, y, z]) => [x, y, z + 1]
const getBack = ([x, y, z]) => [x, y, z - 1]
const getLeft = ([x, y, z]) => [x - 1, y, z]
const getRight = ([x, y, z]) => [x + 1, y, z]
const getTop = ([x, y, z]) => [x, y + 1, z]
const getBottom = ([x, y, z]) => [x, y - 1, z]
const getTopFront = v => getTop(getFront(v))
const getTopBack = v => getTop(getBack(v))
const getTopLeft = v => getTop(getLeft(v))
const getTopRight = v => getTop(getRight(v))
const getBottomFront = v => getBottom(getFront(v))
const getBottomBack = v => getBottom(getBack(v))
const getBottomLeft = v => getBottom(getLeft(v))
const getBottomRight = v => getBottom(getRight(v))
const getA = v => getLeft(getTopFront(v))
const getB = v => getRight(getTopFront(v))
const getC = v => getRight(getBottomFront(v))
const getD = v => getLeft(getBottomFront(v))
const getE = v => getLeft(getTopBack(v))
const getF = v => getRight(getTopBack(v))
const getG = v => getRight(getBottomBack(v))
const getH = v => getLeft(getBottomBack(v))
const getPositionA = ([x, y, z]) => [x - 0.5, y + 0.5, z + 0.5]
const getPositionB = ([x, y, z]) => [x + 0.5, y + 0.5, z + 0.5]
const getPositionC = ([x, y, z]) => [x + 0.5, y - 0.5, z + 0.5]
const getPositionD = ([x, y, z]) => [x - 0.5, y - 0.5, z + 0.5]
const getPositionE = ([x, y, z]) => [x - 0.5, y + 0.5, z - 0.5]
const getPositionF = ([x, y, z]) => [x + 0.5, y + 0.5, z - 0.5]
const getPositionG = ([x, y, z]) => [x + 0.5, y - 0.5, z - 0.5]
const getPositionH = ([x, y, z]) => [x - 0.5, y - 0.5, z - 0.5]
const applyAO = (condition, color) => condition ? darker(color) : color
const isDarkFrontA = (chunk, v) =>
isBlockFilled(chunk, getA(v))
|| isBlockFilled(chunk, getFront(getTop(v)))
|| isBlockFilled(chunk, getFront(getLeft(v)))
const isDarkFrontB = (chunk, v) =>
isBlockFilled(chunk, getB(v))
|| isBlockFilled(chunk, getFront(getTop(v)))
|| isBlockFilled(chunk, getFront(getRight(v)))
const isDarkFrontC = (chunk, v) =>
isBlockFilled(chunk, getC(v))
|| isBlockFilled(chunk, getFront(getRight(v)))
|| isBlockFilled(chunk, getFront(getBottom(v)))
const isDarkFrontD = (chunk, v) =>
isBlockFilled(chunk, getD(v))
|| isBlockFilled(chunk, getFront(getLeft(v)))
|| isBlockFilled(chunk, getFront(getBottom(v)))
const isDarkBackG = (chunk, v) =>
isBlockFilled(chunk, getG(v))
|| isBlockFilled(chunk, getBack(getBottom(v)))
|| isBlockFilled(chunk, getBack(getRight(v)))
const isDarkBackH = (chunk, v) =>
isBlockFilled(chunk, getH(v))
|| isBlockFilled(chunk, getBack(getBottom(v)))
|| isBlockFilled(chunk, getBack(getLeft(v)))
const isDarkBackE = (chunk, v) =>
isBlockFilled(chunk, getE(v))
|| isBlockFilled(chunk, getBack(getLeft(v)))
|| isBlockFilled(chunk, getBack(getTop(v)))
const isDarkBackF = (chunk, v) =>
isBlockFilled(chunk, getF(v))
|| isBlockFilled(chunk, getBack(getTop(v)))
|| isBlockFilled(chunk, getBack(getRight(v)))
const isDarkLeftH = (chunk, v) =>
isBlockFilled(chunk, getH(v))
|| isBlockFilled(chunk, getLeft(getBottom(v)))
|| isBlockFilled(chunk, getLeft(getBack(v)))
const isDarkLeftD = (chunk, v) =>
isBlockFilled(chunk, getD(v))
|| isBlockFilled(chunk, getLeft(getBottom(v)))
|| isBlockFilled(chunk, getLeft(getFront(v)))
const isDarkLeftA = (chunk, v) =>
isBlockFilled(chunk, getA(v))
|| isBlockFilled(chunk, getLeft(getTop(v)))
|| isBlockFilled(chunk, getLeft(getFront(v)))
const isDarkLeftE = (chunk, v) =>
isBlockFilled(chunk, getE(v))
|| isBlockFilled(chunk, getLeft(getBack(v)))
|| isBlockFilled(chunk, getLeft(getTop(v)))
const isDarkRightC = (chunk, v) =>
isBlockFilled(chunk, getC(v))
|| isBlockFilled(chunk, getRight(getFront(v)))
|| isBlockFilled(chunk, getRight(getBottom(v)))
const isDarkRightG = (chunk, v) =>
isBlockFilled(chunk, getG(v))
|| isBlockFilled(chunk, getRight(getBack(v)))
|| isBlockFilled(chunk, getRight(getBottom(v)))
const isDarkRightF = (chunk, v) =>
isBlockFilled(chunk, getF(v))
|| isBlockFilled(chunk, getRight(getBack(v)))
|| isBlockFilled(chunk, getRight(getTop(v)))
const isDarkRightB = (chunk, v) =>
isBlockFilled(chunk, getB(v))
|| isBlockFilled(chunk, getRight(getFront(v)))
|| isBlockFilled(chunk, getRight(getTop(v)))
const isDarkTopA = (chunk, v) =>
isBlockFilled(chunk, getA(v))
|| isBlockFilled(chunk, getTop(getFront(v)))
|| isBlockFilled(chunk, getTop(getLeft(v)))
const isDarkTopB = (chunk, v) =>
isBlockFilled(chunk, getB(v))
|| isBlockFilled(chunk, getTop(getFront(v)))
|| isBlockFilled(chunk, getTop(getRight(v)))
const isDarkTopF = (chunk, v) =>
isBlockFilled(chunk, getF(v))
|| isBlockFilled(chunk, getTop(getBack(v)))
|| isBlockFilled(chunk, getTop(getRight(v)))
const isDarkTopE = (chunk, v) =>
isBlockFilled(chunk, getE(v))
|| isBlockFilled(chunk, getTop(getBack(v)))
|| isBlockFilled(chunk, getTop(getLeft(v)))
const isDarkBottomH = (chunk, v) =>
isBlockFilled(chunk, getH(v))
|| isBlockFilled(chunk, getBottom(getBack(v)))
|| isBlockFilled(chunk, getBottom(getLeft(v)))
const isDarkBottomG = (chunk, v) =>
isBlockFilled(chunk, getG(v))
|| isBlockFilled(chunk, getBottom(getBack(v)))
|| isBlockFilled(chunk, getBottom(getRight(v)))
const isDarkBottomC = (chunk, v) =>
isBlockFilled(chunk, getC(v))
|| isBlockFilled(chunk, getBottom(getFront(v)))
|| isBlockFilled(chunk, getBottom(getRight(v)))
const isDarkBottomD = (chunk, v) =>
isBlockFilled(chunk, getD(v))
|| isBlockFilled(chunk, getBottom(getFront(v)))
|| isBlockFilled(chunk, getBottom(getLeft(v)))
const getFrontPlane = (chunk, v) => [
[getPositionD(v), applyAO(isDarkFrontD(chunk, v), dark(getBlockColor(chunk, v)))],
[getPositionC(v), applyAO(isDarkFrontC(chunk, v), dark(getBlockColor(chunk, v)))],
[getPositionB(v), applyAO(isDarkFrontB(chunk, v), dark(getBlockColor(chunk, v)))],
[getPositionA(v), applyAO(isDarkFrontA(chunk, v), dark(getBlockColor(chunk, v)))],
isDarkFrontD(chunk, v) || isDarkFrontB(chunk, v)
]
const getBackPlane = (chunk, v) => [
[getPositionG(v), applyAO(isDarkBackG(chunk, v), dark(getBlockColor(chunk, v)))],
[getPositionH(v), applyAO(isDarkBackH(chunk, v), dark(getBlockColor(chunk, v)))],
[getPositionE(v), applyAO(isDarkBackE(chunk, v), dark(getBlockColor(chunk, v)))],
[getPositionF(v), applyAO(isDarkBackF(chunk, v), dark(getBlockColor(chunk, v)))],
isDarkBackG(chunk, v) || isDarkBackE(chunk, v)
]
const getLeftPlane = (chunk, v) => [
[getPositionH(v), applyAO(isDarkLeftH(chunk, v), dark(dark(dark(getBlockColor(chunk, v)))))],
[getPositionD(v), applyAO(isDarkLeftD(chunk, v), dark(dark(dark(getBlockColor(chunk, v)))))],
[getPositionA(v), applyAO(isDarkLeftA(chunk, v), dark(dark(dark(getBlockColor(chunk, v)))))],
[getPositionE(v), applyAO(isDarkLeftE(chunk, v), dark(dark(dark(getBlockColor(chunk, v)))))],
isDarkLeftH(chunk, v) || isDarkLeftA(chunk, v)
]
const getRightPlane = (chunk, v) => [
[getPositionC(v), applyAO(isDarkRightC(chunk, v), dark(getBlockColor(chunk, v)))],
[getPositionG(v), applyAO(isDarkRightG(chunk, v), dark(getBlockColor(chunk, v)))],
[getPositionF(v), applyAO(isDarkRightF(chunk, v), dark(getBlockColor(chunk, v)))],
[getPositionB(v), applyAO(isDarkRightB(chunk, v), dark(getBlockColor(chunk, v)))],
isDarkRightC(chunk, v) || isDarkRightF(chunk, v)
]
const getTopPlane = (chunk, v) => [
[getPositionA(v), applyAO(isDarkTopA(chunk, v), getBlockColor(chunk, v))],
[getPositionB(v), applyAO(isDarkTopB(chunk, v), getBlockColor(chunk, v))],
[getPositionF(v), applyAO(isDarkTopF(chunk, v), getBlockColor(chunk, v))],
[getPositionE(v), applyAO(isDarkTopE(chunk, v), getBlockColor(chunk, v))],
isDarkTopA(chunk, v) || isDarkTopF(chunk, v)
]
const getBottomPlane = (chunk, v) => [
[getPositionH(v), applyAO(isDarkBottomH(chunk, v), darkest(getBlockColor(chunk, v)))],
[getPositionG(v), applyAO(isDarkBottomG(chunk, v), darkest(getBlockColor(chunk, v)))],
[getPositionC(v), applyAO(isDarkBottomC(chunk, v), darkest(getBlockColor(chunk, v)))],
[getPositionD(v), applyAO(isDarkBottomD(chunk, v), darkest(getBlockColor(chunk, v)))],
isDarkBottomH(chunk, v) || isDarkBottomC(chunk, v)
]
const triangulatePlane = ([a, b, c, d, inverse]) => inverse ? [[a, b, d], [d, b, c]] : [[a, b, c], [a, c, d]]
const dark = ([r, g, b]) => [r * 0.95, g * 0.95, b * 0.95]
const darker = ([r, g, b]) => [r * 0.8, g * 0.8, b * 0.8]
const darkest = ([r, g, b]) => [r * 0.6, g * 0.6, b * 0.6]
const isFrontPlaneVisible = (chunk, v) => !isBlockFilled(chunk, getFront(v))
const isBackPlaneVisible = (chunk, v) => !isBlockFilled(chunk, getBack(v))
const isLeftPlaneVisible = (chunk, v) => !isBlockFilled(chunk, getLeft(v))
const isRightPlaneVisible = (chunk, v) => !isBlockFilled(chunk, getRight(v))
const isTopPlaneVisible = (chunk, v) => !isBlockFilled(chunk, getTop(v))
const isBottomPlaneVisible = (chunk, v) => !isBlockFilled(chunk, getBottom(v))
const getBlockV = ({ width, depth }, offset) => [
offset % width,
Math.floor(offset / width) % depth,
Math.floor(offset / (width * depth))
]
/*
Back Bottom
| |
| |
v V
Front Top
Left --> Right
*/
const x = 3
const sz = 20
const chunk = {
data: [
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 0, 0,
1, 1, 1, 1, 1, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 0, 0,
0, 0, 1, 1, 1, 0, 0, 0,
0, 0, 0, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 0, 0,
1, 1, 1, 1, 1, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 0, 0,
0, 0, 1, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 0, 0,
1, 1, 1, 1, 1, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 0, 0,
0, 0, 1, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 0, 0,
1, 1, 1, 1, 1, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 0, 0,
1, 1, 1, 1, 1, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
],
width: 8,
height: 8,
depth: 8
}
const triangles = chunk.data
.map((x, i) => getBlockV(chunk, i))
.filter(v => isBlockFilled(chunk, v))
.flatMap(v => [
...(isFrontPlaneVisible(chunk, v) ? [getFrontPlane(chunk, v)] : []),
...(isBackPlaneVisible(chunk, v) ? [getBackPlane(chunk, v)] : []),
...(isLeftPlaneVisible(chunk, v) ? [getLeftPlane(chunk, v)] : []),
...(isRightPlaneVisible(chunk, v) ? [getRightPlane(chunk, v)] : []),
...(isTopPlaneVisible(chunk, v) ? [getTopPlane(chunk, v)] : []),
...(isBottomPlaneVisible(chunk, v) ? [getBottomPlane(chunk, v)] : []),
])
.flatMap(triangulatePlane)
renderTriangles(triangles, {
eye: [3.5, 10, 12],
center: [3.5, 3.5, 3.5],
mode: 'triangles',
orbit: true,
})
const tree = [
3, 3, 3, 3, 3, 3, 3, 3,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 2, 2, 2, 2, 2, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
3, 3, 3, 3, 3, 3, 3, 3,
0, 2, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 2, 2, 2, 0, 0,
0, 0, 2, 2, 2, 2, 2, 0,
0, 0, 0, 2, 2, 2, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
3, 3, 3, 3, 3, 3, 3, 3,
0, 0, 0, 0, 1, 0, 2, 0,
0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 2, 2, 2, 0, 0,
0, 0, 2, 2, 2, 2, 2, 0,
0, 0, 0, 2, 2, 2, 0, 0,
0, 0, 0, 0, 2, 0, 0, 0,
3, 3, 3, 3, 3, 3, 3, 3,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 2, 2, 2, 0, 0,
0, 0, 2, 2, 2, 2, 2, 0,
0, 0, 0, 2, 2, 2, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
3, 3, 3, 3, 3, 3, 3, 3,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 2, 2, 2, 2, 2, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
3, 3, 3, 3, 3, 3, 3, 3,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
3, 3, 3, 3, 3, 3, 3, 3,
4, 4, 4, 4, 4, 4, 4, 0,
4, 0, 0, 4, 0, 0, 4, 0,
4, 0, 0, 4, 0, 0, 4, 0,
4, 0, 0, 4, 4, 4, 4, 0,
4, 0, 0, 4, 0, 0, 4, 0,
4, 0, 0, 4, 0, 0, 4, 0,
4, 4, 4, 4, 4, 4, 4, 0,
3, 3, 3, 3, 3, 3, 3, 3,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment