place objects relative from point javascript - javascript

I have some javascript code that tries to place some objects above each other relative from a center point in this case 0,0
The thing is that the amount of objects, size of the objects, and spacing between the objects are all variable.
See this image below that explains my situation the best (hopefully ;)):
So in this case the center point is the green dot and the mesh center positions are the yellow dots.
The Mh stands for Mesh Height (variable)
The Sh stands for Spacing height (variable)
I tried to show the logic behind the calculation of the yellow dots. But when i try to implement this in javascript it works for 3 lines but it breaks on other amount of lines.
This is what i have tried so far:
var data = {
text : ["THE NEXT","POINT","OF VIEW"],
size : 5,
height : 2,
curveSegments : 12,
line_height : 2
};
function generateTextGeometry(mesh) {
var scaled_lineheight = map_r(data.size, 2, 75, 0.5, 20);
var y_start = (0 - data.text.length * data.size / 2 - data.size / 2 - (data.text.length - 1) * scaled_lineheight)/2;
var loader = new THREE.FontLoader();
loader.load( 'data/suisse_2.json', function ( font ) {
for (var i = data.text.length - 1; i >= 0; i--) {
var geometry = new THREE.TextGeometry( data.text[i], {
font: font,
size: data.size,
height: data.height,
curveSegments: data.curveSegments
});
geometry.center();
mesh.children[i].geometry = geometry;
mesh.children[i].position.y = y_start;
console.log(mesh.children[i].position);
if (i < data.text.length) {
y_start += (data.size + scaled_lineheight);
}else{
y_start += data.size;
}
}
console.log('-----------------------');
});
}
and When i console.log the position for 3 lines it is ok:
p {x: 0, y: -6.301369863013699, z: 0}
p {x: 0, y: 0, z: 0}
p {x: 0, y: 6.301369863013699, z: 0}
but for any other amount of lines it is wrong:
p {x: 0, y: -4.4006849315068495, z: 0}
p {x: 0, y: 1.9006849315068495, z: 0}
So my final question is how do i always get the yellow positions on the right location relative from the green center? What is wrong in my current logic?
If anything is unclear please let me know! So i can clarify.

So after reading my own question about 20 times. The light shined upon my brains haha.
So i just made a mistake when calculating the initial start Here is the updated line:
var y_start = 0 - (((data.text.length - 1) * data.size) + ((data.text.length - 1) * scaled_lineheight))/ 2;

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>

Closest coordinate around

I have hero coordinates, target coordinate and range.
Let's say my hero is at x: 1, y: 1;
Target coordinates are: x: 4, y: 4;
I'm getting every coordinate from target within range. So if range is 1 then I create array of objects like this:
[
{x: 3, y: 3},
{x: 4, y: 3},
{x: 5, y: 3},
{x: 3, y: 4},
{x: 5, y: 4},
{x: 3, y: 5},
{x: 4, y: 5},
{x: 5, y: 5}
]
just 1 sqm - 8 coordinates around target. I'm getting that with simple algorithm
const hero = {x: 1, y: 1};
const closest = {x: 4, y: 4};
const range = 1;
const coordsAround = [];
for (let i = 0; i <= range * 2; i++) {
for (let j = 0; j <= range * 2; j++) {
let newCoord = { x: closest.x - range + j, y: closest.y - range + i };
//here note
if(!((newCoord.x === 3 && newCoord.y === 3) || (newCoord.x === 4 && newCoord.y === 3))) {
coordsAround.push(newCoord);
}
}
}
but additionally before push to coordsAround I'm executing some function which check collisions. Here in example I just added if statement to simplify it a bit. And with this if I excluded 3,3 and 4,3.
So now my dashboard is like this:
( I accidently made this dashboard so it's in y,x pattern instead of x,y srr)
where pink is hero (1,1) red are collisions, gold is the target (4,4) and greens are around the target (gained from above code snippet)
Now it's pretty simple to say which green is closest if it would be without red tiles (collisions):
const realClosest = {
x: hero.x > closest.x
? closest.x + 1
: closest.x - 1,
y: hero.y > closest.y
? closest.y + 1
: closest.y - 1
};
console.log('real closest is:', realClosest, 'but it is not within coordsAournd:', coordsAround,
'so next closest coord is 3,4 or 4,3 but 3,4 is not within coordsAournd as well' +
'so closest coord is 4,3');
but as far as I have this red tiles I don't know how to tell which is second best, third best and so on..
Sort the tiles with a custom function so that coordsAround[0] is the tile that is the closest to the hero, coordsAround[coordsAround.length - 1] is the tile that is thefarthest:
function dist2(a, b) {
let dx = a.x - b.x;
let dy = a.y - b.y;
return dx*dx + dy*dy;
}
coordsAround.sort(function (a, b) {
let da = dist2(a, hero);
let db = dist2(b, hero);
if (da < db) return -1;
if (da > db) return 1;
return 0;
})
The auxiliary function dist2 calculates the square of the distance. The sorting rder will be the same, because sqrt(x) < sqrt(y) when x < y (and both values are non--negative). Given that your range is reates a square, not a circly, you could also use dist = Math.max(Math.abs(dx), Math.abs(dy)).

How calculate a 1/4 circle arc to move along (bezier curve)?

I'm using JQuery.path to move an object along a bezier curve. When the item is clicked, I can determine the start and end points. How do I calculate the angle and length to make the element move from point A to point B on an arc that's 1/4 of a circle intersecting the start and end point?
I essentially want it to move along a curve that never dips lower than the starting y position and never to the left of the end x position.
var path = {
start: {
x: currentLeft,
y: currentTop,
angle: ????, //Don't know how to calculate this
length: ???? //Don't know how to calculate this
},
end: {
x: endLeft,
y: endTop,
angle: ????, //Don't know how to calculate this
length: ???? //Don't know how to calculate this
}
};
jQuery(myElement).animate(
{
path: new jQuery.path.bezier(path)
}
);
Approx. what I want:
Approx what I'm getting (they're dipping too low):
A generalised solution is slightly tricky because it must handle diagonal movements in each of four diagonal directions, and horizontal, and vertical.
First, you need a couple of utility functions :
function r2d(x) {
/* radians to degrees */
return x * 180 / Math.PI;
}
function smaller(x, y) {
/* returns the closer of x|y to zero */
var x_ = Math.abs(x);
var y_ = Math.abs(y);
return (Math.min(x_, y_) === x_) ? x : y;
}
Now a main function, anim, accepts a jQuery object (containing the element of interest) and an end object (with properties .left and .top ).
function anim($el, end) {
var current = $el.position();
var slope1 = (end.top - current.top) / (end.left - current.left);
var slope2 = 1 / slope1;
var endAngle = r2d(Math.atan(smaller(slope1, slope2)));
var startAngle = -endAngle;
var length = 1/3; //Vary between 0 and 1 to affect the path's curvature. Also, try >1 for an interesting effect.
//For debugging
$("#endAngle").text(endAngle);
$("#startAngle").text(startAngle);
$("#length").text(length);
var path = {
start: {
x: current.left,
y: current.top,
angle: startAngle,
length: length
},
end: {
x: end.left,
y: end.top,
angle: endAngle,
length: length
}
};
$el.animate({ path: new jQuery.path.bezier(path) });
}
The calculation of endAngle is pretty simple for each individual case (the four diagonals, horizontal and vertical) but slightly tricky for a generalised solution. It took me a while to develop something that worked in all cases.
DEMO
If the "what you want" is really what you need, i.e. 90 degree departure and arrivals, then we can solve this problem pretty much instantly:
p_start = { X:..., Y:... }
p_end = { X:..., Y:... }
dx = p_end.X - p_start.X
dy = p_end.Y - p_start.Y
control_1 = { X: p_start.X, Y: p_start.Y + 0.55228 * dy }
control_2 = { X: p_end.X - 0.55228 * dx, Y: p_end.Y }
And done. What we've basically done is pretend that the start and end points lie on a circle, and computer the control points such that the resulting Bezier curve has minimal error wrt the quarter circular arc.
In terms of angles: The departure from start is always at angle π/2, and the arrival at the end points is always at angle 0.

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())

