Sort Objects in 3d - javascript

So I'm developing a kind of 3d game with a fixed camera position. I have many entitys, each of which gets these propertys:
Position (coordinateX, coordinateY, coordinateZ)
Hitbox (sizeX, sizeY, sizeZ)
For example, an entity could be structured in this way:
Position (3, 4, 9)
Hitbox (2, 3, 1)
Now I have on my 3d Map many Entitys, which I want to draw. Since I want to draw the objects in the foreground in front of the objects in the background, I have to sort the drawing of the objects in the canvas. That means objects that are behind other objects have to be drawn first and so on...
I have been trying for many hours to figure out how to sort the objects. With this Sort function I came to the end, because unfortunately the calculation doesn't work as it should. Tbh I do not really know how to fix this calculation at all but I thought adding my thoughts to the question is not that bad for all of us.
let objects = {
entity1: {
position: {
x: 0,
y: 10,
z: 5
},
hitbox: {
sizeX: 4,
sizeY: 1
}
},
entity2: {
position: {
x: 4,
y: 2,
z: 4
},
hitbox: {
sizeX: 3,
sizeY: 3
}
}
}
let layeredObjects = Object.values(objects).sort((itemA, itemB) =>  {
if ((itemA.position.x - itemA.hitbox.sizeX < itemB.position.x + itemB.hitbox.sizeX) &&
(itemA.position.y + itemA.hitbox.sizeY < itemB.position.y - itemB.hitbox.sizeY) ||
(itemA.position.x + itemA.hitbox.sizeX < itemB.position.x - itemB.hitbox.sizeX)) return -1;
return 1;
})
console.log(layeredObjects)
So the best solution for my problem would be a method that is able to return the enities sorted (layers) in the way I can easily draw them into my canvas.
I would be very happy about your help.

Related

Javascript Snake Game - too much recursion error

