Tiled based pathfinding in a 1- or 2-dimensional array - javascript

As far as I know all tile based map editors export a JSON object containing one dimensional arrays. While most pathfinding libraries/tutorials are only provided for two dimensional arrays.
Also if for example I would like to do pathfinding in this one dimensional array and this array is huge i'm geussing this would cause performance issues.
So why is it that most tile based map editors output a one dimensional and how should I handle those regarding pathfinding?
example tile editor
Just google pathfinding to find all the two dimensional patfhfinding tutorials

Depending on the orientation by which it is converted to the 1-D array;
function convert(x, y, height, width) {
return x + y * width; // rows
/* return y + x * height; // cols */
}
function reverse(i, height, width) {
var x, y;
// rows
x = i % width
y = (i - x) / width
/* // cols
y = i % height;
x = (i - y) % height; */
return [x, y];
}
Now, say we have a 6 wide by 3 high map
1-D | 2-D
0 1 2 3 4 5 | x0y0 x1y0 x2y0 x3y0 x4y0 x5y0
6 7 8 9 10 11 | x0y1 x1y1 x2y1 x3y1 x4y1 x5y1
12 13 14 15 16 17 | x0y2 x1y2 x2y2 x3y2 x4y2 x5y2
Pick an index in the 1-D Array, e.g. i = 8, to convert it to it's 2-D co-ordinates we can use reverse
reverse(8, 3, 6); // [2, 1]
// i, h, w = [x, y]
Or say we picked co-ordinates x = 2, y = 1 in our 2-D Array, we can convert it to the index in the 1-D Array with convert
convert(2, 1, 3, 6); // 8
// x, y, h, w = i
Once you can convert between the two systems you can do your path finding as normal. You can name these functions however you like, I wrote them more so you can see how to switch between the two systems.
Depending on how it is made, the y axis may have 0 at the bottom, not the top, or the entire thing could be mirrored across a diagonal (which I called cols in the above functions). It really depends on how it was done, but as long as you are consistent with the conversion and have the correct height and width (read maximum y and maximum x respectively), it should not matter.

One approach might be to retrieve an offset into the 1D array, based on the 2D vector coords of the tile:
int MaxX = 100; // assumes a max row of 100;
int offset = Y * MaxX + X;
tile[offset] = ....
No need to convert, just reference the tile directly in the 1D array. I used this approach for A* in a recent game project, and works for me.

Related

How to improve accuracy of a FeedForward Neural Network?

