Arranging rectangles in a circle with equal distance - javascript

I'm writing a program that renders mind maps. So far, I've succeeded in drawing the root node and the first level of child nodes around it, in a circle.
This is the code that renders the mind map nodes:
const root = data.find((node) => node.parent === undefined);
const level1 = data.filter((node) => node.parent === root.id);
root.x = 0;
root.y = 0;
root.level = 0;
await addMindMapNode(scene, root);
const radius = 2;
const slice = (2 * Math.PI) / level1.length;
for (let i = 0; i < level1.length; i++) {
const level1node = level1[i];
level1node.level = 1;
const angle = slice * i;
const x = root.x + radius * Math.cos(angle);
const y = root.y + radius * Math.sin(angle);
level1node.x = x;
level1node.y = y;
await addMindMapNode(scene, level1node);
}
You can find the whole program on CodeSandbox.
The output looks like this (with different number of child nodes, from 1 child to 9 children):
As you can see, depending on the number of child nodes, the distribution of the child nodes around the root node in the center looks more or less uneven and not pleasing to the eye, especially with 3, 7 or 9 child nodes.
This is because of the rectangular format of the child node boxes. The center points of the boxes have the exact same distance between them, but the length of the distance (shown as red line segments below) is different, depending on position:
I have to find a way to make it so that the red circle sections are equal in size.
So my question is:
How can I calculate the angle at which to render each purple box so that the distance between each box looks to be the same (i.e. take into account the width and height of each box)?

Okay, here's my attempt (with diagrams too!) 😅:
So I noticed that the problem was that the calculated angles being used to generate the co-ordinates of the rectangles needed to shift a little by some small angle β. Something like:
Somebody more math-savvy than I am can probably do better, but for my attempt I noticed that I could tinker with the angle generated by the rectangle itself, given its dimensions. Like:
So, plugging that in:
const angle = slice * i;
// == addition starts here
const opp_hyp = 60/120;
const b_prime_degrees = Math.atan(opp_hyp);
const b_prime = 180 * b_prime_degrees/Math.PI; // converting to radians
const x = root.x + (radius * Math.cos(angle-b_prime));
const y = root.y + (radius * Math.sin(angle-b_prime));
// == addition ends here
Using that, I got the following outcomes for 3, 7 and 9:
I wasn't too satisfied, so totally on a whim, I decided to try the other angle in that triangle i.e. 180-β
const angle = slice * i;
// == addition starts here
const opp_hyp = 60/120;
const b_prime_degrees = Math.atan(opp_hyp);
const new_guy = 180 * (180-b_prime_degrees)/Math.PI;
const x = root.x + (radius * Math.cos(angle-new_guy));
const y = root.y + (radius * Math.sin(angle-new_guy));
// == addition ends here
Using that, I got the following outcomes for 3, 7 and 9:
...which kinda looks better, sort of.
Hopefully somebody more knowledgeable than I am comes along and helps you with a definitive answer. Till then, I hope my attempts at least help nudge you in the right direction.

Related

Figuring out the value for PI

