<html><head><title>nav_mesh_path_finding</title>
</head><body><div>
<canvas id="canvas" width="1300" height="550" style="border: solid black 1px; cursor: default;"></canvas>
</div>
<div>
<input type="button" value="clear" onclick="demo.clear();">
<input type="button" value="load from string" onclick="demo.load();">
<input type="button" value="generate" onclick="demo.generate(true);">
<input type="button" value="generate obstacles" onclick="demo.generateObstacles();">
<!-- <input type="button" value="generate maze" onclick="demo.clear();"> -->
<input type="button" value="save path" onclick="demo.savePath();">
</div>
<br/>
<textarea id="txtArea" style="margin: 0px; width: 1200px; height: 33px;"></textarea>
<br/>
<script type="text/javascript" src="MathUtil.js"></script>
<script type="text/javascript">
Triangle.prototype.drawHere = function(ctx) {
var v1 = this.v1, v2 = this.v2, v3 = this.v3;
if (this.isValid) {
if (this.isObstacle) {
ctx.beginPath();
ctx.moveTo(v1.x, v1.y);
ctx.lineTo(v2.x, v2.y);
ctx.lineTo(v3.x, v3.y);
ctx.lineTo(v1.x, v1.y);
ctx.closePath();
ctx.fillStyle = "#636363";
ctx.fill();
} else {
ctx.beginPath();
ctx.moveTo(v1.x, v1.y);
ctx.lineTo(v2.x, v2.y);
ctx.lineTo(v3.x, v3.y);
ctx.lineTo(v1.x, v1.y);
ctx.closePath();
ctx.strokeStyle = "#ff0000";
ctx.stroke();
// ctx.fillStyle = "#ffffff";
// ctx.fill();
}
} else {
ctx.beginPath();
ctx.moveTo(v1.x, v1.y);
ctx.lineTo(v2.x, v2.y);
ctx.lineTo(v3.x, v3.y);
ctx.lineTo(v1.x, v1.y);
ctx.closePath();
ctx.fillStyle = "#a3a3a3";
ctx.fill();
}
// this.drawIndex(ctx);
}
var demo = new Demo();
var config = new Config();
function Config() {
this.MIN_DISTANCE = 30;
this.TOTAL_VERTICES = 200;
this.OBSTACLE_PERCENT = 25;
}
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
function Demo() {
var vertices = [];
var obstacleFlags = [];
var allTriangles;
function initRandomVertices() {
if (vertices.length == 0) {
var first = createRandomPoint();
first.id = 0;
vertices.push(first);
}
for (var i = 1; i < config.TOTAL_VERTICES; i++) {
var p = createProperRandomPoint();
if (p != null) {
p.id = i;
vertices.push(p);
} else {
break;
}
}
console.log("expected: " + config.TOTAL_VERTICES + ", actually: " + vertices.length);
}
function createProperRandomPoint() {
for (var i = 0; i < 255; i++) {
var v = createRandomPoint();
var closestDistance = getClosestPoint(v, vertices, []).distance;
if (closestDistance >= config.MIN_DISTANCE) {
return v;
}
}
return null;
}
function createRandomPoint() {
return new Vertex(getRandom(50, canvas.width-50), getRandom(50, canvas.height-50));
}
function getTriangleByPoint(p) {
if (allTriangles != null && allTriangles.length > 0) {
for (var i = 0; i < allTriangles.length; i++) {
if (allTriangles[i].contains(p)) {
return allTriangles[i];
}
}
}
return null;
}
var srcTriangle = null;
var dstTriangle = null;
var p1, p2;
this.addAt = function(x, y) {
if (srcTriangle != null && dstTriangle != null) {
srcTriangle = dstTriangle = null;
p1 = p2 = null;
//clear path
context.clearRect(0, 0, Number(canvas.width), Number(canvas.height));
this.render();
}
var p = new Vertex(x, y);
p.draw(context);
if (srcTriangle == null) {
srcTriangle = getTriangleByPoint(p);
p1 = p;
} else {
dstTriangle = getTriangleByPoint(p);
p2 = p;
var neighbor = getPathOfTriangles(srcTriangle, dstTriangle);
drawPathEdgeNodes(neighbor, context);
drawSimplifyPath(context, neighbor, p1, p2);
}
}
var isLoading = false;
this.load = function() {
this.clearVertices();
isLoading = true;
var txt = document.getElementById("txtArea").value;
if (txt == null || txt.length < 12) {
return;
}
var arr = txt.split("<>");
var points = arr[0].split(",");
for (var i=0; i<points.length; i++) {
var p = new Vertex(0, 0);
p.init(points[i]);
p.id = i;
vertices.push(p);
}
if (arr.length > 1) {
var obstaclesStr = arr[1];
obstacleFlags = obstaclesStr.split(",");
}
if (obstacleFlags.length > 0) {
this.generateObstacles();
} else if (vertices.length > 0) {
this.generate(true);
}
if (arr.length > 2) {
var twoPoints = arr[2].split(",");
p1 = new Vertex(0, 0);
p2 = new Vertex(0, 0);
p1.init(twoPoints[0]);
p2.init(twoPoints[1]);
p1.draw(context);
p2.draw(context);
srcTriangle = getTriangleByPoint(p1);
dstTriangle = getTriangleByPoint(p2);
var neighbor = getPathOfTriangles(srcTriangle, dstTriangle);
drawPathEdgeNodes(neighbor, context);
drawSimplifyPath(context, neighbor, p1, p2);
}
isLoading = false;
}
this.generate = function(needDraw) {
if (!isLoading) {
this.clear();
initRandomVertices();
document.getElementById("txtArea").value = vertices.toString();
}
allTriangles = getAllDelaunayTriangles(vertices);
initNeighborhood(allTriangles);
if (needDraw) {
this.render();
}
}
this.generateObstacles = function() {
context.clearRect(0, 0, Number(canvas.width), Number(canvas.height));
if (!isLoading) {
if (vertices.length == 0) {
this.generate(false);
}
obstacleFlags=[];
for (var i = 0; i < allTriangles.length; i++) {
if (!allTriangles[i].isValid) {
obstacleFlags.push(2);
continue;
}
var factor = parseInt(getRandom(0, 101));
if (factor <= config.OBSTACLE_PERCENT) {
allTriangles[i].isObstacle = true;
obstacleFlags.push(1);
} else {
allTriangles[i].isObstacle = false;
obstacleFlags.push(0);
}
}
document.getElementById("txtArea").value = vertices.toString() + "<>" + obstacleFlags.toString();
} else {
allTriangles = getAllDelaunayTriangles(vertices);
initNeighborhood(allTriangles);
for (var i = 0; i < allTriangles.length; i++) {
if (!allTriangles[i].isValid) {
continue;
}
allTriangles[i].isObstacle = obstacleFlags[i] == 1 ? true : false;
}
}
this.render();
}
this.savePath = function() {
var startEnd = [p1.toString(), p2.toString()];
document.getElementById("txtArea").value = vertices.toString() + "<>" + obstacleFlags.toString() + "<>" + startEnd.toString();
}
this.clear = function() {
this.clearVertices();
document.getElementById("txtArea").value = "";
}
this.clearVertices = function() {
clearNeighborhood();
vertices = [];
obstacleFlags = [];
context.clearRect(0, 0, Number(canvas.width), Number(canvas.height));
}
// function render() {
this.render = function() {
if (!canvas.getContext)
return;
if (vertices.length <= 0) {
return;
}
var validCount = 0;
for (var i = 0; i < allTriangles.length; i++) {
allTriangles[i].drawHere(context);
if (allTriangles[i].isValid) {
validCount++;
}
}
console.log("delaunay triangle total: " + allTriangles.length + ", validCount:" + validCount);
vertices.forEach(function(vertex) {
vertex.draw(context);
// context.font = "23px Courier New";
// context.fillStyle = "orange";
// context.fillText(vertex.id, vertex.x, vertex.y);
});
}
};
var EPSILON = Number.EPSILON || Math.pow(2,-52);
function getAllDelaunayTriangles(vertices) {
var n, i, j, indices, buffer, open, closed, current, dx, dy;
if ((n = vertices.length) < 3) return [];
vertices = vertices.concat();
//create a array base vertices's key
for (i = n, indices = new Array(n); i--;) indices[i] = i;
//sort the indices base on the x coordinate of vertices
//from bigger to smaller
indices.sort(function (a, b) {
return vertices[b].x - vertices[a].x;
});
//create a supertriangle to buffer and push the vertex to vertices Array
buffer = supertriangle(vertices);
vertices.push(buffer[0], buffer[1], buffer[2]);
//attach supertriangle to open for a start triangle
open = [circumcircle(vertices, n + 0, n + 1, n + 2)];
closed = [];
buffer = [];
//starting loop from smallest x of vertex
for (i = indices.length; i--; buffer.length = 0) {
current = indices[i];
for (j = open.length; j--;) {
dx = vertices[current].x - open[j].x;
if (dx > 0 && dx * dx > open[j].r) {
closed.push(open[j]);
open.splice(j, 1);
continue;
}
//skip this point if it is outside this circumcircle
dy = vertices[current].y - open[j].y;
if (dx * dx + dy * dy - open[j].r > EPSILON) continue;
//add edges of this triangle to buffer
buffer.push(open[j].i, open[j].j, open[j].j, open[j].k, open[j].k, open[j].i);
open.splice(j, 1);
}
dedup(buffer);
for (j = buffer.length; j;) {
var obj = circumcircle(vertices, buffer[--j], buffer[--j], current);
if (obj.r != Infinity) {
open.push(obj);
}
}
}
for (i = open.length; i--;)
closed.push(open[i]);
var ret = [];
for (open.length = 0, i = closed.length; i--;) {
if (closed[i].i < n && closed[i].j < n && closed[i].k < n) {//deleting triangles that are relative to supertriangle
var point = closed[i];
open.push(closed[i].i, closed[i].j, closed[i].k);
var t = new Triangle(vertices[point.i], vertices[point.j], vertices[point.k]);
t.index = ret.length;
ret.push(t);
saveToPointAndEdgeMap(t);
}
}
// return open;
return ret;
}
function supertriangle(v) {
var xmin = Number.POSITIVE_INFINITY, ymin = xmin, ymax = 0, xmax = 0, i, xl, yl, xlh;
for (i = v.length; i--;) v[i].x < xmin && (xmin = v[i].x), v[i].x > xmax && (xmax = v[i].x), v[i].y < ymin && (ymin = v[i].y), v[i].y > ymax && (ymax = v[i].y);
xl = xmax - xmin;
yl = ymax - ymin;
xlh = xl / 2;
return [
new Vertex(xmin - xlh - 2, ymax + 1), //the left vertex's coordinate
new Vertex(xmin + xlh, ymin - yl), //the top vertex's coordinate
new Vertex(xmax + xlh + 2, ymax + 1) //teh right vertex's coordinate
];
}
//multiplex the circumcircle algorithm
function circumcircle(v, i, j, k) {
var x1 = v[i].x,
y1 = v[i].y,
x2 = v[j].x,
y2 = v[j].y,
x3 = v[k].x,
y3 = v[k].y,
fabsy1y2 = Math.abs(y1 - y2),
fabsy2y3 = Math.abs(y2 - y3),
xc, yc, m1, m2, mx1, mx2, my1, my2, dx, dy;
if (fabsy1y2 < EPSILON) {
m2 = -((x3 - x2) / (y3 - y2));
mx2 = (x2 + x3) / 2.0;
my2 = (y2 + y3) / 2.0;
xc = (x2 + x1) / 2.0;
yc = m2 * (xc - mx2) + my2;
}
else if (fabsy2y3 < EPSILON) {
m1 = -((x2 - x1) / (y2 - y1));
mx1 = (x1 + x2) / 2.0;
my1 = (y1 + y2) / 2.0;
xc = (x3 + x2) / 2.0;
yc = m1 * (xc - mx1) + my1;
}
else {
m1 = -((x2 - x1) / (y2 - y1));
m2 = -((x3 - x2) / (y3 - y2));
mx1 = (x1 + x2) / 2.0;
mx2 = (x2 + x3) / 2.0;
my1 = (y1 + y2) / 2.0;
my2 = (y2 + y3) / 2.0;
xc = (m1 * mx1 - m2 * mx2 + my2 - my1) / (m1 - m2);
yc = (fabsy1y2 > fabsy2y3) ?
m1 * (xc - mx1) + my1 :
m2 * (xc - mx2) + my2;
}
dx = x2 - xc;
dy = y2 - yc;
return {i: i, j: j, k: k, x: xc, y: yc, r: dx * dx + dy * dy};
}
function dedup(edges) {
var i, j, a, b, m, n;
for (j = edges.length; j;) {
b = edges[--j];
a = edges[--j];
for (i = j; i;) {
n = edges[--i];
m = edges[--i];
if ((a === m && b === n) || (a === n && b === m)) {
edges.splice(j, 2);
edges.splice(i, 2);
break;
}
}
}
}
function saveTriangleToPointMap(triangle, key) {
if (point_triangles[key] == null) {
point_triangles[key] = [triangle];
} else {
point_triangles[key].push(triangle);
}
}
function saveTriangleToEdgeMap(triangle, str1, str2) {
if (edge_triangles[str1 + str2] != null) {
edge_triangles[str1 + str2].push(triangle);
} else if (edge_triangles[str2 + str1] != null) {
edge_triangles[str2 + str1].push(triangle);
} else {
edge_triangles[str1 + str2] = [triangle];
}
}
function contains(p, arr) {
for (var i=0; i<arr.length; i++) {
if (arr[i].x == p.x && arr[i].y == p.y) {
return true;
}
}
return false;
}
function getClosestPoint(p, array, uncheckArr) {
var arr = subtract(array, uncheckArr);
var min = -1;
var v;
for (var i=0; i<arr.length; i++) {
var distance = getDistanceSquare(p, arr[i]);
if (min < 0) {
min = distance;
v = arr[i];
} else if (min > distance) {
min = distance;
v = arr[i];
}
}
v.distance = Math.sqrt(min);
return v;
}
var point_triangles = [];
var edge_triangles = [];
var triangle_neighbor = [];
function saveToPointAndEdgeMap(triangle) {
var str1 = triangle.v1.toString();
var str2 = triangle.v2.toString();
var str3 = triangle.v3.toString();
saveTriangleToPointMap(triangle, str1);
saveTriangleToPointMap(triangle, str2);
saveTriangleToPointMap(triangle, str3);
saveTriangleToEdgeMap(triangle, str1, str2);
saveTriangleToEdgeMap(triangle, str2, str3);
saveTriangleToEdgeMap(triangle, str3, str1);
}
function initNeighborhood(allTriangles) {
for (var i = 0; i < allTriangles.length; i++) {
var triangle = allTriangles[i];
var neighbor = new Neighbor(triangle);
triangle_neighbor[triangle.toString()] = neighbor;
var edge12_triangles = subtract(getTrianglesByEdge(triangle.v1, triangle.v2), [triangle]);
var edge23_triangles = subtract(getTrianglesByEdge(triangle.v2, triangle.v3), [triangle]);
var edge31_triangles = subtract(getTrianglesByEdge(triangle.v3, triangle.v1), [triangle]);
var v1_triangles = subtract(point_triangles[triangle.v1.toString()], [triangle].concat(edge12_triangles).concat(edge31_triangles));
var v2_triangles = subtract(point_triangles[triangle.v2.toString()], [triangle].concat(edge12_triangles).concat(edge23_triangles));
var v3_triangles = subtract(point_triangles[triangle.v3.toString()], [triangle].concat(edge23_triangles).concat(edge31_triangles));
neighbor.edgeNeighbors = edge12_triangles.concat(edge23_triangles).concat(edge31_triangles);
neighbor.pointNeighbors = v1_triangles.concat(v2_triangles).concat(v3_triangles);
if (edge12_triangles.length > 0) neighbor.triangle_edge[edge12_triangles[0].toString()] = [triangle.v1, triangle.v2];
if (edge23_triangles.length > 0) neighbor.triangle_edge[edge23_triangles[0].toString()] = [triangle.v2, triangle.v3];
if (edge31_triangles.length > 0) neighbor.triangle_edge[edge31_triangles[0].toString()] = [triangle.v3, triangle.v1];
}
}
function clearNeighborhood() {
point_triangles = [];
edge_triangles = [];
triangle_neighbor = [];
}
function getPathOfTriangles(srcTriangle, dstTriangle) {
var start = triangle_neighbor[srcTriangle.toString()];
start.parentNeighbor = "none";
if (start.me.equals(dstTriangle)) {
return start;
}
var openedItems = start.getPassableNeighbors();
var closedNeighbors = [start];
for (var i = 0; i < openedItems.length; i++) {
openedItems[i].parentNeighbor = start;
}
while (openedItems.length > 0) {
var neighbor = openedItems.shift();
if (neighbor.me.equals(dstTriangle)) {
return neighbor;
}
var newComingItems = neighbor.getPassableNeighbors();
for (var i = 0; i < newComingItems.length; i++) {
// if (newComingItems[i].parentNeighbor == null) {
// newComingItems[i].parentNeighbor = neighbor;
// openedItems.push(newComingItems[i]);
// }
var newItem = newComingItems[i];
if (!hasNeighbor(openedItems, newItem) && !hasNeighbor(closedNeighbors, newItem)) {
newItem.parentNeighbor = neighbor;
openedItems.push(newItem);
}
}
closedNeighbors.push(neighbor);
}
return null;
}
function hasNeighbor(arr, neighbor) {
for (var i = 0; i < arr.length; i++) {
if (arr[i].equals(neighbor)) {
return true;
}
}
return false;
}
function drawPathEdgeNodes(neighbor, ctx) {
if (neighbor == null) {
alert("cannot reach.");
return;
}
var desc = [];
var child = neighbor;
while (child.parentNeighbor != null) {
desc.push(child.index);
var node = child.parentNeighbor;
if (node == "none") {
child.me.v1.emphasizeDraw(ctx);
child.me.v2.emphasizeDraw(ctx);
child.me.v3.emphasizeDraw(ctx);
console.log("path: " + desc.toString());
return;
}
var edgeNodes = child.triangle_edge[node.me.toString()];
if (edgeNodes != null) {
edgeNodes[0].emphasizeDraw(ctx);
edgeNodes[1].emphasizeDraw(ctx);
}
child = child.parentNeighbor;
}
}
function getEdgesFromNeighbor(neighbor) {
if (neighbor == null) {
return [];
}
var child = neighbor;
var neighbors = [];
while (child.parentNeighbor != null) {
neighbors.push(child);
if (child.parentNeighbor == "none") {
break;
}
child = child.parentNeighbor;
}
var edges = [];
for (var i= 0; i < neighbors.length - 1; i++) {
edges.push(neighbors[i].triangle_edge[neighbors[i+1].me.toString()]);
}
return edges.reverse();
}
function drawSimplifyPath(ctx, neighbor, start, end) {
ctx.moveTo(start.x, start.y);
var edges = getEdgesFromNeighbor(neighbor);
var points = findSimplifyPath(start, end, edges);
// ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
for (var i = 1; i < points.length; i++) {
ctx.lineTo(points[i].x, points[i].y);
}
// ctx.closePath();
ctx.strokeStyle = "#00ffaa";
ctx.lineWidth = 3;
ctx.stroke();
ctx.lineWidth = 1;
}
function Neighbor(triangle) {
this.index = triangle.index;
this.me = triangle;
this.triangle_edge = [];
this.pointNeighbors = [];
this.edgeNeighbors = [];
this.getAllNeighbors = function() {
return this.edgeNeighbors.concat(this.pointNeighbors);
}
this.getPassableNeighbors = function () {
var ret = [];
for (var i = 0; i < this.edgeNeighbors.length; i++) {
if (this.edgeNeighbors[i].isValid && !this.edgeNeighbors[i].isObstacle) {
ret.push(triangle_neighbor[this.edgeNeighbors[i].toString()]);
}
}
return ret;
}
this.equals = function(other) {
if (other == null)
return false;
if (other.index == this.index) {
return true;
} else {
return false;
}
}
}
function getTrianglesByEdge(v1, v2) {
var str1 = v1.toString();
var str2 = v2.toString();
if (edge_triangles[str1 + str2] != null) {
return edge_triangles[str1 + str2];
} else if (edge_triangles[str2 + str1] != null) {
return edge_triangles[str2 + str1];
}
return [];
}
window.onload = function() {
document.getElementById("canvas").onclick = function(e) {
e = e ? e : window.event;
var rect = this.getBoundingClientRect();
demo.addAt(e.clientX - rect.left, e.clientY - rect.top);
}
};
</script>
</body></html>
(406 | 340),(1144 | 105),(1165 | 353),(361 | 244),(927 | 185),(331 | 326),(906 | 475),(240 | 497),(115 | 213),(173 | 333),(373 | 476),(960 | 231),(1060 | 392),(577 | 61),(1115 | 73),(1125 | 422),(722 | 472),(783 | 491),(798 | 153),(698 | 373),(1141 | 201),(509 | 50),(695 | 112),(359 | 174),(99 | 392),(1176 | 70),(243 | 437),(432 | 291),(1207 | 50),(373 | 286),(1190 | 486),(1170 | 168),(452 | 485),(232 | 387),(1082 | 159),(1061 | 455),(104 | 331),(69 | 57),(845 | 69),(55 | 307),(624 | 285),(425 | 216),(414 | 166),(592 | 312),(1114 | 218),(763 | 250),(359 | 52),(971 | 314),(418 | 94),(1194 | 263),(722 | 281),(1034 | 62),(945 | 469),(485 | 145),(849 | 160),(233 | 311),(1081 | 363),(679 | 74),(865 | 332),(1196 | 301),(842 | 239),(273 | 198),(659 | 51),(750 | 72),(1049 | 160),(587 | 454),(993 | 183),(169 | 488),(923 | 284),(1244 | 186),(719 | 138),(616 | 339),(1042 | 248),(265 | 234),(1156 | 319),(349 | 144),(1208 | 442),(583 | 384),(139 | 172),(416 | 485),(909 | 211),(905 | 347),(606 | 132),(242 | 273),(1160 | 271),(73 | 177),(574 | 343),(1198 | 332),(570 | 283),(504 | 483),(896 | 153),(832 | 460),(681 | 477),(364 | 96),(94 | 462),(651 | 472),(973 | 403),(948 | 424),(907 | 425),(634 | 445),(874 | 483),(115 | 73),(1123 | 157),(1178 | 436),(311 | 261),(199 | 309),(719 | 324),(523 | 328),(754 | 165),(329 | 488),(208 | 120),(889 | 280),(1209 | 144),(507 | 409),(1195 | 363),(124 | 427),(1038 | 191),(891 | 59),(1118 | 473),(437 | 139),(766 | 381),(1241 | 328),(148 | 294),(1101 | 288),(890 | 383),(528 | 254),(751 | 335),(279 | 51),(166 | 158),(779 | 410),(767 | 292),(991 | 268),(885 | 242),(555 | 464),(656 | 368),(668 | 156),(336 | 219),(714 | 435),(237 | 105),(903 | 119),(575 | 124),(744 | 439),(300 | 165),(894 | 311),(554 | 172),(868 | 188),(1112 | 124),(647 | 263),(975 | 81),(1223 | 398),(955 | 123),(286 | 463),(263 | 412),(606 | 240),(1231 | 286),(155 | 63),(154 | 407),(1078 | 235),(279 | 330),(1000 | 153),(245 | 152),(78 | 249),(91 | 143),(182 | 374),(297 | 117),(1047 | 326),(1017 | 415),(66 | 424),(269 | 372),(970 | 492),(307 | 61),(1169 | 406),(966 | 362),(478 | 323),(575 | 205),(475 | 252),(333 | 396),(1063 | 424),(333 | 84),(378 | 377),(812 | 322),(632 | 208),(1175 | 202),(787 | 348),(429 | 362),(270 | 84),(1029 | 498),(419 | 395),(85 | 301),(769 | 106),(393 | 446),(689 | 293),(1055 | 287),(1142 | 451),(665 | 118),(329 | 175),(517 | 190),(191 | 273),(1071 | 90),(828 | 212)<>2,2,2,2,2,2,2,2,1,1,0,0,0,1,1,1,0,0,1,2,0,0,0,0,0,0,0,0,0,1,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,1,1,0,1,0,0,0,0,1,0,0,1,0,0,1,0,0,1,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,
1,0,2,1,0,0,0,0,1,1,0,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,1,0,0,1,1,0,0,1,0,0,0,0,1,1,1,1,1,0,0,0,0,0,1,1,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,0,0,0,0,1,0,1,1,0,1,1,1,0,1,1,0,1,1,0,1,0,0,0,0,0,0,0,0,0,1,
0,0,0,0,0,1,0,0,0,0,0,0,1,0,1,1,0,0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,0,0,1,0,0,0,0,1,1,1,1,0,1,0,0,0,0,1,1,0,1,0,0,1,0,0,0,0,1,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,1,0,0,0,1,1,0,1,0,0,0,0,0,1,1,0,0,0,1,0,0,0,0,0,
0,1,0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,1,0,1,1,0,0,1,0,1,0,0,0,0,1,0