Can I make a half-bezier from full bezier? - javascript

Take a typical cubic bezier curve drawn in JavaScript (this example I googled...)
http://jsfiddle.net/atsanche/K38kM/
Specifically, these two lines:
context.moveTo(188, 130);
context.bezierCurveTo(170, 10, 350, 10, 388, 170);
We have a cubic bezier which starts at 188, 130, ends at 388, 170, and has controls points a:170, 10 and b:350, 10
My question is would it be possible to mathematically adjust the end point and control points to make another curve which is only a segment of the original curve?
The ideal result would be able to able to take a percentage slice of the bezier from the beginning, where 0.5 would draw only half of the bezier, 0.75 would draw most of the bezier (and so on)
I've already gotten working a few implementations of De Castelau which allow me to trace the contour of the bezier between [0...1], but this doesn't provide a way to mathematically recalculate the end and control points of the bezier to make a sub-bezier...
Thanks in advance

De Casteljau is indeed the algorithm to go. For a cubic Bezier curve defined by 4 control points P0, P1, P2 and P3, the control points of the sub-Bezier curve (0, u) are P0, Q0, R0 and S0 and the control points of the sub-Bezier curve (u, 1) are S0, R1, Q2 and P3, where
Q0 = (1-u)*P0 + u*P1
Q1 = (1-u)*P1 + u*P2
Q2 = (1-u)*P2 + u*P3
R0 = (1-u)*Q0 + u*Q1
R1 = (1-u)*Q1 + u*Q2
S0 = (1-u)*R0 + u*R1
Please note that if you want to "extract" a segment (u1, u2) from the original Bezier curve, you will have to apply De Casteljau twice. The first time will split the input Bezier curve C(t) into C1(t) and C2(t) at parameter u1 and the 2nd time you will have to split the curve C2(t) at an adjusted parameter u2* = (u2-u1)/(1-u1).

This is how to do it. You can get the left half or right half with this functin. This function is take thanks to mark from here: https://stackoverflow.com/a/23452618/1828637
I have it modified so it can be fit to a unit cell so we can use it for cubic-bezier in css transitions.
function splitCubicBezier(options) {
var z = options.z,
cz = z-1,
z2 = z*z,
cz2 = cz*cz,
z3 = z2*z,
cz3 = cz2*cz,
x = options.x,
y = options.y;
var left = [
x[0],
y[0],
z*x[1] - cz*x[0],
z*y[1] - cz*y[0],
z2*x[2] - 2*z*cz*x[1] + cz2*x[0],
z2*y[2] - 2*z*cz*y[1] + cz2*y[0],
z3*x[3] - 3*z2*cz*x[2] + 3*z*cz2*x[1] - cz3*x[0],
z3*y[3] - 3*z2*cz*y[2] + 3*z*cz2*y[1] - cz3*y[0]];
var right = [
z3*x[3] - 3*z2*cz*x[2] + 3*z*cz2*x[1] - cz3*x[0],
z3*y[3] - 3*z2*cz*y[2] + 3*z*cz2*y[1] - cz3*y[0],
z2*x[3] - 2*z*cz*x[2] + cz2*x[1],
z2*y[3] - 2*z*cz*y[2] + cz2*y[1],
z*x[3] - cz*x[2],
z*y[3] - cz*y[2],
x[3],
y[3]];
if (options.fitUnitSquare) {
return {
left: left.map(function(el, i) {
if (i % 2 == 0) {
//return el * (1 / left[6])
var Xmin = left[0];
var Xmax = left[6]; //should be 1
var Sx = 1 / (Xmax - Xmin);
return (el - Xmin) * Sx;
} else {
//return el * (1 / left[7])
var Ymin = left[1];
var Ymax = left[7]; //should be 1
var Sy = 1 / (Ymax - Ymin);
return (el - Ymin) * Sy;
}
}),
right: right.map(function(el, i) {
if (i % 2 == 0) {
//xval
var Xmin = right[0]; //should be 0
var Xmax = right[6];
var Sx = 1 / (Xmax - Xmin);
return (el - Xmin) * Sx;
} else {
//yval
var Ymin = right[1]; //should be 0
var Ymax = right[7];
var Sy = 1 / (Ymax - Ymin);
return (el - Ymin) * Sy;
}
})
}
} else {
return { left: left, right: right};
}
}
Thats the function and now to use it with your parameters.
var myBezier = {
xs: [188, 170, 350, 388],
ys: [130, 10, 10, 170]
};
var splitRes = splitCubicBezier({
z: .5, //percent
x: myBezier.xs,
y: myBezier.ys,
fitUnitSquare: false
});
This gives you
({
left: [188, 130, 179, 70, 219.5, 40, 267, 45],
right: [267, 45, 314.5, 50, 369, 90, 388, 170]
})
fiddle proving its half, i overlaid it over your original:
http://jsfiddle.net/K38kM/8/