so I'm working on this snake game, and I'm basically trying to prevent the food from spawning on top of the snake tail. My setup variables:
let headX = 10; //snake starting position
let headY = 10;
let appleX = 5; //food starting position
let appleY = 5;
This is the function that checks head/food collision
function checkAppleCollision() {
if (appleX === headX && appleY === headY) {
generateApplePosition();
tailLength++;
score++;
}
}
And this is the function that randomizes the apple position after collision, and also returns the "too much recursion" error, after a couple of collisions:
function generateApplePosition() {
let collisionDetect = false;
let newAppleX = Math.floor(Math.random() * tileCount);
let newAppleY = Math.floor(Math.random() * tileCount);
for (let i = 0; i < snakeTail.length; i++) {
let segment = snakeTail[i];
if (newAppleX === segment.x && newAppleY === segment.y) {
collisionDetect = true;
}
}
while (collisionDetect === true) {
generateApplePosition();
}
appleX = newAppleX;
appleY = newAppleY;
}
Please help, I have no idea what to do here. Everything else works as intended.
Using recursions or do while is a bad idea (I'll explain later)
meanwhile, you could simplify your logic by creating:
reusable samePos() and collides() functions
a recursive createApple() function, which will return itself if the randomly generated x,y positions are occupied by the snake body
const world = {w:6, h:1}; // height set to 1 for this demo only
const snake = [{x:0, y:0}, {x:1, y:0}, {x:2, y:0}, {x:3, y:0}];
const apple = {pos: {x:0, y:0}};
// Check if two object's x,y match
const samePos = (a, b) => a.x === b.x && a.y === b.y;
// Check if object x,y is inside an array of objects
const collides = (ob, arr) => arr.some(o => samePos(ob, o));
const createApple = () => {
const randPos = {
x: ~~(Math.random() * world.w),
y: ~~(Math.random() * world.h),
};
if (collides(randPos, snake)) {
console.log(`position ${randPos.x} ${randPos.y} is occupied by snake`);
return createApple(); // Try another position.
}
// Finally a free spot!
apple.pos = randPos;
console.log(`Apple to free position: ${apple.pos.x} ${apple.pos.y}`);
}
createApple();
Run this demo multiple times
The problem
Useless random guesswork!
As you can see from the example above, if you run it multiple times, very often the randomly generated number is the same as the previously generated one:
...
position 2 0 is occupied by snake <<<
position 1 0 is occupied by snake
position 2 0 is occupied by snake <<<
position 2 0 is occupied by snake <<<
position 1 0 is occupied by snake
position 2 0 is occupied by snake <<<
...
therefore, as your snake grows in size, the recursion might go wild — ad absurdum, iterating way too many times, repeating and failing on the same xy positions, until finally hitting a rare free spot...
This is a really bad design.
Solutions
One solution would be to keep track of the already used randomized positions inside an Array - but that implies unnecessarily to go trough such an Array.
A best solution would be to actually treat the game not as a 2D game, but as a 1D game:
Consider this 2D map of size 4x3 as indexes:
0 1 2 3
4 5 6 7
8 9 10 11
now, let's place a snake into this map:
0 ⬛ 2 3
4 ⬛ ⬛ 7
8 9 ⬛ 11
here's the linear map with the Snake as a 1D list:
[ 0 ⬛ 2 3 4 ⬛ ⬛ 7 8 9 ⬛ 11 ]
therefore, instead of using an array of objects {x:n, y:n} for the snake body positions, all you need is:
[1, 5, 6, 10] // Snake body as indexes
Now that you know all the indexes where you're not allowed to place an Apple, all you need to do when creating the new apple is:
Create an Array of 0-N indexes of length: world.w * world.h
Loop the snake body indexes and delete those indexes from the array of indexes to get an Array of free spots indexes
Simply get only once a random key from that array of free spots!
const indexToXY = (index, width) => ({ x: index%width, y: Math.trunc(index/width) });
const world = {w:4, h:3};
const snakeBody = [1, 5, 6, 10];
const createApple = () => {
const arr = [...Array(world.w * world.h).keys()]; // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
snakeBody.forEach(i => delete arr[i]);
const freeIndexes = arr.filter(k => k !== undefined); // [0, 2, 3, 4, 7, 8, 9, 11]
const appleIndex = freeIndexes[~~(Math.random() * freeIndexes.length)];
const applePos = indexToXY(appleIndex, world.w);
console.log("New apple position: %o", applePos);
};
createApple();
Run this demo multiple times
Having that free spot index simply draw your apple at the XY coordinates using this simple formula
X = index % mapWidth
Y = floor(index / mapWidth)
As others have said, this doesn't need to be recursive, and you should also take into account the (however unlikely) possibility where there are no more tiles to spawn on which would result in an infinite loop.
function generateApplePosition() {
// Count how many tiles are left for spawning in
const tilesLeft = (tileCount * tileCount) - snakeTail.length;
let collisionDetect;
if (tilesLeft > 0) {
do {
const newAppleX = Math.floor(Math.random() * tileCount);
const newAppleY = Math.floor(Math.random() * tileCount);
collisionDetect = false;
for (let i = 0; i < snakeTail.length; i++) {
const { x, y } = snakeTail[i];
if (newAppleX === x && newAppleY === y) {
collisionDetect = true; // Collision
break;
}
}
if (!collisionDetect) {
// Found spawn point
appleX = newAppleX;
appleY = newAppleY;
}
} while (collisionDetect);
}
}

How to calculate 'square' distance, in steps in 8 directions

Note: I probably could google this problem, but I don't know the right terminology to use.
So I'm working on a square grid where pieces are able to move in 8 directions (i.e. horizontal, vertical or diagonal), and for some decision making processes I need to calculate the "square" distance between spaces. Which is the number of steps it would take to get there in those 8 directions. It would look something like this (colours added for clarity.
So far, I've made this attempt to calculate the directions
distance_between(source, target) {
var x_dist = Math.abs(source.x - target.x);
var y_dist = Math.abs(source.y - target.y);
return x_dist + y_dist;
}
This is more of a diamond pattern, the steps it would take to get there in 4 directions (just horizontal or vertical). The result looks like this:
I feel like there should be a simple way to calculate the square distance, but short of taking repeated steps in that direction, I can't work out what that would be. How can I go about finding the square distance between two spaces?
It might also be useful to know what this kind of distance is called, so I can look for geometry resources on this.
The distance that you are looking for will be the greater of x_dist or y_dist in your current function.
function distance_between(source, target) {
var x_dist = Math.abs(source.x - target.x);
var y_dist = Math.abs(source.y - target.y);
return Math.max(x_dist, y_dist);
}
const
matrix = Array.from({ length: 7 }, (_, i) => Array.from({ length: 7 }, (_, j) => ({ x: j, y: i }))),
mapDistancesFrom = (source = { x: 0, y: 0 }) =>
matrix.map((row) => row.map((col) => distance_between(source, col)));
console.log('Matrix');
matrix.forEach(row => console.log(row.map(({ x, y }) => `(${x},${y})`).join(' ')));
console.log('\nDistances from (3,3)');
mapDistancesFrom({ x: 3, y: 3 }).forEach(row => console.log(row.join(' ')));
console.log('\nDistances from (2,2)');
mapDistancesFrom({ x: 2, y: 2 }).forEach(row => console.log(row.join(' ')));
.as-console-wrapper { max-height: 100% !important; top: 0; }

d3: sort a list of coordinates by x-value

I have a list of coordinates of (x, y) pair, and I want to create a new list of coordinates that's sorted by the x-value in ascending order. How can I achieve this using d3/javascript (I just started learning a few days ago)? I've found that stack.order() could be useful, but I'm not sure. Thanks so much!
assuming your coordinates array looks like this:
var coordinates = [
{ x: 3, y: 2},
{ x: 1, y: 1}
}
you can use:
coordinates.sort(function(a, b){
return a.x - b.x;
})
see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort

Smoothing out values of an array

If I had an array of numbers such as [3, 5, 0, 8, 4, 2, 6], is there a way to “smooth out” the values so they’re closer to each other and display less variance?
I’ve looked into windowing the data using something called the Gaussian function for a 1-dimensional case, which is my array, but am having trouble implementing it. This thread seems to solve exactly what I need but I don’t understand how user naschilling (second post) came up with the Gaussian matrix values.
Context: I’m working on a music waveform generator (borrowing from SoundCloud’s design) that maps the amplitude of the song at time t to a corresponding bar height. Unfortunately there’s a lot of noise, and it looks particularly ugly when the program maps a tiny amplitude which results in a sudden decrease in height. I basically want to smooth out the bar heights so they aren’t so varied.
The language I'm using is Javascript.
EDIT: Sorry, let me be more specific about "smoothing out" the values. According to the thread linked above, a user took an array
[10.00, 13.00, 7.00, 11.00, 12.00, 9.00, 6.00, 5.00]
and used a Gaussian function to map it to
[ 8.35, 9.35, 8.59, 8.98, 9.63, 7.94, 5.78, 7.32]
Notice how the numbers are much closer to each other.
EDIT 2: It worked! Thanks to user Awal Garg's algorithm, here are the results:
No smoothing
Some smoothing
Maximum smoothing
EDIT 3: Here's my final code in JS. I tweaked it so that the first and last elements of the array were able to find its neighbors by wrapping around the array, rather than calling itself.
var array = [10, 13, 7, 11, 12, 9, 6, 5];
function smooth(values, alpha) {
var weighted = average(values) * alpha;
var smoothed = [];
for (var i in values) {
var curr = values[i];
var prev = smoothed[i - 1] || values[values.length - 1];
var next = curr || values[0];
var improved = Number(this.average([weighted, prev, curr, next]).toFixed(2));
smoothed.push(improved);
}
return smoothed;
}
function average(data) {
var sum = data.reduce(function(sum, value) {
return sum + value;
}, 0);
var avg = sum / data.length;
return avg;
}
smooth(array, 0.85);
Interesting question!
The algorithm to smooth out the values obviously could vary a lot, but here is my take:
"use strict";
var array = [10, 13, 7, 11, 12, 9, 6, 5];
function avg (v) {
return v.reduce((a,b) => a+b, 0)/v.length;
}
function smoothOut (vector, variance) {
var t_avg = avg(vector)*variance;
var ret = Array(vector.length);
for (var i = 0; i < vector.length; i++) {
(function () {
var prev = i>0 ? ret[i-1] : vector[i];
var next = i<vector.length ? vector[i] : vector[i-1];
ret[i] = avg([t_avg, avg([prev, vector[i], next])]);
})();
}
return ret;
}
function display (x, y) {
console.clear();
console.assert(x.length === y.length);
x.forEach((el, i) => console.log(`${el}\t\t${y[i]}`));
}
display(array, smoothOut(array, 0.85));
NOTE: It uses some ES6 features like fat-arrow functions and template strings. Firefox 35+ and Chrome 45+ should work fine. Please use the babel repl otherwise.
My method basically computes the average of all the elements in the array in advance, and uses that as a major factor to compute the new value along with the current element value, the one prior to it, and the one after it. I am also using the prior value as the one newly computed and not the one from the original array. Feel free to experiment and modify according to your needs. You can also pass in a "variance" parameter to control the difference between the elements. Lowering it will bring the elements much closer to each other since it decreases the value of the average.
A slight variation to loosen out the smoothing would be this:
"use strict";
var array = [10, 13, 7, 11, 12, 9, 6, 5];
function avg (v) {
return v.reduce((a,b) => a+b, 0)/v.length;
}
function smoothOut (vector, variance) {
var t_avg = avg(vector)*variance;
var ret = Array(vector.length);
for (var i = 0; i < vector.length; i++) {
(function () {
var prev = i>0 ? ret[i-1] : vector[i];
var next = i<vector.length ? vector[i] : vector[i-1];
ret[i] = avg([t_avg, prev, vector[i], next]);
})();
}
return ret;
}
function display (x, y) {
console.clear();
console.assert(x.length === y.length);
x.forEach((el, i) => console.log(`${el}\t\t${y[i]}`));
}
display(array, smoothOut(array, 0.85));
which doesn't take the averaged value as a major factor.
Feel free to experiment, hope that helps!
The technique you describe sounds like a 1D version of a Gaussian blur. Multiply the values of the 1D Gaussian array times the given window within the array and sum the result. For example
Assuming a Gaussian array {.242, .399, .242}
To calculate the new value at position n of the input array - multiply the values at n-1, n, and n+1 of the input array by those in (1) and sum the result. eg for [3, 5, 0, 8, 4, 2, 6], n = 1:
n1 = 0.242 * 3 + 0.399 * 5 + 0.242 * 0 = 2.721
You can alter the variance of the Gaussian to increase or reduce the affect of the blur.
i stumbled upon this post having the same problem with trying to achieve smooth circular waves from fft averages.
i've tried normalizing, smoothing and wildest math to spread the dynamic of an array of averages between 0 and 1. it is of course possible but the sharp increases in averaged values remain a bother that basically makes these values unfeasable for direct display.
instead i use the fft average to increase amplitude, frequency and wavelength of a separately structured clean sine.
imagine a sine curve across the screen that moves right to left at a given speed(frequency) times the current average and has an amplitude of current average times whatever will then be mapped to 0,1 in order to eventually determine 'the wave's' z.
the function for calculating size, color, shift of elements or whatever visualizes 'the wave' will have to be based on distance from center and some array that holds values for each distance, e.g. a certain number of average values.
that very same array can instead be fed with values from a sine - that is influenced by the fft averages - which themselves thus need no smoothing and can remain unaltered.
the effect is pleasingly clean sine waves appearing to be driven by the 'energy' of the sound.
like this - where 'rings' is an array that a distance function uses to read 'z' values of 'the wave's x,y positions.
const wave = {
y: height / 2,
length: 0.02,
amplitude: 30,
frequency: 0.5
}
//var increment = wave.frequency;
var increment = 0;
function sinewave(length,amplitude,frequency) {
ctx.strokeStyle = 'red';
ctx.beginPath();
ctx.moveTo(0, height / 2);
for (let i = 0; i < width; i+=cellSize) {
//ctx.lineTo(i, wave.y + Math.sin(i * wave.length + increment) * wave.amplitude)
ctx.lineTo(i, wave.y + Math.sin(i * length + increment) * amplitude);
rings.push( map( Math.sin(i * length + increment) * amplitude,0,20,0.1,1) );
rings.shift();
}
ctx.stroke();
increment += frequency;
}
the function is called each frame (from draw) with the current average fft value driving the sine function like this - assuming that value is mapped to 0,1:
sinewave(0.006,averg*20,averg*0.3)
allowing fluctuating values to determine wavelength or frequency can have some visually appealing effect. however, the movement of 'the wave' will never seem natural.
i've accomplished a near enough result in my case.
for making the sine appear to be driven by each 'beat' you'd need beat detection to determine the exact tempo of 'the sound' that 'the wave' is supposed to visualize.
continuous averaging of distance between larger peaks in the lower range of fft spectrum might work there with setting a semi fixed frequency - with edm...
i know, the question was about smoothing array values.
forgive me for changing the subject. i just thought that the objective 'sound wave' is an interesting one that could be achieved differently.
and just so this is complete here's a bit that simply draws circles for each fft and assign colour according to volume.
with linewidths relative to total radius and sum of volumes this is quite nice:
//col generator
function getCol(n,m,f){
var a = (PIx5*n)/(3*m) + PIdiv2;
var r = map(sin(a),-1,1,0,255);
var g = map(sin(a - PIx2/3),-1,1,0,255);
var b = map(sin(a - PIx4/3),-1,1,0,255);
return ("rgba(" + r + "," + g + "," + b + "," + f + ")");
}
//draw circles for each fft with linewidth and colour relative to value
function drawCircles(arr){
var nC = 20; //number of elem from array we want to use
var cAv = 0;
var cAvsum = 0;
//get the sum of all values so we can map a single value with regard to this
for(var i = 0; i< nC; i++){
cAvsum += arr[i];
}
cAv = cAvsum/nC;
var lastwidth = 0;
//draw a circle for each elem from array
//compute linewith a fraction of width relative to value of elem vs. sum of elems
for(var i = 0; i< nC; i++){
ctx.beginPath();
var radius = lastwidth;//map(arr[i]*2,0,255,0,i*300);
//use a small col generator to assign col - map value to spectrum
ctx.strokeStyle = getCol(map(arr[i],0,255,0,1280),1280,0.05);
//map elem value as fraction of elem sum to linewidth/total width of outer circle
ctx.lineWidth = map(arr[i],0,cAvsum,0,width);
//draw
ctx.arc(centerX, centerY, radius, 0, Math.PI*2, false);
ctx.stroke();
//add current radius and linewidth to lastwidth
var lastwidth = radius + ctx.lineWidth/2;
}
}
codepen here: https://codepen.io/sumoclub/full/QWBwzaZ
always happy about suggestions.

Advanced Sorting Algorithm

I'm writing an app in Javascript that uses Google maps and calculates a full route between a series of legs based off closest legs.
Say I've got 4 legs on the map, each leg has a start and end Latitude and Longitude. The user specifies which two legs are the start and end of the route. Each end of a leg can only connect to another legs start. Then the end of that leg connects to another legs start, and so on. The code determines which legs start to connect to based off the closest leg it can find.
Something like this for example (the dashed lines are the legs):
start-----------end <-connector-> start----------end <-connector-> start----------end
I've got an array of all of the leg coordinates, and I want to sort this array so that it follows the proper progression of connections. Then I can utilize the array to generate the connectors by linearly looping through it.
The array looks something like this:
[
{start_lat: X, start_lng: X, end_lat: X, end_lng: X},
{start_lat: X, start_lng: X, end_lat: X, end_lng: X},
]
Those will be the inner legs. And then I'll have the outer legs (the two legs that are the start and the end of the entire route) stored in variables:
var start = {end_lat: X, end_lng: X}
var end = {start_lat: X, start_lng: X}
As an example it might end up something like:
start -> array[0] -> array[1] -> end
Or it might end up like:
start -> array[1] -> array[0] -> end
The algorithm needs to sort the array based off the start legs end_lat,end_lng and the end legs end_lat,end_lng.
The end result will be a big route connected together with the shortest path.
I'm struggling to think of a way of writing the sorting algorithm that takes these unique factors into consideration.
It's difficult to put this into words, and I'm not really sure how I can help make it clearer, but I'll edit this post if I can think of anything useful to add. Thanks.
Edit:
Here's a picture of what I'm talking about:
The black lines are the legs, the red lines are the connectors I will need to generate after I've sorted the array of leg coordinates into the correct order. The generating the connectors isn't part of this algorithm, but it's just an example of what I'm trying to accomplish so you can understand the big picture. As you can see there are gaps between the legs, none of the coordinates overlap.
You could do something like this:
DEMO
var start = { end_lat: 1, end_lng: 1 },
end = { start_lat: 4, start_lng: 4 },
coordsBetween = [
{ start_lat: 2, start_lng: 2, end_lat: 3, end_lng: 3 },
{ start_lat: 1, start_lng: 1, end_lat: 2, end_lng: 2 },
{ start_lat: 3, start_lng: 3, end_lat: 4, end_lng: 4 }
];
function orderedCoords(start, coords) {
var result = [],
point = start;
while (point = coords.filter(function (item) {
return item.start_lat === point.end_lat
&& item.start_lng === point.end_lng;
})[0]) {
result.push(point);
}
return result;
}
console.log(orderedCoords(start, coordsBetween));
Basically we find the next point that starts where start ends and let that point become the next start until there's no match and at each step we push the point into result.
EDIT:
This would work of the coordinates of the start and end point
overlapped, but none of mine do, there is a large gap between them and
I need to generate the 'connectors' between them...
I have expanded my first idea by using an algorithm to calculate the closest point insead of looking for overlapping coords.
DEMO
var start = { end_lat: 1, end_lng: 1 },
end = { start_lat: 4, start_lng: 4 },
segments = [
{ start_lat: 2, start_lng: 2, end_lat: 3, end_lng: 3 },
{ start_lat: 1, start_lng: 1, end_lat: 2, end_lng: 2 },
{ start_lat: 3, start_lng: 3, end_lat: 4, end_lng: 4 }
];
function orderedSegments(start, segments) {
var result = [],
segment = start,
i;
while ((i = indexOfClosestSegment(segment, segments)) !== -1) {
result.push(segment = segments.splice(i, 1)[0]);
}
return result;
}
function indexOfClosestSegment(segment, segments) {
var i = 0,
len = segments.length,
segIndex = -1,
tempDistance, smallestDistance;
for (; i < len; i++) {
if (
(tempDistance = distanceBetween(segment, segments[i])) < smallestDistance
|| typeof smallestDistance === 'undefined') {
smallestDistance = tempDistance;
segIndex = i;
}
}
return segIndex;
}
function distanceBetween(segmentA, segmentB) {
return Math.sqrt(
Math.pow(segmentB.start_lat - segmentA.end_lat, 2)
+ Math.pow(segmentB.start_lng - segmentA.end_lng, 2)
);
}
console.log(orderedSegments(start, segments));
Notice that the points are geo coordinates and you will need to use a
spherical distance algorithm, not the trivial pythagoras. I think GM
provides such helper functions for their data structures; of course
the algorithm will still work with a different distanceBetween
implementation. – #Bergi
I think DFS is Okay and transfer visited ordered leg list to next recursion.
and in every recursion choose the last leg and recursive with each unvisted legs.

Categories