I want to draw StackOverflow's logo with this Neural Network:
The NN should ideally become [r, g, b] = f([x, y]). In other words, it should return RGB colors for a given pair of coordinates. The FFNN works pretty well for simple shapes like a circle or a box. For example after several thousands epochs a circle looks like this:
Try it yourself: https://codepen.io/adelriosantiago/pen/PoNGeLw
However since StackOverflow's logo is far more complex even after several thousands of iterations the FFNN's results are somewhat poor:
From left to right:
StackOverflow's logo at 256 colors.
With 15 hidden neurons: The left handle never appears.
50 hidden neurons: Pretty poor result in general.
0.03 as learning rate: Shows blue in the results (blue is not in the orignal image)
A time-decreasing learning rate: The left handle appears but other details are now lost.
Try it yourself: https://codepen.io/adelriosantiago/pen/xxVEjeJ
Some parameters of interest are synaptic.Architect.Perceptron definition and learningRate value.
How can I improve the accuracy of this NN?
Could you improve the snippet? If so, please explain what you did. If there is a better NN architecture to tackle this type of job could you please provide an example?
Additional info:
Artificial Neural Network library used: Synaptic.js
To run this example in your localhost: See repository
By adding another layer, you get better results :
let perceptron = new synaptic.Architect.Perceptron(2, 15, 10, 3)
There are small improvements that you can do to improve efficiency (marginally):
Here is my optimized code:
const width = 125
const height = 125
const outputCtx = document.getElementById("output").getContext("2d")
const iterationLabel = document.getElementById("iteration")
const stopAtIteration = 3000
let perceptron = new synaptic.Architect.Perceptron(2, 15, 10, 3)
let iteration = 0
let inputData = (() => {
const tempCtx = document.createElement("canvas").getContext("2d")
tempCtx.drawImage(document.getElementById("input"), 0, 0)
return tempCtx.getImageData(0, 0, width, height)
})()
const getRGB = (img, x, y) => {
var k = (height * y + x) * 4;
return [
img.data[k] / 255, // R
img.data[k + 1] / 255, // G
img.data[k + 2] / 255, // B
//img.data[(height * y + x) * 4 + 3], // Alpha not used
]
}
const paint = () => {
var imageData = outputCtx.getImageData(0, 0, width, height)
for (let x = 0; x < width; x++) {
for (let y = 0; y < height; y++) {
var rgb = perceptron.activate([x / width, y / height])
var k = (height * y + x) * 4;
imageData.data[k] = rgb[0] * 255
imageData.data[k + 1] = rgb[1] * 255
imageData.data[k + 2] = rgb[2] * 255
imageData.data[k + 3] = 255 // Alpha not used
}
}
outputCtx.putImageData(imageData, 0, 0)
setTimeout(train, 0)
}
const train = () => {
iterationLabel.innerHTML = ++iteration
if (iteration > stopAtIteration) return
let learningRate = 0.01 / (1 + 0.0005 * iteration) // Attempt with dynamic learning rate
//let learningRate = 0.01 // Attempt with non-dynamic learning rate
for (let x = 0; x < width; x += 1) {
for (let y = 0; y < height; y += 1) {
perceptron.activate([x / width, y / height])
perceptron.propagate(learningRate, getRGB(inputData, x, y))
}
}
paint()
}
const startTraining = (btn) => {
btn.disabled = true
train()
}
EDIT : I made another CodePen with even better results:
https://codepen.io/xurei/pen/KKzWLxg
It is likely to be over-fitted BTW.
The perceptron definition:
let perceptron = new synaptic.Architect.Perceptron(2, 8, 15, 7, 3)
Taking some insights from the lecture/slides of Bhiksha Raj (from slides 62 onwards), and summarizing as below:
Each node can be assumed like a linear classifier, and combination of several nodes in a single layer of neural networks can approximate any basic shapes. For example, a rectangle can be formed by 4 nodes for each lines, assuming each nodes contributes to one line, and the shape can be approximated by the final output layer.
Falling back to the summary of complex shapes such as circle, it may require infinite nodes in a layer. Or this would likely hold true for a single layer with two disjoint shapes (A non-overlapping triangle and rectangle). However, this can still be learnt using more than 1 hidden layers. Where, the 1st layer learns the basic shapes, followed by 2nd layer approximating their disjoint combinations.
Thus, you can assume that this logo is combination of disjoint rectangles (5 rectangles for orange and 3 rectangles for grey). We can use atleast 32 nodes in 1st hidden layer and few nodes in the 2nd hidden layer. However, we don't have control over what each node learns. Hence, a few more number of neurons than required neurons should be helpful.

Calculating the two x coordinates at edge of circle for a specific y coordinate

How do I find the two opposite x coordinates at the edge of a circle for a specific y coordinate?
A y coordinate of zero means the center of the circle so the two x coordinates would be +- radius
A y coordinate equaling the radius would give two x coordinates of zero.
I'm using Javascript but any language solution is fine.
Assuming you're talking about circle placed at (0,0) (described by equation x²+y²=R²) and you need to return pair of (symmetric) x coordinates based on y and R, that would be something, like:
const getX = (y, R) => [1, -1].map(n => n*(R**2-y**2)**0.5)
Following is a quick proof-of-a-concept live-demo:
const getX = (y, R) => [1, -1].map(n => n*(R**2-y**2)**0.5)
console.log(getX(0,1))
console.log(getX(1,1))
console.log(getX(-1,1))
console.log(getX(0.7071,1))
.as-console-wrapper{min-height:100%;}
If arbitrary circle center ((x0,y0)) is considered ((x-x0)²+(y-y0)²=R²), more generic solution should work:
const getX = (y, R, x0, y0) => [1, -1].map(n => n*(R**2-(y-y0)**2)**0.5+x0)
The existing answer though technically correct is hugely inefficient. The pattern used creates 2 Arrays every call, and repeats the full calculation twice even though the second result is a simple negative of the first (2 rather than 1 square root operations).
The following is 14 times (1400%) faster
const circleXForY = (y, r) => [y = (1 - (y / r) ** 2) ** 0.5 * r, -y];
If you include the fact that the result can also be NaN when y > r then the above function is a staggering 196 times (19,600%) quicker when y > r || y < -r.
A further slight improvement is to just use the positive result
const circleXForY = (y, r) => (1 - (y / r) ** 2) ** 0.5 * r;
The reason I posted a faster version is that this function is very often used when scanning lining circles for graphical like content presentation. I such cases performance is critical.