Yes it is! Have a look at the bezier section here
http://en.m.wikipedia.org/wiki/De_Casteljau's_algorithm
It is not that difficult all in all.

Related

Function that generates a path along other path, from a starting to an end point

const coords = [
{
name: "Rijnstraat vervolg",
points: [
[695, 500],
[680, 480],
[580, 475],
[520, 460],
],
width: 10,
types: [types.car, types.truck, types.pedestrian, types.bike],
oneway: true,
},
...
]
I have an array that looks like the above and I want to make a function that generates a path (along the other paths, which are the black lines in the image) from a black or gray circle to another black or gray circle. So I want the function to take in a start and end point (black or gray circle) and return an array of points that follow the already existings paths. (Which are sort of like roads)
And the function can be described as someone who is trying to get to somewhere.
I already tried a recursive function that looks like this:
function calculatePathToShop(startPoint, shopPoint) {
const targetShopPoint = findClosestPointOnPath(shopPoint);
const targetPathIndex = findPathByPoint(targetShopPoint);
const connectedPaths = calculateConnectedPaths(targetPathIndex);
let startPathIndex = -1;
connectedPaths.forEach(path => {
const pathPoints = coords[path].points;
pathPoints.forEach(pathPoint => {
if (comparePoints(startPoint.point, pathPoint)) startPathIndex = path;
});
});
if (startPathIndex == -1) return false;
let startPathPoints = coords[startPathIndex].points;
let targetPathPoints = coords[targetPathIndex].points;
if (!comparePoints(startPoint.point, startPathPoints[0])) startPathPoints.reverse();
ctx.strokeStyle = "rgba(255, 0, 0, .05)";
}
This one generated a path (along the existing ones) to a shop point, which is almost the same as a gray point. But this worked for some starting points, but the rest would just straight up fail
So does anyone know an algorithm, or has a function/solution that I can use to generate the path that someone can walk along the road (the black lines in the image)
Full coords array, and part of my already existing code is found here: https://raw.githubusercontent.com/CodeFoxDev/people-simulation/main/func/paths.js
(The rest of the code is in the github repo itself)
Fixed step interpolation
To interpolate a line segment you divide the vector from the start pointing to the end by the number of steps.
EG
steps = 100;
start = {x: 50, y: 100}
end = {x: 150, y: 300}
step = {x: (end.x - start.x) / steps, y: (end.y - start.y) / steps};
Then loop that number of steps adding the vector to a position initialized to the start point.
points = []; // array of interpolated points
point = {...start} // set start position.
while (steps--) {
points.push({...point});
point.x += vec.x;
point.y += vec.y;
}
points.push({...end}); // last point at end
This will create different spacing for different line lengths.
Fixed distance interpolation
To get a constant spacing between points you will need to use the lines' length to get the number of steps.
pixelsPerStep = 2; // distance between points.
start = {x: 50, y: 100}
end = {x: 150, y: 300}
step = {x: end.x - start.x, y: end.y - start.y};
lineSteps = Math.hypot(step.x, step.y) / pixelsPerStep;
points = []; // array of interpolated points
for (i = 0; i < lineSteps ; i += 1) {
u = i / lineSteps;
points.push({x: start.x + step.x * u, y: start.y + step.y * u});
}
// check to add end point
Note that the last point may or may not be at the correct distance. Due to rounding errors in floating point numbers you will need to check if the last point is close to the correct spacing and whether or not to include it.
eg from code above
// add last point if within (0.01 * pixelsPerStep) pixels of correct spacing
if (Math.abs(lineSteps - i) < 0.01) {
points.push({...end});
}
Note Use the overflow lineSteps - i when interpolating many line segments, to carry the correct start offset to each subsequent line segment.
Example
The code below is an example of a constant spaced set of points interpolated from another set of points.
The example draws the new points in black dots. The original points are rendered in red.
Note that the distance between new points is constant and thus may not fall on the original (red) points.
Note that there is a check at the end to test if a last point should be added.
const ctx = canvas.getContext("2d");
const P2 = (x, y) => ({x, y});
const points = [
P2(100,90),
P2(300,210),
P2(350,110),
P2(50,10),
P2(6,219),
];
const interpolatedPoints = interpolatePath(points, 35);
drawPoints(interpolatedPoints, 2);
ctx.fillStyle = "RED";
drawPoints(points);
function drawPoints(points, size = 1) {
ctx.beginPath();
for (const p of points) {
ctx.rect(p.x - size, p.y - size, size * 2 + 1, size * 2 + 1);
}
ctx.fill();
}
function interpolatePath(path, pixelStep) {
const res = [];
var p2, i = 1, overflow = 0;
while (i < path.length) {
const p1 = path[i - 1];
p2 = path[i];
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
const len = Math.hypot(dx, dy) / pixelStep;
let j = overflow;
while (j < len) {
const u = j / len;
res.push(P2(p1.x + dx * u, p1.y + dy * u));
j++;
}
overflow = j - len;
i++;
}
// add last point if close to correct distance
if (Math.abs(overflow) < 0.01) {
res.push(P2(p2.x, p2.y));
}
return res;
}
<canvas id="canvas" width="400" height="400"></canvas>

