How can I lerp between two 3d vectors?
I use this method for 2d vectors:
public Vector2d lerp(Vector2d other, double speed, double error) {
if (equals(other) || getDistanceSquared(other) <= error * error)
return other;
double dx = other.getX() - this.x, dy = other.getY() - this.y;
double direction = Math.atan2(dy, dx);
double x = this.x + (speed * Math.cos(direction));
double y = this.y + (speed * Math.sin(direction));
return new Vector2d(x, y);
}
Note: this is not exactly "linear interpolation"; this method will interpolate at a constant rate, which is what I want.
I want to do exactly what this does but with an added z component for the third dimension. How can I do this?
The easiest way would be to transform your two vectors such that they lie in the (u, v) plane; then apply your method above; then transform back to the original coordinate space.
This requires you to construct a rotation matrix:
Take the cross product of your two vectors to get the mutual normal vector; call this cross_1;
Define that this points along the u axis;
Take the cross product of this and cross_1 to get a vector cross_2, which is the direction of your v axis.
Normalize each of these three vectors; call them this_norm, cross_2_norm and cross_1_norm.
These three vectors can be written as a 3x3 orthonormal matrix (each of the vectors is a 3-element column vector):
R = [ this_norm cross_2_norm cross_1_norm ]
Now: you can multiply your 3d vectors this and other by this matrix, and you will get vectors which have the form
[ u ]
[ v ]
[ 0 ]
i.e. a 3-dimensional column vector with zero as the third element (or, at least, you should. I may have forgotten to transpose the 3x3 matrix above).
So, you can obviously discard the third element, and have 2-element column vectors: you can store these in Vector2d. And so you can apply your method above to do the interpolation.
That gives you a Vector2d which interpolates in the (u, v) plane. You can transform that back to the (x, y, z) space by attaching a zero third element to it, and pre-multiplying by R' (which is the inverse of R, since it is orthonormal).
Of course, you need to handle degenerate cases, like zero and (anti-)parallel vectors. In these cases, one or both of the cross products are zero, meaning you can't normalize them; simply pick arbitrary directions instead.
If I understand your code correctly, when you compute dx and dy offsets, then compute angle from it, and finally sin/cos pair - you're basically normalizing the dx,dy vector, so you could write it like that:
Vector2d delta = other - this; // I'm not sure about your API here,
delta.normalize(); // you may need to fix those lines
double x = this.x + (speed * delta.x);
double y = this.y + (speed * delta.y);
Now it should be straightforward to add a Z component.
Related
I'm trying to calculate the lateral surface of a frustum cone using the code below.
Input: 2 Nodes containing x, y, z values (absolut position) and radius (radius at position) all in double
What I am doing so far:
1. calculate length of frustum cone
2. calculate lateral surface of frustum cone
My Problem:
Hypothetically substraction of 2 floating point numbers with similar magnitude is problematic due to precision loss. (I didn't run into the issue)
My Question:
What can I do to improve the final result?
Possibilities I found / thought of:
- using BigDecimal (what I don't want to because of longer runtime)
- replacing (r1-r2)*(r1-r2) with r1^2 - 2*r1*r2 + r2^2
- implement a check on how close two double values are and if very close assume their difference to be 0. Would that even improve the accuracy of my final result? Doesn't the result of the substraction have a smaller absolute error than the one with 0 assumed?
public static double calculateLateralSurface(Node node1, Node node2) {
double x, y, z, length, r1, r2, lateralSurface;
// calculate length of frustum cone
x = node1.getPosX() - node2.getPosX();
y = node1.getPosY() - node2.getPosY();
z = node1.getPosZ() - node2.getPosZ();
length = Math.sqrt(x * x + y * y + z * z);
r1 = node1.getRadius();
r2 = node2.getRadius();
// calculate lateral surface of frustum cone
lateralSurface = (r1+r2)*Math.PI*Math.sqrt((r1-r2)*(r1-r2)+length*length);
return lateralSurface;
}
I hope someone can help me :)
double has more than enough accuracy for any practical and even not-so-practical use. So, unless your cone describes the field of view of an optical telescope from Earth all the way to Alpha Centauri, you should not have any precision problems.
I can point out to you that you are calculating length by taking a square root only to square it again later before using it, so eliminating that unnecessary pair of calculations might somehow improve things, but I doubt that this is your problem.
So, if your results do not look correct, then maybe a bug is to blame and not the precision of double.
In any case, why don't you provide a self-contained, compilable example, and actual input values, and actual output values, and your expected values, and we can look deeper into it.
I am currently working on a Processing (as in the language) sketch, which is driven by Turtle logic (see https://en.wikipedia.org/wiki/Turtle_graphics). This means that I draw a line from the current coordinate to a supplied coordinate. This supplied coordinate will then become the new current coordinate. I want to approximate a circle and have written a simple piece of code using trigonometrics. The code looks as follow:
void drawCircle(int radius){
// The circle center is radius amount to the left of the current xpos
int steps = 16;
double step = TWO_PI /steps;
for(double theta = step; theta <= TWO_PI; theta += step){
float deltaX = cos((float)theta) - cos((float)(theta - step));
float deltaY = sin((float)theta) - sin((float)(theta - step));
moveXY(deltaX*radius, deltaY*radius);
}
}
The program logic is simple. It will use the variable theta to loop through all the radians in a circle. The amount of steps will indicate how large each theta chunk is. It will then calculate the x,y values for the specific point in the circle governed by theta. It will then deduct the x,y values of the previous cycle (hence the theta-step) to get the amount it will have to move from this position to attain the desired x,y position. It will finally supply those delta values to a moveXY function, which draws a line from the current point to the supplied values and makes them the new current position.
The program seems to work quite well when using a limited amount of steps. However, when the step count is increased, the circles become more and more like a Fibonacci spiral. My guess is that this is due to imprecision with the float number and the sine and cosine calculations, and that this adds up with each iteration.
Have I interpreted something wrong? I am looking to port this code to Javascript eventually, so I am looking for a solution in the design. Using BigDecimal might not work, especially since it does not contain its own cosine and sine functions. I have included a few images to detail the problem. Any help is much appreciated!
Step count 16:
Step count 32:
Step count 64:
Step count 128:
Float and sine/cosine should be sufficiently precise. The question is: How precise is your position on the plane? If this position is measured in pixels, then each of your floating point values is rounded to integer after each step. The loss of precision then adds up.
At each iteration round the loop, you are calculating the delta without regard of what the current coordinate is. So effectively, you are "dead-reckoning", which is always going to be inaccurate, since errors at each step build up.
Since you know that you want a circle, an alternative approach would be at each iteration, to first determine the actual point on the circle you want to get to, and then calculate the delta to get there - so something like the following (but I must admit I haven't tested it !):
void drawCircle(int radius){
// The circle center is radius amount to the left of the current xpos
int steps = 16;
double step = TWO_PI /steps;
float previousX = 0;
float previousY = radius;
for(double theta = step; theta <= TWO_PI; theta += step){
float thisX = radius * sin((float)theta);
float thisY = radius * cos((float)theta);
moveXY(thisX - previousX, thisY - previousY);
previousX = thisX;
previousY = thisY;
}
}
This question already has answers here:
What algorithm can I use to determine points within a semi-circle?
(11 answers)
Closed 8 years ago.
I have a points x and y and I need to check the point which intersect within a semi- pie.
I need an algorithm to find the point is intersect in the semi circle. for rectangle we have point contains method to check the point intersect the rectangle, but this doesn't work for semi circle segments with start and end angle.
I have created many semi pie segment, when I touch the pie segment, I need to check the point intersect of which segment.
I'm assuming this is speed-critical, and also that you want to be able to specify the semi-circle as having a center and arbitrary start and end angles, so not just a semi-circle but a circular sector. For a semi-circle just make the start and end angles 180 degrees apart, or remove the test against endVector.
Make the test a two-step process. For a given point, first check that it is inside a rectangle enclosing the semi-circle. You can use your existing sort and binary search algorithm for this. If a point is not in the rectangle then reject the point, if it is then test against the semi-circle.
Also, outside of the loop, convert the values specifying the semi-circle into a form that will enable the tests to be done faster:
Convert the radius to radius squared. Compare the distance of a point from the center squared, to the radius squared. This saves a square root when computing the distance.
Convert the start and end angles of the semi-circle to a couple of 2D vectors, and then use these to check whether the point is inside the sector. Then you won't have to use any trig functions like atan2(y, x), which are slow, or do annoying fiddly comparisons to start and end angles and handling the case where angles wrap around from 360 to 0.
pseudo code:
float radiusSquared;
float startVectorX;
float startVectorY;
float endVectorX;
float endVectorY;
float centerX;
float centerY;
void convertSector(float radius, float startAngle, float endAngle)
{
radiusSquared = radius * radius;
startVectorX = cos(startAngle);
startVectorY = sin(startAngle);
endVectorX = cos(endAngle);
endVectorY = sin(endAngle);
}
boolean testPoint(float x, float y)
{
// check if point is within the radius:
float distanceX = x - centerX;
float distanceY = y - centerY;
if((distanceX * distanceX) + (distanceY * distanceY)) > radiusSq)
return false;
// check if point is outside start radius vector using 2D dot-product with normal:
if(((distanceX * -startVectorY) + (distanceY * startVectorX)) < 0.0f)
return false;
// check if point is outside end radius vector using 2D dot-product with normal:
if(((distanceX * -endVectorY) + (distanceY * endVectorX)) > 0.0f)
return false;
return true;
}
The above code will only work for sectors with an internal angle of <= 180 degrees. To test a point against sectors larger than that (i.e. Pac-Man like shapes), test if the point is inside the circle and then test that it is not inside the sector making up the remainder of the circle.
This is to find evenly-distributed points lying on a specific line (from a starting position, 2 points on the line, and an angle against the horizontal) and then past the second point, to draw something so it's moving at a fixed rate in a given direction.
I'm thinking about calculating a slope, which would give me vertical movement to horizontal movement. However, I don't even know how to assure they'd be the same speed in two different lines. For example, if there are two different pairs of points, how it would take the same amount of time for the drawing to travel the same distance on both.
Is what I'm describing the correct idea? Are there any methods in OpenGL that could help me?
You should use vectors. Start with a point and travel in the direction of a vector. For example:
typedef struct vec2 {
float x;
float y;
} vec2;
That defines a basic 2D vector type. (This will work in 3D, just add a z coord.)
To move a fixed distance in a given direction, simply take some starting point and add the direction vector scaled by a scalar amount. Like this:
typedef struct point2D {
float x;
float y;
} point2D; //Notice any similarities here?
point2D somePoint = { someX, someY };
vec2 someDirection = { someDirectionX, someDirectionY };
float someScale = 5.0;
point2D newPoint;
newPoint.x = somePoint.x + someScale * someDirection.x;
newPoint.y = somePoint.y + someScale * someDirection.y;
The newPoint will be 5 units in the direction of someDirection. Note that you'll probably want to normalize someDirection before using it in this manner, so it's length is 1.0:
void normalize (vec2* vec)
{
float mag = sqrt(vec->x * vec->x + vec->y * vec->y);
// TODO: Deal with mag being 0
vec->x /= mag;
vec->y /= mag;
}
I have a 2D convex polygon in 3D space and a function to measure the area of the polygon.
public double area() {
if (vertices.size() >= 3) {
double area = 0;
Vector3 origin = vertices.get(0);
Vector3 prev = vertices.get(1).clone();
prev.sub(origin);
for (int i = 2; i < vertices.size(); i++) {
Vector3 current = vertices.get(i).clone();
current.sub(origin);
Vector3 cross = prev.cross(current);
area += cross.magnitude();
prev = current;
}
area /= 2;
return area;
} else {
return 0;
}
}
To test that this method works at all orientations of the polygon I had my program rotate it a little bit each iteration and calculate the area. Like so...
Face f = poly.getFaces().get(0);
for (int i = 0; i < f.size(); i++) {
Vector3 v = f.getVertex(i);
v.rotate(0.1f, 0.2f, 0.3f);
}
if (blah % 1000 == 0)
System.out.println(blah + ":\t" + f.area());
My method seems correct when testing with a 20x20 square. However the rotate method (a method in the Vector3 class) seems to introduce some error into the position of each vertex in the polygon, which affects the area calculation. Here is the Vector3.rotate() method
public void rotate(double xAngle, double yAngle, double zAngle) {
double oldY = y;
double oldZ = z;
y = oldY * Math.cos(xAngle) - oldZ * Math.sin(xAngle);
z = oldY * Math.sin(xAngle) + oldZ * Math.cos(xAngle);
oldZ = z;
double oldX = x;
z = oldZ * Math.cos(yAngle) - oldX * Math.sin(yAngle);
x = oldZ * Math.sin(yAngle) + oldX * Math.cos(yAngle);
oldX = x;
oldY = y;
x = oldX * Math.cos(zAngle) - oldY * Math.sin(zAngle);
y = oldX * Math.sin(zAngle) + oldY * Math.cos(zAngle);
}
Here is the output for my program in the format "iteration: area":
0: 400.0
1000: 399.9999999999981
2000: 399.99999999999744
3000: 399.9999999999959
4000: 399.9999999999924
5000: 399.9999999999912
6000: 399.99999999999187
7000: 399.9999999999892
8000: 399.9999999999868
9000: 399.99999999998664
10000: 399.99999999998386
11000: 399.99999999998283
12000: 399.99999999998215
13000: 399.9999999999805
14000: 399.99999999998016
15000: 399.99999999997897
16000: 399.9999999999782
17000: 399.99999999997715
18000: 399.99999999997726
19000: 399.9999999999769
20000: 399.99999999997584
Since this is intended to eventually be for a physics engine I would like to know how I can minimise the cumulative error since the Vector3.rotate() method will be used on a very regular basis.
Thanks!
A couple of odd notes:
The error is proportional to the amount rotated. ie. bigger rotation per iteration -> bigger error per iteration.
There is more error when passing doubles to the rotate function than when passing it floats.
You'll always have some cumulative error with repeated floating point trig operations — that's just how they work. To deal with it, you basically have two options:
Just ignore it. Note that, in your example, after 20,000 iterations(!) the area is still accurate down to 13 decimal places. That's not bad, considering that doubles can only store about 16 decimal places to begin with.
Indeed, plotting your graph, the area of your square seems to be going down more or less linearly:
This makes sense, assuming that the effective determinant of your approximate rotation matrix is about 1 − 3.417825 × 10-18, which is well within normal double precision floating point error range of one. If that's the case, the area of your square would continue a very slow exponential decay towards zero, such that you'd need about two billion (2 × 109) 7.3 × 1014 iterations to get the area down to 399. Assuming 100 iterations per second, that's about seven and a half months 230 thousand years.
Edit: When I first calculated how long it would take for the area to reach 399, it seems I made a mistake and somehow managed to overestimate the decay rate by a factor of about 400,000(!). I've corrected the mistake above.
If you still feel you don't want any cumulative error, the answer is simple: don't iterate floating point rotations. Instead, have your object store its current orientation in a member variable, and use that information to always rotate the object from its original orientation to its current one.
This is simple in 2D, since you just have to store an angle. In 3D, I'd suggest storing either a quaternion or a matrix, and occasionally rescaling it so that its norm / determinant stays approximately one (and, if you're using a matrix to represent the orientation of a rigid body, that it remains approximately orthogonal).
Of course, this approach won't eliminate cumulative error in the orientation of the object, but the rescaling does ensure that the volume, area and/or shape of the object won't be affected.
You say there is cumulative error but I don't believe there is (note how your output desn't always go down) and the rest of the error is just due to rounding and loss of precision in a float.
I did work on a 2d physics engine in university (in java) and found double to be more precise (of course it is see oracles datatype sizes
In short you will never get rid of this behaviour you just have to accept the limitations of precision
EDIT:
Now I look at your .area function there is possibly some cumulative due to
+= cross.magnitude
but I have to say that whole function looks a bit odd. Why does it need to know the previous vertices to calculate the current area?