Canvas Graph plotting data incorrectly

I have made a simple graph in a canvas but am having difficulty with two issues.
The first issue is setting the vertical axis with an appropriate scale automatically with enough room for each data value in an array. Ideally i'd like the numbers to be more rounded to the nearest million or thousand etc depending on it's actual value ranges rather than a value like 33145 as the first scale line.
Currently one value is too high for the scale and is not being drawn on the canvas because it is out of bounds.
The second issue, is the points don't seem to be plotting in their correct location, which I am unsure where my mistake was.
I made a JSFiddle as for the most part it might be a bit confusing without seeing it in action:
http://jsfiddle.net/ezttywzr/
This is how i plot my data and draw my vertical axis:
Vertical Axis:
var x = 0,
y,
range = data.max() - data.min(),
valueStep = range / 10,
// get width of largest number
margin = 3 + ctx.measureText(data.min() + (valueStep*10)).width,
pixelStep = (graph.height-40) / 10,
verticalP = pixelStep,
output;
// draw left hand values
for(var i = 0; i < 11; i++){
output = data.min() + (valueStep*i);
y = graph.height-20 - (verticalP + i*pixelStep);
ctx.fillText(output,x,y+6);
ctx.beginPath();
ctx.moveTo(margin, y);
ctx.lineTo(x2,y);
ctx.stroke();
}
Data Plotting:
var y = graph.height,
x = margin,
pos,
valueStep = (graph.width-(margin*2)) / data.length,
pixelRange = graph.height-20,
pp = range / pixelRange;
for(var i = 0; i < data.length; i++){
x += valueStep;
pos = x - (valueStep/2);
ctx.beginPath();
ctx.moveTo(x, graph.height-20);
ctx.lineTo(x, graph.height);
ctx.stroke();
ctx.fillText('Week '+(i+1),pos-(ctx.measureText('Week '+(i+1)).width/2),y);
ctx.beginPath();
ctx.arc(pos,(graph.height-20)-(verticalP+(data[i]/pp)),2,0,2*Math.PI);
ctx.stroke();
ctx.fill();
}
Nice job so far.
I made a few changes: http://jsfiddle.net/ezttywzr/2/
To get the scale I used
STEP = data.max() / NUM_HORIZONTAL_LINES
Where NUM_HORIZONTAL_LINES is the number of horizontal lines you want above the x-axis. In this case I used 10.
This means the first line will be 1 * STEP, the second will be 2 * STEP, the third will be 3 * STEP and so on..
This scale is convenient because it guarantees that the max value fits on the graph. In fact, the max value is on the top line because of the way we defined the scale.
Once we have our scale it's easy to calculate the position of the points relative to the x-axis. It's simply:
(PIXELS_PER_STEP / STEP) * VALUE
To go a step further you can do some math to round the top point of the graph up and pick a scale with that has nice round numbers.

Convert co-ordinates to isometric and vice versa not working