Swift: How to convert a particle network JS animation to iOS

I found this javascript animation:
JS animation
I really want to know how I can convert animations like this to use it in my Swift iOS app.
Has anybody got experience in likely things? I found things like spritekit, but I really can't find the right info/tutorials about this.
Maybe someone does know what I should do/look up?
Hope anybody can help me, thanks in advance!
Use a customView to upgrade their js codes to swift. Hope it is the answer. Beware of the copyright when you use their codes in this way. It's just for a demo.
In details, circle class is needed here to represent their points. In storyboard, assign a UIView to MyView and you can get the result.
class Circle : NSObject {
var position: CGPoint!
var speed : CGPoint!
init(position: CGPoint, speed: CGPoint) {
super.init()
self.position = position
self.speed = speed
}
}
class MyView : UIView{
var balls: [Circle] = []
override func didMoveToSuperview() {
super.didMoveToSuperview()
clearsContextBeforeDrawing = true
contentMode = .redraw
clipsToBounds = false
func randomValue () -> CGFloat {
let upperBound : UInt32 = 1000;
return (CGFloat(arc4random_uniform(upperBound))) / CGFloat(upperBound);
}
let array = (0..<Int(bounds.width*bounds.height / (65*65))).map { _ in
Circle.init(position: CGPoint.init(x: bounds.width * randomValue() , y: bounds.height * randomValue()), speed: CGPoint.init(x: randomValue() * 2 - 1 , y:randomValue() * 2 - 1) ) }
balls.append(contentsOf: array)
let displayLink = CADisplayLink.init(target: self, selector: #selector(update(_:)))
displayLink.add(to: RunLoop.main, forMode: RunLoop.Mode.default)
}
#objc func update(_ sender : CADisplayLink){
self.setNeedsDisplay()
}
override func draw(_ rect: CGRect) {
super.draw(rect)
let ctx = UIGraphicsGetCurrentContext()!
_ = (0..<(balls.count)).map{
let circle = balls[$0]
if (circle.position.x > rect.size.width + 50 || circle.position.x < -50) {
circle.speed.x = -circle.speed.x;
}
if (circle.position.y > rect.size.height + 50 || circle.position.y < -50) {
circle.speed.y = -circle.speed.y;
}
circle.position.x += circle.speed.x;
circle.position.y += circle.speed.y;
}
let color = UIColor.init(red: 0, green: 0x1c / 255 , blue: 0x33 / 255, alpha: 1.0).cgColor
ctx.setFillColor(color )
ctx.fill(rect)
_ = (0..<(balls.count)).map{
let ball = balls[$0];
ctx.beginPath()
let color = UIColor.init(red: 0x44 / 255 , green: 0x8f / 255 , blue: 0xda / 255, alpha: 0.4).cgColor
ctx.setFillColor(color)
ctx.addArc( center: ball.position , radius: 3.0, startAngle: 0, endAngle: .pi * 2, clockwise: false)
ctx.fillPath()
ctx.beginPath();
var index2 = balls.count - 1
while (index2 > $0 ){
let ball2 = balls[index2];
let dist = hypot(ball.position.x - ball2.position.x , ball.position.y - ball2.position.y );
if (dist < 100) {
ctx.setStrokeColor(UIColor.init(red: 0x44 / 255, green: 0x8f / 255, blue: 0xda / 255, alpha: 1.0).cgColor )
ctx.setAlpha( 1 - (dist > 100 ? 0.8 : dist / 150) )
ctx.setLineWidth(2);
ctx.move(to: CGPoint.init(x: 0.5 + ball.position.x, y: 0.5 + ball.position.y))
ctx.addLine(to: CGPoint.init(x: 0.5 + ball2.position.x, y: 0.5 + ball2.position.y))
}
index2 -= 1;
}
ctx.strokePath()
}
}
}
Using SpriteKit, To make the animation in that link you provided, I would create a loop that generates N number of dots (SKSpriteNodes) randomly placed on the scene. Then recursively move each dot to a new random location. Then, in the update() function of the scene, loop over all the dots and run some calculations to determine if dot X is within so many points of any other dot on the scene and create a CGPath between those two. And remove that CGPath if is not.

