Related
I am looking at this example and one part of this does not make sense.
The code creates an array of values like this:
for (i = 0; i < 84; i++) {
data.push(i * 10 / 84);
}
It then uses this array to get both the x and y values for the graph where d is an element of the array:
sine
.x(function (d, i) { return xScale(d); })
.y(function (d, i) { return yScale(Math.sin(d - time)); });
Is 84 just an arbitrary number for the available width remaining for the graph or is there any particular reason of where this came from?
I think it is a number of points per circle... compromise between accuracy and speed. I usually use 36 for small circles and 90 for big. ... and few thousand for huge ones ... so the idea is to use as low count as possible while the circle still looks like circle (in max zoom) and not like polygon.
You can also compute this algebraically ...
da=2.0*M_PI/n
e=r-(r*cos(0.5*da))
where n is the number of line segments per circumference and e is the max distance from desired circle shape. if you set it to desired error in pixels (and radius r is in pixels) then:
n=M_PI/acos((r-e)/r)
Hopefully I did not make any mistake while deriving the equations directly in SO editor. So if you want really precise circle set e=0.4 [pixels] and you should be fine
[edit1] sin wave
The for loop creates list wit these properties:
d(i) = < 0.0 , 10.0 )
i = { 0,1,2,...83 }
Then the sinwave is rendered:
x(i) = xscale * d(i)
y(i) = yscale * sin(d(i)-time)
Which gives you:
x(i) = < 0.0 , xscale )
y(i) = < -yscale , +yscale )
So the sinwave renders 10/(2*PI)= ~ 1.59 periods. The half overlap is cut off by the view. So in theory you could use 6.28/84 -> 7/84 instead of 10/84 but it is maybe just safety value to handle different aspect ratio seettings of the rendering (I do not code in that platform so this is just speculation on my side) But as I said in the comments the sinwave is scaled so the PI period x size is equal to PI*circle_radius so the 84 most likely comes from the circle (my original answer).
This is possibly just a magic number, that is, completely arbitrary. In fact, as you said, the first thing I thought was that it is related to the width of the graph.
Here is a fiddle: https://jsfiddle.net/1nboube9/1/
You can tweak the number and see what happens. It seems to me that any number above 44 does the trick.
for (i = 0; i < 44; i++) {
data.push(i * 10 / 84);
}
But, of course, the path is not the same if you change the denominator as well:
for (i = 0; i < 44; i++) {
data.push(i * 10 / 44);
}
This creates a very different path. And, so, I tried this:
for (i = 0; i < someNumber; i++) {
data.push(i);
}
And it creates a very unpleasant path. So, I believe that this is what happened: the designer first created data.push(i * 10 / 84); to make the path more circular, and then changed the loop accordingly. Maybe I'm completely wrong, but that's my bet.
I wanna to produce a Pie Chart on a Hexagon. There are probably several solutions for this. In the picture are my Hexagon and two Ideas:
My Hexagon (6 vertices, 4 faces)
How it should look at the end (without the gray lines)
Math: Can I get some informations from the object to dynamically calculate new vertices (from the center to each point) to add colored faces?
Clipping: On a sphere a Pie-Chart is easy, maybe I can clip the THREE Object (WITHOUT SVG.js!) so I just see the Hexagon with the clipped Chart?
Well the whole clipping thing in three.js is already solved here : Object Overflow Clipping Three JS, with a fiddle that shows it works and all.
So I'll go for the "vertices" option, or rather, a function that, given a list of values gives back a list of polygons, one for each value, that are portions of the hexagon, such that
they all have the centre point as a vertex
the angle they have at that point is proportional to the value
they form a partition the hexagon
Let us suppose the hexagon is inscribed in a circle of radius R, and defined by the vertices :
{(R sqrt(3)/2, R/2), (0,R), (-R sqrt(3)/2, R/2), (-R sqrt(3)/2, -R/2), (0,-R), (R sqrt(3)/2, -R/2)}
This comes easily from the values cos(Pi/6), sin(Pi/6) and various symmetries.
Getting the angles at the centre for each polygon is pretty simple, since it is the same as for a circle. Now we need to know the position of the points that are on the hexagon.
Note that if you use the symmetries of the coordinate axes, there are only two cases : [0,Pi/6] and [Pi/6,Pi/2], and you then get your result by mirroring. If you use the rotational symmetry by Pi/3, you only have one case : [-Pi/6,Pi/6], and you get the result by rotation.
Using rotational symmetry
Thus for every point, you can consider it's angle to be between [-Pi/6,Pi/6]. Any point on the hexagon in that part has x=R sqrt(3)/2, which simplifies the problem a lot : we only have to find it's y value.
Now we assumed that we know the polar coordinate angle for our point, since it is the same as for a circle. Let us call it beta, and alpha its value in [-Pi/6,Pi/6] (modulo Pi/3). We don't know at what distance d it is from the centre, and thus we have the following system :
Which is trivially solved since cos is never 0 in the range [-Pi/6,Pi/6].
Thus d=R sqrt(3)/( 2 cos(alpha) ), and y=d sin(alpha)
So now we know
the angle from the centre beta
it's distance d from the centre, thanks to rotational symmetry
So our point is (d cos(beta), d sin(beta))
Code
Yeah, I got curious, so I ended up coding it. Sorry if you wanted to play with it yourself. It's working, and pretty ugly in the end (at least with this dataset), see the jsfiddle : http://jsfiddle.net/vb7on8vo/5/
var R = 100;
var hexagon = [{x:R*Math.sqrt(3)/2, y:R/2}, {x:0, y:R}, {x:-R*Math.sqrt(3)/2, y:R/2}, {x:-R*Math.sqrt(3)/2, y:-R/2}, {x:0, y:-R}, {x:R*Math.sqrt(3)/2, y:-R/2}];
var hex_angles = [Math.PI / 6, Math.PI / 2, 5*Math.PI / 6, 7*Math.PI / 6, 3*Math.PI / 2, 11*Math.PI / 6];
function regions(values)
{
var i, total = 0, regions = [];
for(i=0; i<values.length; i++)
total += values[i];
// first (0 rad) and last (2Pi rad) points are always at x=R Math.sqrt(3)/2, y=0
var prev_point = {x:hexagon[0].x, y:0}, last_angle = 0;
for(i=0; i<values.length; i++)
{
var j, theta, p = [{x:0,y:0}, prev_point], beta = last_angle + values[i] * 2 * Math.PI / total;
for( j=0; j<hexagon.length; j++)
{
theta = hex_angles[j];
if( theta <= last_angle )
continue;
else if( theta >= beta )
break;
else
p.push( hexagon[j] );
}
var alpha = beta - (Math.PI * (j % 6) / 3); // segment 6 is segment 0
var d = hexagon[0].x / Math.cos(alpha);
var point = {x:d*Math.cos(beta), y:d*Math.sin(beta)};
p.push( point );
regions.push(p.slice(0));
last_angle = beta;
prev_point = {x:point.x, y:point.y};
}
return regions;
}
I am using a Flot graph and I am setting up various interactive elements.
One of these elements is one in which the user inputs any x value (It really could be either an x or y value depending on the situation, but for simplicity, let's assume it is always an x-axis value) and I need to output the corresponding y coordinate on the line I have graphed. I feel like this should be kind of simple, so I apologize if the answer is an obvious one. Note that the input value is probably not going to be a "point" in the array which flot is using to create the line (although it could).
You could also imagine a vertical line at x = [user input, not necessarily a whole number] intersecting another line series at some point. I would need to find the point of intersection. I tried uploading a photo, but I don't have enough reputation points.
How's your algebra?
There's actually an example of this buried in flot's examples here. If you view the source to that page you'll see this (I've added explanation comments):
// Find the nearest points, x-wise
// loop the series data until you find the point
// immediately after your x value of interest (pos.x in this code)
for (j = 0; j < series.data.length; ++j) {
if (series.data[j][0] > pos.x) {
break;
}
}
// Now Interpolate
// Here's the algebra fun!
var y,
p1 = series.data[j - 1], // point before your x
p2 = series.data[j]; // point after your x
if (p1 == null) {
y = p2[1]; // if no point before just get y-value
} else if (p2 == null) {
y = p1[1]; // if no point after just get y-value
} else {
y = p1[1] + (p2[1] - p1[1]) * (pos.x - p1[0]) / (p2[0] - p1[0]);
// here's the algebra bit, see below
}
In that final else the equation used is this interpolation between two points. Ain't math grand?
I have a circle with a rotation. See images below for example. The circle is divided into segments of varying degrees, for this example I've divided the circle into three equal 120 degree segments.
Given a point of impact (a point on the exterior radius of the circle) I calculate the degree between the center of the circle and the point of impact. I then need to determine which segment was impacted.
My current solution went something like this:
var circleRotation = 270;
var segments = [120, 120, 120];
function segmentAtAngle(angle) {
var sumTo = circleRotation;
for (var i = 0, l = segments.length; l > i; i++) {
if (sumTo <= angle && sumTo + segments[i] >= angle) {
// return the segment
return i;
}
sumTo += segments[i];
}
}
My solution does not work in all cases, given a large offset of say 270 and when requesting the segment at impact degree 45 I currently faultily provide nothing.
Note: Provided angle to segmentAtAngle and circleRotation will also never be negative or above 360. I standardize the degrees by { degrees = degrees % 360; if (degrees < 0) degrees += 360; return degrees; }
What would be the proper way to calculate the hit segment of a circle given an offset rotation?
A simple ad-hoc solution would be duplicating your lists of segments. Then you have the whole range from 0° to 2·360°=720° covered. If angle and circleRotation is between 0° and 360°, as you say they are, then their sum will be between 0° and 720°, and having twice the list of segments will yield a match in all cases. If the resulting index is greater or equal to the length of the original unduplicated list, you can subtract that length to obtain an index from that original list.
First, the conditions of your for loop looks kind of weird. l will always be larger than zero, so the loop will never execute at all. Secondly, you should probably standardize sumTo each time you add to it. Third, you return angle within the loop, which never changes. Do you want to return the index of the impacted segment?
var circleRotation = 270;
var segments = [120, 120, 120];
function standardize(degrees){
degrees = degrees % 360;
if (degrees < 0) degrees += 360;
return degrees;
}
function segmentAtAngle(angle) {
var sumTo = circleRotation;
for (var i = 0; i<segments.length; i++) {
if (sumTo <= angle && sumTo + segments[i] >= angle) {
return i;
}
sumTo = standardize(sumTo + segments[i]);
}
}
The function atan2(DY, DX) will give you the angle from the center to any point. This angle will be in range -pi to +pi. For the sake of the discussion, let us convert this to the -180..+180° range.
Now consider the delimiting angles of your segments, as if obtained by the same function: they will correspond to the ranges [-120..0], [0..120] and [120, -120]. All is fine, except that the third interval straddles the discontinuity, and it should be split into [120..180] and [-180..-120].
In the end, you should consider this list of bounds, with corresponding sectors:
-180 -120 0 120 180
Yellow | Red | Green | Yellow
With N colors, you will need to consider N+1 intervals and compare to N bounds (no need to check against the extreme values, they are implicitly fulfilled). You will do this by linear or dichotomic search (or simple rescaling in case of equidistant bounds).
I'm developing a simple 2D board game using hexagonal tile maps, I've read several articles (including the gamedev one's, which are linked every time there's a question on hexagonal tiles) on how to draw hexes on the screen and how to manage the movement (though much of it I had already done before). My main problem is finding the adjacent tiles based on a given radius.
This is how my map system works:
(0,0) (0,1) (0,2) (0,3) (0,4)
(1,0) (1,1) (1,2) (1,3) (1,4)
(2,0) (2,1) (2,2) (2,3) (2,4)
(3,0) (3,1) (3,2) (3,3) (3,4)
etc...
What I'm struggling with is the fact that I cant just 'select' the adjacent tiles by using for(x-range;x+range;x++); for(y-range;y+range;y++); because it selects unwanted tiles (in the example I gave, selecting the (1,1) tile and giving a range of 1 would also give me the (3,0) tile (the ones I actually need being (0,1)(0,2)(1,0)(1,2)(2,1)(2,2) ), which is kinda adjacent to the tile (because of the way the array is structured) but it's not really what I want to select. I could just brute force it, but that wouldn't be beautiful and would probably not cover every aspect of 'selecting radius thing'.
Can someone point me in the right direction here?
What is a hexagonal grid?
What you can see above are the two grids. It's all in the way you number your tiles and the way you understand what a hexagonal grid is. The way I see it, a hexagonal grid is nothing more than a deformed orthogonal one.
The two hex tiles I've circled in purple are theoretically still adjacent to 0,0. However, due to the deformation we've gone through to obtain the hex-tile grid from the orthogonal one, the two are no longer visually adjacent.
Deformation
What we need to understand is the deformation happened in a certain direction, along a [(-1,1) (1,-1)] imaginary line in my example. To be more precise, it is as if the grid has been elongated along that line, and squashed along a line perpendicular to that. So naturally, the two tiles on that line got spread out and are no longer visually adjacent. Conversely, the tiles (1, 1) and (-1, -1) which were diagonal to (0, 0) are now unusually close to (0, 0), so close in fact that they are now visually adjacent to (0, 0). Mathematically however, they are still diagonals and it helps to treat them that way in your code.
Selection
The image I show illustrates a radius of 1. For a radius of two, you'll notice (2, -2) and (-2, 2) are the tiles that should not be included in the selection. And so on. So, for any selection of radius r, the points (r, -r) and (-r, r) should not be selected. Other than that, your selection algorithm should be the same as a square-tiled grid.
Just make sure you have your axis set up properly on the hexagonal grid, and that you are numbering your tiles accordingly.
Implementation
Let's expand on this for a bit. We now know that movement along any direction in the grid costs us 1. And movement along the stretched direction costs us 2. See (0, 0) to (-1, 1) for example.
Knowing this, we can compute the shortest distance between any two tiles on such a grid, by decomposing the distance into two components: a diagonal movement and a straight movement along one of the axis.
For example, for the distance between (1, 1) and (-2, 5) on a normal grid we have:
Normal distance = (1, 1) - (-2, 5) = (3, -4)
That would be the distance vector between the two tiles were they on a square grid. However we need to compensate for the grid deformation so we decompose like this:
(3, -4) = (3, -3) + (0, -1)
As you can see, we've decomposed the vector into one diagonal one (3, -3) and one straight along an axis (0, -1).
We now check to see if the diagonal one is along the deformation axis which is any point (n, -n) where n is an integer that can be either positive or negative.
(3, -3) does indeed satisfy that condition, so this diagonal vector is along the deformation. This means that the length (or cost) of this vector instead of being 3, it will be double, that is 6.
So to recap. The distance between (1, 1) and (-2, 5) is the length of (3, -3) plus the length of (0, -1). That is distance = 3 * 2 + 1 = 7.
Implementation in C++
Below is the implementation in C++ of the algorithm I have explained above:
int ComputeDistanceHexGrid(const Point & A, const Point & B)
{
// compute distance as we would on a normal grid
Point distance;
distance.x = A.x - B.x;
distance.y = A.y - B.y;
// compensate for grid deformation
// grid is stretched along (-n, n) line so points along that line have
// a distance of 2 between them instead of 1
// to calculate the shortest path, we decompose it into one diagonal movement(shortcut)
// and one straight movement along an axis
Point diagonalMovement;
int lesserCoord = abs(distance.x) < abs(distance.y) ? abs(distance.x) : abs(distance.y);
diagonalMovement.x = (distance.x < 0) ? -lesserCoord : lesserCoord; // keep the sign
diagonalMovement.y = (distance.y < 0) ? -lesserCoord : lesserCoord; // keep the sign
Point straightMovement;
// one of x or y should always be 0 because we are calculating a straight
// line along one of the axis
straightMovement.x = distance.x - diagonalMovement.x;
straightMovement.y = distance.y - diagonalMovement.y;
// calculate distance
size_t straightDistance = abs(straightMovement.x) + abs(straightMovement.y);
size_t diagonalDistance = abs(diagonalMovement.x);
// if we are traveling diagonally along the stretch deformation we double
// the diagonal distance
if ( (diagonalMovement.x < 0 && diagonalMovement.y > 0) ||
(diagonalMovement.x > 0 && diagonalMovement.y < 0) )
{
diagonalDistance *= 2;
}
return straightDistance + diagonalDistance;
}
Now, given the above implemented ComputeDistanceHexGrid function, you can now have a naive, unoptimized implementation of a selection algorithm that will ignore any tiles further than the specified selection range:
int _tmain(int argc, _TCHAR* argv[])
{
// your radius selection now becomes your usual orthogonal algorithm
// except you eliminate hex tiles too far away from your selection center
// for(x-range;x+range;x++); for(y-range;y+range;y++);
Point selectionCenter = {1, 1};
int range = 1;
for ( int x = selectionCenter.x - range;
x <= selectionCenter.x + range;
++x )
{
for ( int y = selectionCenter.y - range;
y <= selectionCenter.y + range;
++y )
{
Point p = {x, y};
if ( ComputeDistanceHexGrid(selectionCenter, p) <= range )
cout << "(" << x << ", " << y << ")" << endl;
else
{
// do nothing, skip this tile since it is out of selection range
}
}
}
return 0;
}
For a selection point (1, 1) and a range of 1, the above code will display the expected result:
(0, 0)
(0, 1)
(1, 0)
(1, 1)
(1, 2)
(2, 1)
(2, 2)
Possible optimization
For optimizing this, you can include the logic of knowing how far a tile is from the selection point (logic found in ComputeDistanceHexGrid) directly into your selection loop, so you can iterate the grid in a way that avoids out of range tiles altogether.
Simplest method i can think of...
minX = x-range; maxX = x+range
select (minX,y) to (maxX, y), excluding (x,y) if that's what you want to do
for each i from 1 to range:
if y+i is odd then maxX -= 1, otherwise minX += 1
select (minX, y+i) to (maxX, y+i)
select (minX, y-i) to (maxX, y-i)
It may be a little off; i just worked it through in my head. But at the very least, it's an idea of what you need to do.
In C'ish:
void select(int x, int y) {
/* todo: implement this */
/* should ignore coordinates that are out of bounds */
}
void selectRange(int x, int y, int range) {
int minX = x - range, maxX = x + range;
for (int i = minX; i <= maxX; ++i) {
if (i != x) select(i, y);
}
for (int yOff = 1; yOff <= range; ++yOff) {
if ((y+yOff) % 2 == 1) --maxX; else ++minX;
for (int i=minX; i<=maxX; ++i) {
select(i, y+yOff);
select(i, y-yOff);
}
}
}