Let's say I have a function called bars()
bars () {
const bars = []
for (let i = 0; i < this.numberOfBars; i++) {
bars.push(Math.sqrt(this.numberOfBars * this.numberOfBars - i * i))
}
return bars
}
If I'm reducing the bars array to approximate PI, what should be on the right side of the arrow function?
PI = bars().reduce((a, b) =>
I tried adding the values and dividing by the number of bars, but I'm not getting anywhere near the approximation of Pi. I feel like there's a simple trick that I'm missing.
Your funcion seems to list lengths of "bars" in a quarter of a circle, so we have to add them all up (to have the area of the quarter of a circle), then multiply by 4 (because there is 4 quarter) and the divide by this.numberOfBars ^ 2 because area = π * r^2, but like we have to know the radius, it is better using a pure function :
// Your function rewritten as a pure one
const bars = numberOfBars => {
const bars = []
for (let i = 0; i < numberOfBars; i++) {
bars.push(Math.sqrt(numberOfBars * numberOfBars - i * i))
}
return bars
}
// Here we take 1000 bars as an example but in your case you replace it by this.numberOfBars
// Sum them all up, multiply by 4, divide by the square of the radius
const PI = bars(1000).reduce((g, c) => g + c) * 4 / Math.pow(1000, 2)
console.log(PI)
/** Approximates PI using geometry
* You get a better approximation using more bars and a smaller step size
*/
function approximatePI(numberOfBars, stepSize) {
const radius = numberOfBars * stepSize;
// Generate bars (areas of points on quarter circle)
let bars = [];
// You can think of i as some point along the x-axis
for (let i = 0; i < radius; i += stepSize) {
let height = Math.sqrt(radius*radius - i*i)
bars.push(height * stepSize);
}
// Add up all the areas of the bars
// (This is approximately the area of a quarter circle if stepSize is small enough)
const quarterArea = bars.reduce((a, b) => a + b);
// Calculate PI using area of circle formula
const PI = 4 * quarterArea / (radius*radius)
return PI;
}
console.log(`PI is approximately ${approximatePI(100_000, 0.001)}`);
There is no reason to push all terms to an array, then to reduce the array by addition. Just use an accumulator variable and add all terms to it.
Notice that the computation becomes less and less accurate the closer you get to the end of the radius. If you sum to half of the radius, you obtain r²(3√3+π)/24, from which you can draw π.
(Though in any case, this is one of the worst methods to evaluate π.)

noisy line between two specific points P5.js

I'm trying to draw a noisy line (using perlin noise) between two specific points.
for example A(100, 200) and B(400,600).
The line could be a points series.
Drawing random noisy line is so clear but I dont know how can I calculate distance specific points.
working of P5.js.
I don't have any code written yet to upload.
Please can anyone help me?
I tried to add sufficient comments that you would be able to learn how such a thing is done. There are a number of things that you should make yourself aware of if you aren't already, and it's hard to say which if these you're missing:
for loops
drawing lines using beginShape()/vertex()/endShape()
Trigonometry (in this case sin/cos/atan2) which make it possible to find angles and determine 2d offsets in X and Y components at a given angle
p5.Vector() and its dist() function.
// The level of detail in the line in number of pixels between each point.
const pixelsPerSegment = 10;
const noiseScale = 120;
const noiseFrequency = 0.01;
const noiseSpeed = 0.1;
let start;
let end;
function setup() {
createCanvas(400, 400);
noFill();
start = createVector(10, 10);
end = createVector(380, 380);
}
function draw() {
background(255);
let lineLength = start.dist(end);
// Determine the number of segments, and make sure there is at least one.
let segments = max(1, round(lineLength / pixelsPerSegment));
// Determine the number of points, which is the number of segments + 1
let points = 1 + segments;
// We need to know the angle of the line so that we can determine the x
// and y position for each point along the line, and when we offset based
// on noise we do so perpendicular to the line.
let angle = atan2(end.y - start.y, end.x - start.x);
let xInterval = pixelsPerSegment * cos(angle);
let yInterval = pixelsPerSegment * sin(angle);
beginShape();
// Always start with the start point
vertex(start.x, start.y);
// for each point that is neither the start nor end point
for (let i = 1; i < points - 1; i++) {
// determine the x and y positions along the straight line
let x = start.x + xInterval * i;
let y = start.y + yInterval * i;
// calculate the offset distance using noice
let offset =
// The bigger this number is the greater the range of offsets will be
noiseScale *
(noise(
// The bigger the value of noiseFrequency, the more erretically
// the offset will change from point to point.
i * pixelsPerSegment * noiseFrequency,
// The bigger the value of noiseSpeed, the more quickly the curve
// fluxuations will change over time.
(millis() / 1000) * noiseSpeed
) - 0.5);
// Translate offset into x and y components based on angle - 90°
// (or in this case, PI / 2 radians, which is equivalent)
let xOffset = offset * cos(angle - PI / 2);
let yOffset = offset * sin(angle - PI / 2);
vertex(x + xOffset, y + yOffset);
}
vertex(end.x, end.y);
endShape();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
This code makes jaggy lines, but they could be smoothed using curveVertex(). Also, making the line pass through the start and end points exactly is a little tricky because the very next point may be offset by a large amount. You could fix this by making noiseScale very depending on how far from an endpoint the current point is. This could be done by multiplying noiseScale by sin(i / points.length * PI) for example.

Natural Movement with Noise

Im creating an object that randomly moves in a natural way using noise like this (works as intended):
The objects encounter a collision and their trajectory is manipulated, the movement path now changes to straight line (words as intended)
thisRabbit.x = _world.width * (noise(thisRabbit.t));
thisRabbit.y = _world.height * (noise(thisRabbit.t+5));
thisRabbit.t += 0.001;
The problem is after this movement , i want the object to start moving in a random direction again as it was initially. If i use the same function, the object jumps to the last location before the trajectory was modified.
let vx = this.acquiredFood[0] - this.x;
let vy = this.acquiredFood[1] - this.y;
let f = (this.genes.speed + 10) / Math.sqrt(vx*vx+vy*vy);
vx = vx * f;
vy = vy * f;
let newX = this.x + vx;
let newY = this.y + vy;
So how do i get the object to move as before, given a starting position
edit: snippet here: https://editor.p5js.org/vince.chinner/sketches/HPFKR8eIw
Your problem is that you used a factor from 0 to 1 generated with noise and an incremented seed to generate the position by multiplying directly the world dimentions. When reaching food, you cannot increment the seed as to be in the exact position where the movement to get your food led you (I found no inverse function for noise to get the seed from the return value).
What you need to do instead is use the noise to increment or decrement the coordinates, so that no matter where the seed is, you don't loose your current position.
Here are the different corrections I applied to the code, as there were also syntax errors, I can't really paste the whole stuff here for copyright reasons (you didn't share the whole code here and the sketch belongs to you)
MAIN CORRECTION:
used a var found because returning from the forEach callback doesn't make you leave the findFood function, but the callback one. And the forEach loop doesn't stop. Using this var prevents the further forEach tests to be made and allows you to return from findFood so that no further move is made after seeing food.
noise is now applied to a value of 4 and I subtract 2, so that x and y now change with a range of -2 to 2 each. Of course, with this method, you need to check against world dimentions or else the rabbit could leave the world. The seed increment has been changed too or else it would vary too slowly (adapt values as you wish)
findFood(){
var thisRabbit = this, found = false;
_world.food.forEach(f => {
if(!found){
let d = int(dist(f[0], f[1], thisRabbit.x, thisRabbit.y));
if(d < (thisRabbit.genes.vision / 2)+3){
thisRabbit.state = "foundFood";
this.acquiredFood = f;
found = true;
}
}
});
if(found){ return; }
thisRabbit.x += (noise(thisRabbit.t) * 4) - 2;
if(thisRabbit.x < 0){ thisRabbit.x = 0; }
if(thisRabbit.x > _world.width){ thisRabbit.x = _world.width; }
thisRabbit.y += (noise(thisRabbit.t + 5) * 4) - 2;
if(thisRabbit.y < 0){ thisRabbit.y = 0; }
if(thisRabbit.y > _world.height){ thisRabbit.y = _world.height; }
thisRabbit.t += 0.01;
}
SYNTAX ERRORS:
lines 23 / 24: assignment should be with a value (null or false)
this.genes = null;
this.acquiredFood = null;
lines 129 to 133: end you instructions with a ; instead of a ,
this.width = w;
this.height = h;
this.foodDensity = foodDensity;
this.food = [];
this.rabits = [];
line 156 to 160: there should be no space between rabbit and .t. Additionnally, because the coordinates are not directly linked to t, I would prefer to use random for starting position:
let x = this.width * random();
let y = this.height * random();
let _rabbit = new rabbit(x, y);
_rabbit.genes = genes;
_rabbit.t = t;

How can I simplify my game enemy spawning algorithm? (code snippet included)

I have a hero character in the middle of the screen and I want to spawn zombies all around him in random positions but some distance away from him. heroDistance defines this distance.
It does not matter if they are pushed outside the boundaries of the screen when they are spawned, they all come towards him. It would not matter if this did not happen, but it just seemed easier.
At the moment the random location of the zombie is created for the x axis with random(screenWidth) and y axis random(screenHeight), and those values are fed into the spawnLocation function that depending on where they are in relation to the hero are either increased or decreased to move they away.
My code seems far too verbose, even though I have worked really hard on it. Am I missing some obvious technique to make it simpler?
const state = {
options: {
numberOfZombies: 10,
},
characters: {
hero: {
xPosition: 150,
yPosition: 150,
},
},
};
const screenWidth = 400;
const screenHeight = 400;
const random = range => Math.floor(Math.random() * range);
function createZombies(state) {
const heroDistance = 10;
const spawnLocation = (zomPos, heroPos, axisLength) => {
return zomPos > heroPos
? zomPos + axisLength / heroDistance
: zomPos - axisLength / heroDistance;
};
for (let index = 0; index < state.options.numberOfZombies; index += 1) {
console.log({
xPosition: spawnLocation(
random(screenWidth),
state.characters.hero.xPosition,
screenWidth,
),
yPosition: spawnLocation(
random(screenHeight),
state.characters.hero.yPosition,
screenHeight,
),
});
}
}
createZombies(state);
Generate a random angle and radius, and then transform these values into Cartesian coordinates.
let theta = Math.random() * (2 * Math.PI)
let r = Math.random() * variationInR + minimumR
let zombieX = Math.cos(theta) * r + heroX
let zombieY = Math.sin(theta) * r + heroY
If you want these to be integers, then make them so. This generates zombies uniformly radially from the hero at least minimumR units away (Pythagorean distance). If you want to maintain the Manhattan distance behavior, then generate your dX and dY and add them to the hero's position.

Algorithm for random land in a "Tank Wars" game [closed]

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 9 years ago.
Did you ever played the "Tank wars" game?
I'm programming this game with JavaScript + Canvas (for a personal challenge), and what I need is an algorithm for generating that random green land every time I start the game, but I'm not too good at maths, so I can't do it myself.
I don't want someone to give me the code, I only want the idea for the algorithm.
Thanks!
(9 segments)
Fiddle demo
(7 segments)
The main generation function look like this:
var numOfSegments = 9; // split horizontal space
var segment = canvas.width / numOfSegments; // calc width of each segment
var points = [], calcedPoints;
var variations = 0.22; // adjust this: lower = less variations
var i;
//produce some random heights across the canvas
for(i=0; i < numOfSegments + 1; i++) {
points.push(segment * i);
points.push(canvas.height / 2.8 + canvas.height * variations * Math.random());
}
//render the landscape
ctx.beginPath();
ctx.moveTo(canvas.width, canvas.height);
ctx.lineTo(0, canvas.height);
calcedPoints = ctx.curve(points); // see below
ctx.closePath();
ctx.fillStyle = 'green';
ctx.fill();
The curve() function is a separate function which generate a cardinal spline. In here you can modify it to also store tension values to make more spikes. You can also used the generated points as a basis for where and at what angle the tanks will move at.
The function for cardinal spline:
CanvasRenderingContext2D.prototype.curve = function(pts, tension, numOfSegments) {
tension = (tension != 'undefined') ? tension : 0.5;
numOfSegments = numOfSegments ? numOfSegments : 16;
var _pts = [], res = [], t, i, l, r = 0,
x, y, t1x, t2x, t1y, t2y,
c1, c2, c3, c4, st, st2, st3, st23, st32;
_pts = pts.concat();
_pts.unshift(pts[1]);
_pts.unshift(pts[0]);
_pts.push(pts[pts.length - 2]);
_pts.push(pts[pts.length - 1]);
l = (_pts.length - 4);
for (i = 2; i < l; i+=2) {
//overrides and modifies tension for each segment.
tension = 1 * Math.random() - 0.3;
for (t = 0; t <= numOfSegments; t++) {
t1x = (_pts[i+2] - _pts[i-2]) * tension;
t2x = (_pts[i+4] - _pts[i]) * tension;
t1y = (_pts[i+3] - _pts[i-1]) * tension;
t2y = (_pts[i+5] - _pts[i+1]) * tension;
st = t / numOfSegments;
st2 = st * st;
st3 = st2 * st;
st23 = st3 * 2;
st32 = st2 * 3;
c1 = st23 - st32 + 1;
c2 = -(st23) + st32;
c3 = st3 - 2 * st2 + st;
c4 = st3 - st2;
x = c1 * _pts[i] + c2 * _pts[i+2] + c3 * t1x + c4 * t2x;
y = c1 * _pts[i+1] + c2 * _pts[i+3] + c3 * t1y + c4 * t2y;
res[r++] = x;
res[r++] = y;
} //for t
} //for i
l = res.length;
for(i=0;i<l;i+=2) this.lineTo(res[i], res[i+1]);
return res; //return calculated points
}
Look into perlin noise generation, this in combination with a good smoothing algorithm can produce some pretty good terrain, and is fairly quick. There is a reference version of the code kicking around the net somewhere, which should provide you with a fairly hefty headstart
First you need a point that is random y (between 55,65); got x=0
So this is the origin point for the green, lets keep it as x1,y1 (x1 always 0).
Then you need a random integer between 30 to 40. This is x2. And a random y which is in the range y1 + 8 to y1 + 20.
Then x3 and y3 on same principle (lets call it formula type 1)
Now you need to first get a random either -1 or 1, this will be directions of y4. So y4 can go higher than y3 or lower ... this will be formula type 2.
You need to keep a max and min y for a new y, if it crosses that then go the other way -> this will be a correction type formula 3.
Xn keeps increasing till its >= width of board.
Join the lines in a eclipses ... and looks like web searches is the way to go !
I am sure there are a lot of coded libraries that you could use to make this easy. But if you are trying to code this by yourself, here is my idea.
You need to define terrain from everything else. So every part of your environment is a cluster for example. You need to define how are separated these clusters, by nodes(points) for example.
You can create a polygon from a sequence of points, and this polygon can become whatever you want, in this case terrain.
See that on the image you passed, there are peaks, those are the nodes (points). Remember to define also nodes on the borders of your environment.
There are surely a novel, written algorithms, either fractal as #DesertIvy pointed out or others, maybe there are libraries as well, but if you want toi generate what is in the image, it can be pretty straightforward, since it is just (slightly curved) lines between points. If you do it in phases, not trying to be correct at once, it is easy:
Split x region of your game screen into sections (with some minimal and maximal width) using random (you may be slightly off in last section, but it does not matter as much, I think). Remember the x-es where sections meet (including the ones at game screen border)
Prepare some data structure to include y-s as well, on previously remembered x-s. Start with leftmost.y = 0, slope = Math.random()-0.5;.
Generate each next undefined y beginning with 1: right.y = left.y + slope * (right.x-left.x); as well as update slope after each y: slope += Math.random()-0.5;. Do not bother, for the moment, if it all fits into game screen.
If you want arcs, you can generate "curviness" parameter for each section randomly which represent how much the middle of the line is bumped compared to straight lines.
Fit the ys into the game screen: first find maximal and minimal generated y (mingeny, maxgeny) (you can track this while generating in point 4). Choose where the max and min y in game screen (minscry, maxscry) (say at the top fourth and at the bottom fourth). Then transform generated ys so that it spans between minscry and maxscry: for every point, do apoint.y = minscry + (maxscry-minscry)/(maxgeny-mingeny)*(apoint.y-mingeny).
Now use lines between [x,y] points as a terrain, if you want to use "curviness", than add curvemodifier to y for any particular x in a section between leftx and rightx. The arc need not to be a circle: I would suggest a parabola or cosine which are easy to produce: var middle = (left.x+right.x)/2; var excess = (x-left)/(middle-left); and then either var curvemodifier = curviness * (1-excess*excess); or var curvemodifier = curviness * Math.cos(Math.PI/2*excess).
Wow...At one point I was totally addicted to tank wars.
Since you are on a learning adventure...
You might also learn about the context.globalCompositeOperation.
This canvas operation will let you grab an image of actual grass and composite it into your game.
You can randomize the grass appearance by changing the x/y of your drawImage();
Yes, the actual grass would probably be too distracting to include in your finished game, but learning about compositing would be valuable knowledge to have.
...and +1 for the question: Good for you in challenging yourself !

Categories