Returning precise vector components in js canvas

I have been wrestling with rendering an animation that fires a projectile accurately from an "enemy" node to a "player" node in a 2D 11:11 grid (0:0 = top-left) in JS/Canvas. After a lot of reading up I've managed to get the shots close, but not quite bang on. I think my velocity function is a little out but I really don't know why. This is the trigonometric function:
this.getVelocityComponents = function(speed){
// loc (location of enemy actor) = array(2) [X_coord, Y_coord]
// des (destination (ie. player in this instance)) = array(2) [X_coord, Y_coord]
var i, sum, hyp, output = [], dis = [];
var higher = false;
for (i in loc) {
sum = 0;
if (loc[i] > des[i])
sum = loc[i] - des[i];
if (loc[i] < des[i])
sum = des[i] - loc[i];
dis.push(sum);
}
hyp = Math.sqrt(Math.pow(dis[X], 2) + Math.pow(dis[Y], 2));
if (dis[X] > dis[Y]) {
output[X] = (speed * Math.cos(dis[X]/hyp))
output[Y] = (speed * Math.sin(dis[Y]/hyp))
} else if (dis[X] < dis[Y]) {
output[X] = (speed * Math.cos(dis[Y]/hyp))
output[Y] = (speed * Math.sin(dis[X]/hyp))
}
return output;
}
and this is the instruction that tells the X and the Y of the projectile frame to advance:
var distance = [];
for (i in loc) {
var sum = 0;
if (loc[i] > des[i])
sum = loc[i] - des[i];
if (loc[i] < des[i])
sum = des[i] - loc[i];
distance.push(sum);
}
if (distance[X] > distance[Y]) {
frm[X] += (loc[X] < des[X]) ? v[X] : -v[X];
frm[Y] += (loc[Y] < des[Y]) ? v[Y] : -v[Y];
} else {
frm[Y] += (loc[Y] < des[Y]) ? v[X] : -v[X];
frm[X] += (loc[X] < des[X]) ? v[Y] : -v[Y];
}
Below is a screenshot. Blue is player, pink enemy and the yellow circles are projectiles
as you can see, it's almost on the mark.
Have I done something wrong? what do I need to do?
To calculate the direction from enemy to player you can simplify the calculations a little.
Find direction angle
var diffX = Player.x - Enemy.x, // difference in position
diffY = Player.y - Enemy.y,
angle = Math.atan2(diffY, diffX); // atan2 will give the angle in radians
Notice also difference for Y comes first for atan2 as canvas is oriented 0° pointing right.
Velocity vector
Then calculate the velocity vector using angle and speed:
// calculate velocity vector
var speed = 8,
vx = Math.cos(angle) * speed, // angle x speed
vy = Math.sin(angle) * speed;
You might want to consider using time as a factor if that is important. You can see my answer from a while back here for an example on this.
Demo
Using these calculations you will be able to always "hit" the player with the projectile (reload demo to change enemy position to random y):
var ctx = document.querySelector("canvas").getContext("2d"),
Player = {
x: 470,
y: 75
},
Enemy = {
x: 100,
y: Math.random() * 150 // reload demo to change y-position
};
// calculate angle
var diffX = Player.x - Enemy.x,
diffY = Player.y - Enemy.y,
angle = Math.atan2(diffY, diffX);
// calculate velocity vector
var speed = 8,
vx = Math.cos(angle) * speed, // angle x speed
vy = Math.sin(angle) * speed,
x = Enemy.x, // projectil start
y = Enemy.y + 50;
// render
(function loop() {
ctx.clearRect(0, 0, 500, 300);
ctx.fillRect(Player.x, Player.y, 30, 100);
ctx.fillRect(Enemy.x, Enemy.y, 30, 100);
ctx.fillRect(x - 3, y -3, 6, 6);
x += vx;
y += vy;
if (x < 500) requestAnimationFrame(loop);
})();
<canvas width=500 height=300></canvas>
The solution is much simpler than that.
What should you do ?
1) compute the vector that leads from you enemy to the player. That will be the shooting direction.
2) normalize the vector : meaning you build a vector that has a length of 1, with the same direction.
3) multiply that vector by your speed : now you have a correct speed vector, with the right norm, aimed at the player.
Below some code to help you understand :
function spawnBullet(enemy, player) {
var shootVector = [];
shootVector[0] = player[0] - enemy[0];
shootVector[1] = player[1] - enemy[1];
var shootVectorLength = Math.sqrt(Math.pow(shootVector[0], 2) + Math.pow(shootVector[1],2));
shootVector[0]/=shootVectorLength;
shootVector[1]/=shootVectorLength;
shootVector[0]*=bulletSpeed;
shootVector[1]*=bulletSpeed;
// ... here return an object that has the enemy's coordinate
// and shootVector as speed
}
Then, since you don't use time in your computations (!! wrooong !! ;-) ) you will make the bullet move with the straightforward :
bullet[0] += bullet.speed[0];
bullet[1] += bullet.speed[1];
Now the issue with fixed-step is that your game will run, say, twice slower on a 30fps device than on a 60fps device. The solution is to compute how much time elapsed since the last refresh, let's call this time 'dt'. Using that time will lead you to an update like :
bullet[0] += dt * bullet.speed[0];
bullet[1] += dt * bullet.speed[1];
and now you'll be framerate-agnostic, your game will feel the same on any device.