I have two functions which recieve co-ordinates that are not returning the correct output.
One receives the position of the mouse relative to the element and returns a grid co ordinate for isometric tiles.
The other function essentially reverses this process from iso tile back to pixel position on the screen.
When i send in a co-ordinate for my mouse position and convert it to isometric, then convert it back to pixel position i get a different result from what i started with by a large margin rather than the rounding of the tile size - suggesting I got the maths wrong some where, but am not sure where.
My two functions are:
function isoToScreen(isoX,isoY){ //recieves simple grid co-ordinate (int,int)
var x = (isoX - isoY) * (grid.getWidth()/2),
y = (isoX + isoY) * (grid.getHeight()/2);
//need to remove the camera offset to get the relative position
x = camera.removeOffsetX(x);
y = camera.removeOffsetY(y);
return {'x':x,'y':y};
}
function screenToIso(x,y){ //receives mouse position relative to canvas
//add camera offset to get the correct isometric grid
x = camera.addOffsetX(x);
y = camera.addOffsetY(y);
var isoX = x / (grid.getWidth()/2) + y / (grid.getHeight()/2),
isoY = y / (grid.getHeight()/2) - x / (grid.getWidth()/2);
return {'x':Math.floor(isoX),'y':Math.floor(isoY)}
}
Just some extra info, grid height == 46 and grid width == 92.
Can any one see where i am going wrong in the my maths logic?
In screenToIso you are multiplying the vector [x;y] by the matrix:
[ 2 / grid.getWidth(), 2 / grid.getHeight()]
[ -2 / grid.getWidth(), 2 / grid.getHeight()]
Its inverse is:
[grid.getWidth() / 4, -grid.getWidth() / 4]
[grid.getHeight() / 4, grid.getHeight() / 4]
Hence the first two lines of isoToScreen should be:
var x = (grid.getWidth() / 4) * isoX - (grid.getWidth() / 4) * isoY;
var y = (grid.getHeight() / 4) * isox + (grid.getHeight() / 4) * isoY;

Javascript function for trilinear interpolation

All,
I THINK that I'm looking for a function for Trilinear interpolation.
Here's the details:
I have a three dimensional dataset:
Dimension 1 varies from 0 to 100 in increments of 5
Dimension 2 varies from 0 to 100 in increments of 5
Dimension 3 varies from 0 to 1 in increments of 0.1
So, I have 4851 total values (21 x 21 x 11).
If I need to find the value for (10, 25, 0.3) - that's easy - I can just look it up in the 3-dimensional array.
But, I need to be able to come up with the best approximation, given dimensional values of (17,48,0.73), for example.
So, I think that what I'm looking for is a trilinear interpolation (although I'd definitely appreciate any suggestions for a better method, or a hint that I'm on the wrong topic altogether...)
A quick google search turns up this formula:
Vxyz =
V000(1-x)(1-y)(1-z) +
V100x(1-y)(1-z) +
V010(1-x)y(1-z) +
V001(1-x)(1-y)z +
V101x(1-y)z +
V011(1-x)yz +
V110xy(1-z) +
V111xyz
Which looks like what I'm looking for, but I'm not sure what x, y, and z represent. If I had to guess, x is a ratio - the distance of my "target" first dimension value from the nearest two values I have, y is the ratio for the second dimension, and z is the ratio for the third dimension.
Of course, since I don't really know what I'm talking about, I wouldn't know if this is right or wrong.
So, ideally, I'd like a bit of Javascript or pseudo-code that shows exactly how to accomplish this.
Many thanks in advance!
The code you are looking at is trying to do a weighted average of the 8 points of the cube with vertices that are in your dataset, and which encloses the point you are trying to find a value for.
For a point p
// Find the x, y and z values of the
// 8 vertices of the cube that surrounds the point
x0 = Math.floor(p.x / 5);
x1 = Math.floor(p.x / 5) + 1;
y0 = Math.floor(p.y / 5);
y1 = Math.floor(p.y / 5) + 1;
z0 = Math.floor(p.z / .1);
z1 = Math.floor(p.z / .1) + 1;
// Look up the values of the 8 points surrounding the cube
p000 = dataset[x0][y0][z0];
p001 = dataset[x0][y0][z1];
// ...
// Find the weights for each dimension
x = (x - x0) / 5;
y = (y - y0) / 5;
z = (z - z0) / .1;
// Compute the guess using the method you found
// ...

Categories

Resources