I have an array of items that I would like to place on concentric circles. See diagram. I am having trouble with the math.
I can calculate the "steps" variable (each item is 40x40 so the steps are the circumference divided by the width plus margin). And I can calculate the points given the radius and the steps, but I don't know how to calculate the radius as a function of the current item index.
for(var i = 0; i < items.length; i++) {
var radius = functionOf(i)??;
var steps = Math.floor((2*radius*Math.PI)/60);
var x = Math.floor(0 + radius * Math.cos(2 * Math.PI * index / steps));
var y = Math.floor(0 + radius * Math.sin(2 * Math.PI * index / steps));
//draw item at x,y
}
Thoughts on how to calculate radius as a function of i?
You can calculate radius for every item using sum of arithmetic progression (for equidistant circles number of items form that progression), but there is simpler and faster approach - change radius after circle filling (pseudocode)
var radius = 0;
var i = 0;
while (i < items.length) {
var steps = Math.floor((2*radius*Math.PI)/60);
for(var index = 0; index < steps; index++) {
var x = Math.floor(0 + radius * Math.cos(2 * Math.PI * index / steps));
var y = Math.floor(0 + radius * Math.sin(2 * Math.PI * index / steps));
//draw item at x,y
i++;
if (i == items.length)
break;
}
radius = radius + 60; //start next circle
}
About arithmetic progression:
for progression with the first item a0 and difference d the sum of first n members is
S = n * (2 * a0 + d * (n - 1)) / 2
so to find what circle some index S belongs to, we have to solve quadratic inequality (find max integer x to fulfill the condition)
x^2 * d + x * (2 * a0 - d) - 2 * S <= 0
checked solution in Delphi:
function GetCircle(a0, d, i: Integer): Integer;
var
Discr: Double;
begin
Discr := (2 * a0 - d) * (2 * a0 - d) + 8 * i * d;
if Discr < 0 then
Exit(0);
Result := Floor((d - 2 * a0 + Sqrt(Discr)) / (2 * d));
end;
for case a0=1, d=6 (your picture differs a bit - progression is not exact) it gives
i=0: 0
i=1..7: 1
i=8..20: 2
i=21..39: 3
and so on
This means:
0th item is at the circle with radius 0
3rd item at the circle with radius 1 * R
11th item at the circle with radius 2 * R
...
Given your sample image, an easy approach is to add 6 elements for every outer circle, so that we have the sequence: 1, 6, 12, 18...
Then the radius of each circle can be enlarged by a constant amount, bigger then the element size:
// center of the circles
var centerx = 350;
var centery = 350;
// start drawing the central (first) element
if ( items.length > 0 ) {
// draw item at centerx, centery
}
var k = 1;
var i = 1;
while ( i < items.length ) {
// number of elements on this circle
var steps = k * 6;
// angular distance between elements
var angle_range = 2 * Math.PI / steps;
// every circle is bigger then the previuos of the same amount
var radius = k * 60;
var j = 0;
while ( j < steps && i < items.length ) {
var angle = j * angle_range;
var x = Math.round(centerx + radius * Math.cos(angle));
var y = Math.round(centery + radius * Math.sin(angle));
//draw item at x,y
i++;
j++;
}
k++;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>Circles</title>
<link href="stile.css"/>
</head>
<body>
<canvas id="myCanvas" width="700" height="700" style="border:1px solid #000000;">
</canvas>
<script>
// dummy draw function
function draw_item( itm, cntx, x, y) {
cntx.fillStyle = "#0022FF";
cntx.fillRect(x - 20, y - 20, 40, 40);
cntx.font = "14px Arial";
cntx.fillStyle = "#FFFFFF";
cntx.fillText(itm, x - 7, y + 5);
}
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
// dummy array
var items = [];
for ( var i = 0; i < 91; i++ ) {
items.push(i);
}
// center of the circles
var centerx = 350;
var centery = 350;
// start drawing the central (first) element
if ( items.length > 0 ) {
draw_item(items[0], ctx, centerx, centery);
}
var k = 1;
var i = 1;
while ( i < items.length ) {
// number of elements on this circle
var steps = k * 6;
// angular distance between elements
var angle_range = 2 * Math.PI / steps;
// every circle is bigger then the previuos of the same amount
var radius = k * 60;
var j = 0;
while ( j < steps && i < items.length ) {
var angle = j * angle_range;
var x = Math.round(centerx + radius * Math.cos(angle));
var y = Math.round(centery + radius * Math.sin(angle));
//draw item at x,y
draw_item(items[i], ctx, x, y);
i++;
j++;
}
k++;
}
</script>
</body>
</html>
Related
I am trying to make my own 3D renderer in JavaScript using raycasting, but despite checking over the math and the code countless times, it still does not seem to be working. I've tried everything I possibly could to get this thing to work and it won't, so I'm hoping someone else can figure it out.
My code runs an Update method every frame, increasing the yaw (Camera.Rot.Yaw) by 0.1 radians every iteration, but it ends up looking weird and unrealistic, and I can't figure out why. Sorry if it's confusing and long, I can't really think of a way to make a minimal reproducible example of this.
This is the Update method:
Update(Canvas, Ctx, Map, Camera) {
var id = Ctx.getImageData(0, 0, Canvas.width, Canvas.height);
var Pixels = id.data;
//Distance of projection plane from camera
//It should be behind I think
var PlaneDist = 64;
//Divides the second slopes by this so each ray goes a shorter
//distance each iteration, effectively increasing quality
var Quality = 160;
//The midpoint of the projection plane for each coordinate
var MidX =
Camera.Pos.X +
PlaneDist * Math.cos(Camera.Rot.Pitch) * Math.cos(Camera.Rot.Yaw);
var MidY = Camera.Pos.Y + PlaneDist * Math.sin(Camera.Rot.Pitch);
var MidZ =
Camera.Pos.Z +
PlaneDist * Math.cos(Camera.Rot.Pitch) * Math.sin(Camera.Rot.Yaw);
//Slopes to get to other points on the projection plane
var SlopeX =
Math.sin(Camera.Rot.Yaw) +
(Canvas.height / Canvas.width) *
Math.cos(Camera.Rot.Yaw) *
Math.sin(Camera.Rot.Pitch);
var SlopeY = -Math.cos(Camera.Rot.Pitch);
var SlopeZ =
Math.cos(Camera.Rot.Yaw) +
(Canvas.height / Canvas.width) *
Math.sin(Camera.Rot.Yaw) *
Math.sin(Camera.Rot.Pitch);
//Loops for every point on the projection plane
for (let i = 0; i < Canvas.height; i++) {
for (let j = 0; j < Canvas.width; j++) {
let NewX = Camera.Pos.X;
let NewY = Camera.Pos.Y;
let NewZ = Camera.Pos.Z;
//Slopes for the actual ray to follow, just the distance between
//the plane point and the camera divided by quality
let SlopeX2 = (Camera.Pos.X-(MidX - SlopeX * (j - Canvas.width / 2)))/ Quality;
let SlopeY2 = (Camera.Pos.Y-(MidY - SlopeY * (i - Canvas.height / 2))) / Quality;
let SlopeZ2 = (Camera.Pos.Z-(MidZ - SlopeZ * (j - Canvas.width / 2)))/ Quality;
//Ray's current map position, divides the map into a 16x32x16
//list of blocks (map initialization shown elsewhere)
let MapPos =
Map.MData[0][Math.floor(NewX / 16) + 2][Math.floor(NewY / 16)][
Math.floor(NewZ / 16)
];
//Iterates until ray either hits a block with max opacity, or
//hits the boundary of the map
while (
MapPos[3] !== 255 &&
NewX + SlopeX2 < 256 &&
NewY + SlopeY2 < 512 &&
NewZ + SlopeZ2 < 256 &&
NewX + SlopeX2 >= 0 &&
NewY + SlopeY2 >= 0 &&
NewZ + SlopeZ2 >= 0
) {
//Advances ray's current position according to slopes
NewX += SlopeX2;
NewY += SlopeY2;
NewZ += SlopeZ2;
MapPos =
Map.MData[0][Math.floor(NewX / 16) + 2][Math.floor(NewY / 16)][
Math.floor(NewZ / 16)
];
}
//Sets pixel on screen to the color of the block the ray hit
//or just white (opacity 0) if it hit the boundary
Pixels[(i * id.width + j) * 4] = MapPos[0];
Pixels[(i * id.width + j) * 4 + 1] = MapPos[1];
Pixels[(i * id.width + j) * 4 + 2] = MapPos[2];
Pixels[(i * id.width + j) * 4 + 3] = MapPos[3];
}
}
//Displays the final image
Ctx.putImageData(id, 0, 0);
}
The map initialization (CreateChunk) looks like this:
constructor() {
this.MData = [];
}
CreateChunk(X, Y) {
let Chunk = [X, Y];
for (let x = 0; x < 16; x++) {
let Plane = [];
for (let y = 0; y < 32; y++) {
let Row = [];
for (let z = 0; z < 16; z++) {
//Colors are just to help tell which pixels are at what coordinates
if (y < 8) Row.push([x * 15, y * 7, z * 15, 255]);
else Row.push([0, 0, 0, 0]);
}
Plane.push(Row);
}
Chunk.push(Plane);
}
this.MData.push(Chunk);
}
I'm hoping it's just some coding mistake I've made, but despite my countless checks it may be the trigonometry that's wrong.
I want to distribute n points evenly on a circle circumference in quadrants I and IV only.
As parameters, I have the numbers of point n, the center of circle coordiantes cx and cy and the radius r.
I can distribute the points over the whole circumference like using this formula below, but I am looking for the formula to spread them only in quadrants I and IV
var n = 5;
var cx = 1;
var cy = 1;
var r = 2;
//I store each point's coordinates in this array below
var coordinates = [];
for (var i=0; i < n; i++) {
//defining point's angle with the center of the circle in radiant
//this angle distribute the points evenly over all 4 quadrants
var angle = ((360/n) * i) * (Math.PI/180);
//calculating the point's coordinates on the circle circumference
var pointX = cx + r * Math.cos(angle);
var pointY = cx + r * Math.sin(angle);
//storing the point's coordinates
coordinates.push([pointX, pointY]);
}
Here would be the steps I'd take to solve this:
find the angle betw. each point var incrementBy = 180 / n
start angle will be 270º and end angle will be 90º
iterate through via
code
var increment = 180 / n
var startAngle = 270
for (var i = 0; i < n; i++)
{
var angle = startAngle + increment * i
var rads = angle * Math.pi / 180
var tx = cx + r * Math.cos(rads)
var ty = cy + r * Math.sin(rads)
coords.push([tx, ty])
}
note
I didn't bother to convert for traditional quadrants (vs JS's y-axis moving downwards). If that is needed then, after your calculations, just invert the ty value. I also didn't bother to reduce the angle value when it exceeds 360º when you're incrementing back into Quad I.
like this?
var n = 5;
var r = 2;
var cx = 1;
var cy = 1;
var coordinates = [];
for(var i=0; i<n; ++i){
var a = (i+.5) / n * Math.PI;
coordinates.push([
cx + Math.sin(a) * r,
cy - Math.cos(a) * r
]);
}
Here is the way to equally distribute objects
var boxes = []
let count = 10;
for (let i = 0; i < count; i++) {
let size = 0.8;
let radius = 3;
let box = new THREE.Mesh(new THREE.BoxGeometry( size, size, size ),new THREE.MeshPhysicalMaterial(0x333333))
let gap = 0.5;
let angle = i * ((Math.PI * 2) / count);
let x = radius * Math.cos(angle);
let y = 0;
let z = radius * Math.sin(angle);
box.position.set(x,y,z);
boxes.push(box);`enter code here`
scene.add(box)
}
here is how it looks for 10 blocks
here is how it looks for 5 blocks
var n = 5;
var cx = 1;
var cy = 1;
var r = 2;
//I store each point's coordinates in this array below
var coordinates = [];
for (var i=0; i < n; i++) {
//defining the angle of the point with the center of the circle in radiant
var angle = ((360/n) * i) * (Math.PI/180);
//calculating the coordinates of the point on the circle circumference
var pointX = cx + r * Math.cos(angle);
var pointY = cx + r * Math.sin(angle);
// Here, we are going to use a boolean expression to determine if
// [pointX, pointY] is within quadrant 1 or 4.
// We can start with this boolean equation:
// (pointX >= cx && pointY >= cy) || (pointX >= cx && pointY <= cy)
// But this problem can be simplified to only pointX >= cx
if(pointX >= cx){
//storing the point's coordinates
coordinates.push([pointX, pointY]);
}
}
I'm looking for a function to arrange some elements around a circle.
result should be something like :
Here's some code that should help you:
var numElements = 4,
angle = 0
step = (2*Math.PI) / numElements;
for(var i = 0; i < numElements.length; i++) {
var x = container_width/2 + radius * Math.cos(angle);
var y = container_height/2 + radius * Math.sin(angle);
angle += step;
}
It is not complete but should give you a good start.
Update: Here's something that actually works:
var radius = 200; // radius of the circle
var fields = $('.field'),
container = $('#container'),
width = container.width(),
height = container.height(),
angle = 0,
step = (2*Math.PI) / fields.length;
fields.each(function() {
var x = Math.round(width/2 + radius * Math.cos(angle) - $(this).width()/2),
y = Math.round(height/2 + radius * Math.sin(angle) - $(this).height()/2);
$(this).css({
left: x + 'px',
top: y + 'px'
});
angle += step;
});
Demo: http://jsfiddle.net/ThiefMaster/LPh33/
Here's an improved version where you can change the element count.
For an element around a centre at (x, y), distance r, the element's centre should be positioned at:
(x + r cos(2kπ/n), y + r sin(2kπ/n))
where n is the number of elements, and k is the "number" of the element you're currently positioning (between 1 and n inclusive).
I've combined ThiefMaster's fiddle with the jQuery pointAt plugin:
Demo: http://jsfiddle.net/BananaAcid/nytN6/
the code is somewhat like above.
might be interesting to some of you.
Arrange Elements In Circle (Javascript)
function arrangeElementsInCircle (elements, x, y, r) {
for (var i = 0; i < elements.length; i++) {
elements[i].scaleX = 1 / elements.length
elements[i].scaleY = 1 / elements.length
elements[i].x = (x + r * Math.cos((2 * Math.PI) * i/elements.length))
elements[i].y = (y + r * Math.sin((2 * Math.PI) * i/store.length))
}
}
Where x,y is point co-ordinates and elements is array of elements to be placed and r is radius.
Javascript only version of thiefmaster's answer
function distributeFields(deg){
deg = deg || 0;
var radius = 200;
var fields = document.querySelectorAll('.field'), //using queryselector instead of $ to select items
container = document.querySelector('#container'),
width = container.offsetWidth, //offsetWidth gives the width of the container
height = container.offsetHeight,
angle = deg || Math.PI * 3.5,
step = (2 * Math.PI) / fields.length;
console.log(width, height)
//using forEach loop on a NodeList instead of a Jquery .each,
//so we can now use "field" as an iterator instead of $(this)
fields.forEach((field)=>{
var x = Math.round(width / 2 + radius * Math.cos(angle) - field.offsetWidth/2);
var y = Math.round(height / 2 + radius * Math.sin(angle) - field.offsetHeight/2);
console.log(x, y)
field.style.left = x + 'px'; //adding inline style to the document (field)
field.style.top= y + 'px';
angle += step;
})
}
distributeFields();
I want to plot a range of points on the lower left section (6 to 9 o'clock) of the perimeter of a circle. However, the starting point of rendering X,Y coordinates always begins at 3 o'clock.
!https://dl.dropboxusercontent.com/u/55849501/plotting-xy.png
Here is the rendering portion of my code:
var items = 5;
for(var i = 0; i < items; i++) {
var x = 96 + 100 * Math.cos(0.665 * Math.PI * i / items);
var y = 96 + 100 * Math.sin(0.665 * Math.PI * i / items);
$("#center").append("<div class='point' style='left:"+ x +"px;top:"+ y +"px'></div>");
}
And here is a jsfiddle of the code in action: http://jsfiddle.net/jE26S/198/
In summary:
I want the points to render starting at the 6 o'clock position instead of the 3 o'clock position.
What you are really doing here is interpolating between two values of theta. In your case, you want to start at Pi/2 and end at Pi. I took the liberty of re-writing your snippet using this interpolation paradigm. Also, you can adjust how far you want the dots/items away from the circle using outerCircleRadius.
var items = 5;
var startTheta = .5 * Math.PI;
var endTheta = 1 * Math.PI;
var outerCircleRadius = 112;
var cx = 90;
var cy = 90;
for(var i = 0; i < items; i++) {
var theta = startTheta + (endTheta - startTheta) * i / (items - 1)
var x = cx + outerCircleRadius * Math.cos(theta);
var y = cy + outerCircleRadius * Math.sin(theta);
$("#center").append("<div class='point' style='left:"+ x +"px;top:"+ y +"px'></div>");
}
Something like this?
var x = 86 + 100 * Math.cos(0.665 * Math.PI * (items-1+i-0.5) / items);
var y = 96 + 100 * Math.sin(0.665 * Math.PI * (items-1+i-0.5) / items);
Everything in Richard Shurtz's answer, except for the "items - 1" in the first line in the loop.
This worked for any number of items:
var items = 5;
var startTheta = .5 * Math.PI;
var endTheta = 1 * Math.PI;
var outerCircleRadius = 112;
var cx = 90;
var cy = 90;
for(var i = 0; i < items; i++) {
var theta = startTheta + (endTheta - startTheta) * i / items
var x = cx + outerCircleRadius * Math.cos(theta);
var y = cy + outerCircleRadius * Math.sin(theta);
$("#center").append("<div class='point' style='left:"+ x +"px;top:"+ y +"px'></div>");
}
I'm creating a Canvas animation, and have managed to position x number of circles in a circular path. Here's a simplified version of the code I'm using:
var total = circles.length,
i = 0,
radius = 500,
angle = 0,
step = (2*Math.PI) / total;
for( i; i < total; i++ ) {
var circle = circles[i].children[0],
cRadius = circle[i].r,
x = Math.round(winWidth/2 + radius * Math.cos( angle ) - cRadius),
y = Math.round(winHeight/2 + radius * Math.sin( angle ) - cRadius);
circle.x = x;
circle.y = y;
angle += step;
}
Which results in this:
What I am trying to achieve is for all the circles to be next to each other with no gap between them (except the first and last). The circles sizes (radius) are generated randomly and shouldn't adjust to fit the circular path:
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
I expect there to be a gap between the first and last circle.
I'm struggling to get my head around this so any help would be much appreciated :)
Cheers!
Main creation loop :
• take a current radius
• compute the angles it cover ( = atan2(smallRadius/bigRadius) )
• move base angle by this latest angle.
http://jsfiddle.net/dj2v7mbw/9/
function CircledCircle(x, y, radius, margin, subCircleCount, subRadiusMin, subRadiusMax) {
this.x = x;
this.y = y;
this.radius = radius;
this.subCircleCount = subCircleCount;
var circles = this.circles = [];
// build top sub-circles
var halfCount = Math.floor(subCircleCount / 2);
var totalAngle = addCircles(halfCount);
// re-center top circles
var correction = totalAngle / 2 + Math.PI / 2;
for (var i = 0; i < halfCount; i++) this.circles[i].angle -= correction;
// build bottom sub-circles
totalAngle = addCircles(subCircleCount - halfCount);
// re-center bottom circles
var correction = totalAngle / 2 - Math.PI / 2;
for (var i = halfCount; i < subCircleCount; i++) this.circles[i].angle -= correction;
// -- draw this C
this.draw = function (angleShift) {
for (var i = 0; i < this.circles.length; i++) drawDistantCircle(this.circles[i], angleShift);
}
//
function drawDistantCircle(c, angleShift) {
angleShift = angleShift || 0;
var thisX = x + radius * Math.cos(c.angle + angleShift);
var thisY = y + radius * Math.sin(c.angle + angleShift);
ctx.beginPath();
ctx.arc(thisX, thisY, c.r, 0, 2 * Math.PI);
ctx.fillStyle = 'hsl(' + (c.index * 15) + ',75%, 75%)';
ctx.fill();
ctx.stroke();
}
//
function addCircles(cnt) {
var currAngle = 0;
for (var i = 0; i < cnt; i++) {
var thisRadius = getRandomInt(subRadiusMin, subRadiusMax);
var thisAngle = Math.atan2(2 * thisRadius + margin, radius);
var thisCircle = new subCircle(thisRadius, currAngle + thisAngle / 2, i);
currAngle += thisAngle;
circles.push(thisCircle);
}
return currAngle;
}
}
with
function subCircle(radius, angle, index) {
this.r = radius;
this.angle = angle;
this.index = index;
}
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
use with
var myCircles = new CircledCircle(winWidth / 2, winHeight / 2, 350, 2, 24, 5, 50);
myCircles.draw();
animate with :
var angleShift = 0;
function draw() {
requestAnimationFrame(draw);
ctx.clearRect(0, 0, winWidth, winHeight);
myCircles.draw(angleShift);
angleShift += 0.010;
}
draw();
It's something like this, but you're gonna have to figure out the last circle's size:
http://jsfiddle.net/rudiedirkx/ufvf62yf/2/
The main logic:
var firstStep = 0, rad = 0, step = 0;
firstStep = step = stepSize();
for ( var i=0; i<30; i++ ) {
draw.radCircle(rad, step);
rad += step;
step = stepSize();
rad += step;
}
stepSize() creates a random rad between Math.PI/48 and Math.PI/48 + Math.PI/36 (no special reason, but it looked good). You can fix that to be the right sizes.
draw.radCircle(rad, step) creates a circle at position rad of size step (also in rad).
step is added twice per iteration: once to step from current circle's center to its edge and once to find the next circle's center
firstStep is important because you have to know where to stop drawing (because the first circle crosses into negative angle)
I haven't figured out how to make the last circle the perfect size yet =)
There's also a bit of magic in draw.radCircle():
var r = rRad * Math.PI/3 * 200 * .95;
The 200 is obviously the big circle's radius
The 95% is because the circle's edge length is slightly longer than the (straight) radius of every circle
I have no idea why Math.PI/3 is that... I figured it had to be Math.PI/2, which is 1 rad, but that didn't work at all. 1/3 for some reason does..... Explain that!
If you want to animate these circle sizes and keep them aligned, you'll have a hard time. They're all random now. In an animation, only the very first iteration can be random, and the rest is a big mess of cache and math =)