Calculate point in 3d space, between two points at specific distance - javascript

For a camera movement in three.js I need to calculate point C so to move the camera from point A to a certain distance dist to point B.

three.js has methods to do that very easily.
Assuming a, b, and c are instances of THREE.Vector3(),
a.set( 2, 1, 4 );
b.set( 9, 4, 2 );
c.subVectors( a, b ).setLength( dist ).add( b );
three.js r.91

So you need to calculate the coordinates of point C, given that it lies on the line between B and A at the given distance from B? This is pretty straightforward using the following steps:
Calculate the vector from B to A (this will just be A - B).
Normalize that vector (make it's length 1).
Multiply that by the distance you want.
Add that vector to the point B.
So a short javascript example:
const A = [2, 1, 4];
const B = [9, 4, 2];
const dist = 3;
function addVectors(v1, v2) {
return v1.map((x, i) => x + v2[i]);
}
function scalarMultiply(v, c) {
return v.map(x => x*c);
}
function getLength(v) {
return Math.hypot(...v);
}
const vecB2A = addVectors(A, scalarMultiply(B, -1)); // Step 1
const normB2A = scalarMultiply(vecB2A, 1/getLength(vecB2A)); // Step 2
const distB2A = scalarMultiply(normB2A, dist); // Step 3
const C = addVectors(B, distB2A); // Final step
console.log(C);

Point C is equal to point B minus 'dist' times a unit vector whose direction is AB. So it is quite easy:
vector v from A to B is equal (xB-xA, yB-yA, zB-zA) / distance(AB)
Then C = B - d*v where d is the distance from B you want C to be.

Related

How to find all elements between two coordinates in a 2d array

Essentially I'm trying to create the game reversi.
To cut it short if you don't know what it is, I have a 8x8 board of squares. There are 2 coordinates and I need to determine all the squares that are between the two coordinates and fill them in. The 2 coordinates are either on the same y, same x or diagonal to each other.
Can someone explain the logic behind how I would go about doing something like this? How can I determine the coordinates of all the elements between the 2 coordinates.
You need a simple for loop, starting at one of the coordinates and moving towards the other.
let connect = (c1, c2) => {
// Determine the distance between c1 & c2
let delta = c1.map((v, i) => c2[i] - v);
let distance = Math.max(...delta.map(v => Math.abs(v)));
// Determine the unit vector (e.g. [1, -1]) to move each iteration
let direction = delta.map(v => v / distance);
// Starting at `c1`, iterate for `distance` iterations, moving in `direction` each iteration.
return [...Array(distance + 1)].map((_, i) => c1.map((v, j) => v + direction[j] * i));
// Same as above, but exclude `c1` and `c2` from the return array.
// return [...Array(distance - 1)].map((_, i) => c1.map((v, j) => v + direction[j] * (i + 1)));
};
let c1 = [3, 6];
let c2 = [8, 1];
console.log(connect(c1, c2));

Getting two points on the edges of a rectangle

I have a rectangle and would like to:
Get a random point on one (any) of the sides.
Get a random point on one (except for the previously picked) side.
My initial approach is to create arrays for each possible side.
var arr:Array = [[{x:0,y:0}, // Top
{x:width,y:0}], //
[{x:width,y:0}, // Right
{x:width,y:height}], //
[{x:width,y:height}, // Bottom
{x:0,y:height}], //
[{x:0,y:height}, // Left
{x:0,y:0}]]; //
Then, I get the sides.
rand is an instance of Rand and has the methods:
.next() which provides a random number between 0 and 1
.between(x,y) which returns a random number between x and y.
var firstSide:Array = arr[rand.next() * arr.length];
var secondSide:Array;
do {
secondSide = arr[rand.next() * arr.length];
} while(secondSide.equals(firstSide));
Finally, I calculate my points.
var pointOnFirstSide:Object = {x:rand.between(firstSide[0].x, firstSide[1].x),
y:rand.between(firstSide[0].y, firstSide[1].y};
var pointOnSecondSide:Object = {x:rand.between(secondSide[0].x, secondSide[1].x),
y:rand.between(secondSide[0].y, secondSide[1].y};
I don't think this is the most efficient way to solve this.
How would you do it?
Assuming we have the following interfaces and types:
interface Rand {
next(): number;
between(x: number, y: number): number;
}
interface Point {
x: number;
y: number;
}
type PointPair = readonly [Point, Point];
and taking you at your word in the comment that the procedure is: first randomly pick two sides, and then pick random points on those sides... first let's see what's involved in picking two sides at random:
const s1 = Math.floor(rand.between(0, arr.length));
const s2 = (Math.floor(rand.between(1, arr.length)) + s1) % arr.length;
s1 and s2 represent the indices of arr that we are choosing. The first one chooses a whole number between 0 and one less than the length of the array. We do this by picking a real number (okay, floating point number, whatever) between 0 and the length of the array, and then taking the floor of that real number. Since the length is 4, what we are doing is picking a real number uniformly between 0 and 4. One quarter of those numbers are between 0 and 1, another quarter between 1 and 2, another quarter between 2 and 3, and the last quarter are between 3 and 4. That means you have a 25% chance of choosing each of 0, 1, 2 and 3. (The chance of choosing 4 is essentially 0, or perhaps exactly 0 if rand is implemented in the normal way which excludes the upper bound).
For s2 we now pick a number uniformly between 1 and the length of the array. In this case, we are picking 1, 2, or 3 with a 33% chance each. We add that number to s1 and then take the remainder when dividing by 4. Think of what we are doing as starting on the first side s1, and then moving either 1, 2, or 3 sides (say) clockwise to pick the next side. This completely eliminates the possibility of choosing the same side twice.
Now let's see what's involved in randomly picking a point on a line segment (which can be defined as a PointPair, corresponding to the two ends p1 and p2 of the line segment) given a Rand instance:
function randomPointOnSide([p1, p2]: PointPair, rand: Rand): Point {
const frac = rand.next(); // between 0 and 1
return { x: (p2.x - p1.x) * frac + p1.x, y: (p2.y - p1.y) * frac + p1.y };
}
Here what we do is pick a single random number frac, representing how far along the way from p1 to p2 we want to go. If frac is 0, we pick p1. If frac is 1, we pick p2. If frac is 0.5, we pick halfway between p1 and p2. The general formula for this is a linear interpolation between p1 and p2 given frac.
Hopefully between the two of those, you can implement the algorithm you're looking for. Good luck!
Link to code
jcalz already gave an excellent answer. Here is an alternate version for the variant I asked about in the comments: When you want your points uniformly chosen over two sides of the perimeter, so that if your w : h ratio was 4 : 1, the first point is four times as likely to lie on a horizontal side as a vertical one. (This means that the chance of hitting two opposite long sides is 24/45; two opposite short side, 1/45; and one of each, 20/45 -- by a simple but slightly tedious calculation.)
const rand = {
next: () => Math. random (),
between: (lo, hi) => lo + (hi - lo) * Math .random (),
}
const vertices = (w, h) => [ {x: 0, y: h}, {x: w, y: h}, {x: w, y: 0}, {x: 0, y: 0} ]
const edges = ([v1, v2, v3, v4]) => [ [v1, v2], [v2, v3], [v3, v4], [v4, v1] ]
const randomPoint = ([v1, v2], rand) => ({
x: v1 .x + rand .next () * (v2 .x - v1 .x),
y: v1 .y + rand .next () * (v2 .y - v1 .y),
})
const getIndex = (w, h, x) => x < w ? 0 : x < w + h ? 1 : x < w + h + w ? 2 : 3
const twoPoints = (w, h, rand) => {
const es = edges (vertices (w, h) )
const perimeter = 2 * w + 2 * h
const r1 = rand .between (0, perimeter)
const idx1 = getIndex (w, h, r1)
const r2 = (
rand. between (0, perimeter - (idx1 % 2 == 0 ? w : h)) +
Math .ceil ((idx1 + 1) / 2) * w + Math .floor ((idx1 + 1) / 2) * h
) % perimeter
const idx2 = getIndex (w, h, r2)
return {p1: randomPoint (es [idx1], rand), p2: randomPoint (es [idx2], rand)}
}
console .log (
// Ten random pairs on rectangle with width 5 and height 2
Array (10) .fill () .map (() => twoPoints (5, 2, rand))
)
The only complicated bit in there is the calculation of r2. We calculate a random number between 0 and the total length of the remaining three sides, by adding all four sides together and subtracting off the length of the current side, width if idx is even, height if it's odd. Then we add it to the total length of the sides up to and including the index (where the ceil and floor calls simply count the number of horizontal and vertical sides, these values multiplied by the width and height, respectively, and added together) and finally take a floating-point modulus of the result with the perimeter. This is the same technique as in jcalz's answer, but made more complex by dealing with side lengths rather than simple counts.
I didn't make rand an instance of any class or interface, and in fact didn't do any Typescript here, but you can add that yourself easily enough.

Which face is further in parallel projections?

I'm working with JavaScript(React) on a geometry program that creates the axonometry(better parallel projections) of the specified object defined by vertices and faces(a face can have different numbers of vertices).
It works perfectly when you do not need faces to be opaque otherwise there are faces above other that should be below.
So I want to order my list of faces from the further to the nearest:
[
[[100, 0, 100], [50, 50, 50], [120, 170, 120], [10, 200, 150]],
[[10, 20, 30], [10, 200, 250], [50, 50, 50], [100, 30, 30]],...
]
I will use faces.sort(sortingFunction).
I don't care about intersecting faces
(it will take the faces of all objects together)
How should sortingFunction be?
You have to consider how is the axonometry defined. It is defined by the X-, Y-axis rotation(Xrotation can be both greater and smaller than Yrotation), Z rotation is π / 4 (90°).
Here is an old version of the application that makes you understand what I mean: http://dev_proiezioni.surge.sh/
Sorry for my terrible English.
Thanks
What you are trying to do is called "back-face culling. One common technique is to determine if the list of the points in the representation of a polygon are in clockwise or counterclockwise order from the point of view of the camera. This requires that you are very careful about how you create the list of vertices. For more details, check out the wikipedia article: https://en.wikipedia.org/wiki/Back-face_culling. The Implementation section describes the mathematics involved which you will have to translate into JavaScript. Note that this technique is faster than sorting the list of faces because it requires checking each face only once rather than comparing each face against other faces.
I don't care about intersecting faces
That means that we can reduce the plains to points, by taking the point in the middle:
const vecOp = op => (a, b) => a.map((c, i) => op(c, b[i] || b));
const add = vecOp((a, b) => a + b);
const sub = vecOp((a, b) => a - b);
const mul = vecOp((a, b) => a * b);
const div = vecOp((a, b) => a / b);
const sum = v => v.reduce((a, b) => a + b, 0);
const middle = (a, b) => div(add(a, b), 2);
const planeToPoint = ([a, b, c, d]) => middle(
middle(a, b),
middle(c, d)
);
Now to sort by "closer to camera", one could draw a line between the centers of two planes, which will result in a direction:
const aToB = (a, b) =>
sub(
planeToPoint(b),
planeToPoint(a)
);
Now we could turn the camera rotation into a camera lookAt vector :
const rotToVec = (yaw, pitch) => ([
Math.cos(yaw) * Math.cos(pitch),
Math.sin(yaw) * Math.cos(pitch),
Math.sin(pitch)
]);
and that direction can be compared to the cameras direction resulting in an angle between them:
const angle = (a, b) => sum(mul(a, b)) / sum(a) * sum(b)
Now lets turn that alltogether:
const camVec = rotToVec(cam.yaw, cam.pitch);
planes.sort((a, b) =>
Math.abs(angle(aToB(a, b), camVec)) < Math.PI / 4 /*90°*/ ? 1 : -1
);
Disclaimer: I neither tried the code above, nor did I work with parallel projections, nor am I good at math, so take my words with caution, I have no idea what I'm talking about
For an approximate solution, use a 3D to 3D transform, and consider the Z coordinate. For every face, keep the nearest Z and sort the faces on this Z.
For a more exact solution, consider https://en.wikipedia.org/wiki/Newell%27s_algorithm.
Sort by the distance from where you camera is.
function distanceBetweenTwoPoints (p1, p2) {
return Math.hypot(p1.x - p2.x, p1.y - p2.y, p1.z - p2.z)
}
function sortFunction (p1, p2) {
return distanceBetweenTwoPoints(camera, p1) > distanceBetweenTwoPoints(camera,p2) ? -1 : 1
}
Tweak the sign > depending on which order you'd like.

Calculate stationary points in JS with math.js

I want to calculate the stationary points of a derivative in JavaScript with the external library math.js.
So if I have f(x) = x^2, the derivative is 2x and the stationary points can be calculated making 2x = 0 so x = 0 . But the function in math.js that is math.eval needs to assign a value to x.
For example:
let scope = {
a: 3,
b: 4
}
const c = math.eval('a * b', scope);
console.log(c); // 12
<script src="https://unpkg.com/mathjs#5.0.4/dist/math.min.js"></script>
So how can I calculate 2x + 2 = 0 for example? I want that the result given to me is x = -1.

Finding closest 90 degree angle spot

I've gotten pretty far but something just doesn't seem to work.
A = 50.88259382849774,6.003988087177277
B = 50.88269282423443,6.0036662220954895
C = 50.882530369581545,6.003847271203995
The C coordinate is a little off from the 90 degree line (x) and this function I made should position C on the closest way to the x line.
this.snapCoords = function(a, b, c){
var result = (b.x-a.x)*(c.x-b.x)+(b.y-a.y)*(c.y-b.y);
var negative = false;
if(result < 0){
result = result*-1;
negative = true;
}
result = Math.sqrt(result);
result = result/2;
if(negative === false){
var d = {x: c.x+result, y: c.y-result};
}
else{
var d = {x: c.x-result, y: c.y+result};
}
console.log(d); // returns : 50.88246729610898,6.003910344676565
}
It does get the the 90 degree (x) line but not the closest way. Something must still be wrong in my function but I can't figure it out.
EDIT:
So this is my problem
My function puts the third coordinate on C which is 90 degrees but not where it should be (the red spot) it somehow extends to a further point.
I think the OP is trying to project the point C onto the line passing thru point B and is perpendicular to line AB. If this is the case, the math for the projection is not correct. You can find the projected point D as
D= C - dot(vec(BC), vec(AB)) * vec(AB)/|vec(AB)|^2
By this calculation, the projected point D will be (50.8825952820492, 6.00363622113846).
The following is a picture for points A, B, C and D :

Categories