How to animate both rotation and transformation in Raphaël

I'm trying to do something I thought would be rather simple. I've an object that I move around stepwise, i.e. I receive messages every say 100 milliseconds that tell me "your object has moved x pixels to the right and y pixels down". The code below simulates that by moving that object on a circle, but note that it is not known in advance where the object will be heading in the next step.
Anyway, that is pretty simple. But now I want to also tell the object, which is actually a set of subobjects, that it is being rotated.
Unfortunately, I am having trouble getting Raphaël to do what I want. I believe the reason is that while I can animate both translation and rotation independently, I have to set the center of the rotation when it starts. Obviously the center of the rotation changes as the object is moving.
Here's the code I'm using and you can view a live demo here. As you can see, the square rotates as expected, but the arrow rotates incorrectly.
// c&p this into http://raphaeljs.com/playground.html
var WORLD_SIZE = 400,
rect = paper.rect(WORLD_SIZE / 2 - 20, 0, 40, 40, 5).attr({ fill: 'red' }),
pointer = paper.path("M 200 20 L 200 50"),
debug = paper.text(25, 10, ""),
obj = paper.set();
obj.push(rect, pointer);
var t = 0,
step = 0.05;
setInterval(function () {
var deg = Math.round(Raphael.deg(t));
t += step;
debug.attr({ text: deg + '°' });
var dx = ((WORLD_SIZE - 40) / 2) * (Math.sin(t - step) - Math.sin(t)),
dy = ((WORLD_SIZE - 40) / 2) * (Math.cos(t - step) - Math.cos(t));
obj.animate({
translation: dx + ' ' + dy,
rotation: -deg
}, 100);
}, 100);
Any help is appreciated!
If you want do a translation and a rotation too, the raphael obj should be like that
obj.animate({
transform: "t" + [dx , dy] + "r" + (-deg)
}, 100);
Check out http://raphaeljs.com/animation.html
Look at the second animation from the top on the right.
Hope this helps!
Here's the code:
(function () {
var path1 = "M170,90c0-20 40,20 40,0c0-20 -40,20 -40,0z",
path2 = "M270,90c0-20 40,20 40,0c0-20 -40,20 -40,0z";
var t = r.path(path1).attr(dashed);
r.path(path2).attr({fill: "none", stroke: "#666", "stroke-dasharray": "- ", rotation: 90});
var el = r.path(path1).attr({fill: "none", stroke: "#fff", "stroke-width": 2}),
elattrs = [{translation: "100 0", rotation: 90}, {translation: "-100 0", rotation: 0}],
now = 0;
r.arrow(240, 90).node.onclick = function () {
el.animate(elattrs[now++], 1000);
if (now == 2) {
now = 0;
}
}; })();

Categories