Extrapolate split cubic-bezier to 1,1

I need help with the solution provided here.
Create easy function 40% off set
I need to modify it so that the returned left and rights are extrapolated to 1,1 after splitting. This is because if I don't extrapolate, I can't use the returned split cubic-bezier as a css transition.
So this is the test I did. Please help because real does not match mike way :( I think the issue is I need to extrapolate the result to 1,1. I can't simply double the values though I'm pretty sure.
REAL
ease-in-out is cubic-bezier(.42,0,.58,1) and graphically is http://cubic-bezier.com/#.42,0,.58,1
first half is ease-in which is cubic-bezier(.42,0,1,1) and graphically is http://cubic-bezier.com/#.42,0,1,1
seoncd half is ease-out which is cubic-bezier(0,0,.58,1) and grpahically is http://cubic-bezier.com/#0,0,.58,1
The function posted above returns the following
ease-in-out is same as this is starting point
first half, left, is given to be cubic-bezier(0.21, 0, 0.355, 0.25) and graphically is http://cubic-bezier.com/#.21,0,.35,.25
code returned: left:[0, 0, 0.21, 0, 0.355, 0.25, 0.5, 0.5]
second half, right, is given to be cubic-bezier(0.645, 0.75, 0.79, 1) and graphically is http://cubic-bezier.com/#.64,.75,.79,1
code returned right:[0.5, 0.5, 0.645, 0.75, 0.79, 1, 1, 1]
Code used for getting it the Mike way is this:
var result = split({
z: .5,
x: [0, 0.42, 0.58, 1],
y: [0, 0, 1, 1]
});
alert(result.toSource());
first half is ease-in which is cubic-bezier(.42,0,1,1) and graphically is http://cubic-bezier.com/#.42,0,1,1
Please verify this assumption. (curves original end points are 0,0, and 1,1 in a css timing function)
The first half of the bezier curve [0,0, .42,0, .58,1, 1,1] should not be [0,0 .42,0, 1,1, 1,1]
The end points are correct (after scaling to 1,1), but you have lost continuity there.
The values returned by Mike's algorithm is correct.
Try this visualisation for an explanation on why your assumption might be wrong.
The algorithm you are using to split is a well known algorithm called de Casteljau algorithm. This method can be geometrically expressed in a very simple manner. Check out the animated visualisations on how this splitting at any arbitrary point is possible https://en.wikipedia.org/wiki/B%C3%A9zier_curve.
However, You may soon hit an issue trying to correctly scale a split portion of a bezier curve to fit in a unit square, with endpoints fixed at 0,0 and 1,1. This probably you can try out quite easily on paper. The easiest way probably is to just linearly scale the control points of the bezier, you will get a squashed curve in most cases though.
I created a modified version of Mike's split function so it fits it to a unit square :) It uses hkrish's pointers to do coordinate normalization.
Just set parameter fitUnitCell to true. :)
function splitCubicBezier(options) {
var z = options.z,
cz = z-1,
z2 = z*z,
cz2 = cz*cz,
z3 = z2*z,
cz3 = cz2*cz,
x = options.x,
y = options.y;
var left = [
x[0],
y[0],
z*x[1] - cz*x[0],
z*y[1] - cz*y[0],
z2*x[2] - 2*z*cz*x[1] + cz2*x[0],
z2*y[2] - 2*z*cz*y[1] + cz2*y[0],
z3*x[3] - 3*z2*cz*x[2] + 3*z*cz2*x[1] - cz3*x[0],
z3*y[3] - 3*z2*cz*y[2] + 3*z*cz2*y[1] - cz3*y[0]];
var right = [
z3*x[3] - 3*z2*cz*x[2] + 3*z*cz2*x[1] - cz3*x[0],
z3*y[3] - 3*z2*cz*y[2] + 3*z*cz2*y[1] - cz3*y[0],
z2*x[3] - 2*z*cz*x[2] + cz2*x[1],
z2*y[3] - 2*z*cz*y[2] + cz2*y[1],
z*x[3] - cz*x[2],
z*y[3] - cz*y[2],
x[3],
y[3]];
if (options.fitUnitSquare) {
return {
left: left.map(function(el, i) {
if (i % 2 == 0) {
//return el * (1 / left[6])
var Xmin = left[0];
var Xmax = left[6]; //should be 1
var Sx = 1 / (Xmax - Xmin);
return (el - Xmin) * Sx;
} else {
//return el * (1 / left[7])
var Ymin = left[1];
var Ymax = left[7]; //should be 1
var Sy = 1 / (Ymax - Ymin);
return (el - Ymin) * Sy;
}
}),
right: right.map(function(el, i) {
if (i % 2 == 0) {
//xval
var Xmin = right[0]; //should be 0
var Xmax = right[6];
var Sx = 1 / (Xmax - Xmin);
return (el - Xmin) * Sx;
} else {
//yval
var Ymin = right[1]; //should be 0
var Ymax = right[7];
var Sy = 1 / (Ymax - Ymin);
return (el - Ymin) * Sy;
}
})
}
} else {
return { left: left, right: right};
}
}
var easeInOut = {
xs: [0, .42, .58, 1],
ys: [0, 0, 1, 1]
};
var splitRes = splitCubicBezier({
z: .5,
x: easeInOut.xs,
y: easeInOut.ys,
fitUnitSquare: false
});
alert(splitRes.toSource())

Finding the distance between two hexagons

I have two hexagons which I am trying to make snap together when the edges hit a certain tolerance.
How can I find which edges are the closest?
Here is the code returning the two closest Hexagons:
Canvas.getClosestPiece = function(){
var current = {};
current.x = selection.MidPoint.X;
current.y = selection.MidPoint.Y;
smallestDistance = null;
closestHex = null;
hexagons.forEach(function(hexagon){
if(hexagon !== selection){
testPiece = {};
testPiece.x = hexagon.MidPoint.X;
testPiece.y = hexagon.MidPoint.Y;
if((lineDistance(current, testPiece) < smallestDistance) || smallestDistance === null){
smallestDistance = lineDistance(current, testPiece)
closestHex = hexagon
hexagons.forEach(function(hexagon){
hexagon.lineColor = 'grey'
})
hexagon.lineColor = 'red';
}
}
})
// console.log(smallestDistance)
return [selection, closestHex]
}
Distance between two hexagon midpoints:
function lineDistance( point1, point2 ){
var xs = 0;
var ys = 0;
xs = point2.x - point1.x;
xs = xs * xs;
ys = point2.y - point1.y;
ys = ys * ys;
return Math.sqrt( xs + ys );
}
And here is a standard point array for one of the hexagons that getClosestPiece returns:
Point {X: 658, Y: 284}
Point {X: 704, Y: 304}
Point {X: 704, Y: 354}
Point {X: 658, Y: 375}
Point {X: 613, Y: 354}
Point {X: 613, Y: 304}
If your have 2 points with their coordinate like p1(x1, y1) and p2(x2, y2). You can do this:
var disptance = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
For calculating if to snap, see the other answers.
As to where to snap (which edges), which I think is your real question: calculate the relative angle using
atan2(midy1-midy2, midx1-midx2).
You get a value in radians, which describes the angle of the connection line between the hexes. 0 = horizontal line.
Calculate Math.floor(value*6/(2*pi)) --> you get a number between 0..5 denoting the edge pairing.
If your hexes are rotatable, you need to add/substract the rotatins (in rad) to/from value. (The signs are best figured out on a piece of paper).
edit: regarding your distance calculation, it is advisable to work with the square of the distance as long as possible (e.g. compare x^2+y^2 against threshold^2), to avoid the expensive Math.sqrt operation. Especially when testing distance against a multitude of other objects.
Use Euclian Distance formula
dist=sqrt((x2-xq)^2 + (y2-y1)^2)
to find which edges are the closest you hav to say us that how do you have information of the edge lines of each hexagon. here, i assume they are accessible through an array as a property of each hexagon. so we have 6 edges (edges[0] to edges[5]) for each hexagon. we can find closest edges by looping through them and measuring the distance between center of each two edges. a sample code will look like this:
var dMin=-1, iMin=-1, jMin=-1; //info about the min distance
for(var i=0; i<5; i++) //loop through hexagon1.edges
{
var p1 = midPointOfLine( hexagon1.edges[i] ); //center of this edge line
for(var j=0; j<5; j++) //loop through hexagon2.edges
{
var p2 = midPointOfLine( hexagon2.edges[j] ); //center of this edge line
var d = getDistance(p1, p2); //get distance of two points
if (d<dMin || dMin==-1) {dMin=d; iMin=i; jMin=j;} //store the info about the min distance
}
}
function midPointOfLine(edge) // return new point( X=(X1+X2)/2 , Y=(Y1+Y2)/2 )
{
var mp; //define a new point
mp.X = (edge.startPoint.X + edge.endPoint.X) / 2;
mp.Y = (edge.startPoint.Y + edge.endPoint.Y) / 2;
return mp;
}
function getDistance(p1, p2) //return sqrt( (X2-X1)^2 + (Y2-Y1)^2 )
{
return Math.sqrt( Math.pow(p2.X - p1.X, 2) + Math.pow(p2.Y - p1.Y, 2) );
}
In Summary:
Check distance between center of each edge of hexagon1 and center of
each edge of hexagon2.
The center of each edge is mid point of its
start and end points: ( (x1+x2)/2, (y1+y2)/2 ).
The distance of two points can be calculated from sqrt(dx*dx + dy*dy) formula.

Categories