May I ask you for piece of advice writing my raytracer in c#. Here's the rpoblem:
I've got a problem detecting hits of the rays and geometry in my raytracer. I've implemented several functions based on this articles: https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle/ray-triangle-intersection-geometric-solution
Here's the code of calculating hitpoint:
public Vector3 GetHitPoint(Vector3 origin, Vector3 ray)
{
float D = Vector3.Dot(this.GetNormal(), vertices[0]);
float up = Vector3.Dot(this.GetNormal(), origin);
up = -1 * (up + D); //50
float down =Vector3.Dot(this.GetNormal(), ray); //0.999975
float t = up / -1* down; //50,00125003125078
Console.WriteLine(origin + Vector3.Multiply(t, ray));
return origin + Vector3.Multiply(t, ray);
}
Not very elegant, but shall work. I've got the problem with precision. I've prepared test triangle that is located perpendicular to camera (center -> 0,0,50). The code calculates the point of intersection between triangle and ray.
Origin stands for camera position, ray is the normalized vector that comes from camera to geometry, vertices[0] is the position of vertex and GetNormal() function gives correct normal vector of triangle. The problem in this case is the precision of calculation. After doing all these calculation, my hitpoint has the z coordinate of 49.99975 instead of 50.0.
This is a problem, because I use another algorithm (based on baricentric coordinates) in order to check if hitpoint is inside the triangle.
public bool CheckHitPoint(Vector3 P)
{
Vector3 v0 = vertices[1] - vertices[0];
Vector3 v1 = vertices[2] - vertices[0];
Vector3 v2 = P - vertices[0];
float d00 = Vector3.Dot(v0, v0);
float d01 = Vector3.Dot(v0, v1);
float d11 = Vector3.Dot(v1, v1);
float d20 = Vector3.Dot(v2, v0);
float d21 = Vector3.Dot(v2, v1);
float denom = d00 * d11 - d01 * d01;
float v = (d11 * d20 - d01 * d21);
float w = (d00 * d21 - d01 * d20);
float u = 1.0f - v - w;
if (u < 0 || v < 0 || w < 0)
{
return false;
}
else if (u > 1 || v > 1 || w > 1)
{
return false;
}
else return true;
}
The conditions in algorithm and in the article is the same, but because of inaccurate result of previous function, u v w coefficients are completely wrong (since hitpoint is actually in front of the triangle).
Can I ask you to help me fixing the precision issue in the first algorithm, or introduce some kind of bias into the second algorithm, so I can get the precise hitpoint and successfully detect it inside triangle?
EDIT: Sorry, I thought that problem is preety much clear. Let me explain it deeper. Let's see the triangle:
Vector3[] vertices =
{
new Vector3(-5,-5,50),
new Vector3(5,-5,50),
new Vector3(0,5,50)
};
Vector3[] normals =
{
new Vector3(0,0,-1),
new Vector3(0,0,-1),
new Vector3(0,0,-1)
};
It is clear that Normal Vector for this triangle is n(0,0,-1) and in combination with any point on it's surface, it may describe mathematically the surface.
D is the distance between between (0,0,0) and point on surface, that the triangle lies on. Since the surface may be described as a pair of parameters (normal vector and any point on surface), dot product of these parameter describes D.
Next four lines describes the equation:
t = N(A,B,C)⋅O + D/−N(A,B,C)⋅R
where
N(A,B,C) - normal vector of triangle
O - camera Position
D - Distance from (0,0,0) to surface
R - Normalized Ray Vector
Equation calculates the distance from camera position to point of intersection.
Following these equations, with triangle parameters I attached and camera position (0,0,0) poinitng at (0,0,50) the return value shall be the point with coordinates (x,y,50), no matter what is the pixel I create the ray for.
The problem is, vector methods in c# generally use floats for computations, and this is why the z coordinate is close, but not precisely 50.
Mathematically this is 100% correct, but the precision is poor.
This makes a problem, when I try to check, if the point lies inside triangle using transformation to baricentric coordinates.
The second method is ok mathematically too. Provided that Hitpoint is on the surface, if all coordinates are between 0 and 1 that means that point lies on the triangle. Details here: https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle/barycentric-coordinates
If only I could precisely count the coordinates from previous method, that would work. The problem is, because of lack of precision, the hitpoint is slightly on top or below the surface, and baricentric coordinates have crazy values.
The question is, how can I make the first method to be more precise, that means reproduce the t - distance between camera and hitpoint to be enough precise to be 50. What solution would be the best, rounding, creating custom methods for vectors replacing the built-in float-based methods or maybe some algorithm modification?
Would be grateful, if someone with experience with raytracers would gave me the piece of advice.
Related
I'm trying to write a function to handle movement within a game I'm programming. What I have nearly works, but there are a couple situations where it breaks down.
I've coded up a minimal demonstrative example, presented below. In this example, I'm trying to calculate the travel of an object, represented by a point, and movement vector. This object's movement path is checked against a collection of polygons, which are broken down into line segments for testing. When this object collides with a line segment, I want it to slide along that segment (rather than stop or bounce away).
To do this, I check along my intended path for collisions, and if I find an intersection, I do a new test from that intersection point along the path of the line segment I've collided with, with the magnitude of the remainder of movement.
The problem arises when we slide along a line segment into a "pocket". Often times, the collision check will pass on both of the line segments that form the pocket, and the object will slip through. Because I'm travelling parallel to one of the line segments, and I'm intersecting with both line segments at an end points, I believe this issue is caused by floating point error. Whether or not it slips through, is caught, or is caught once and then slips through on the second check seems to be totally random.
I'm calculating intersection using a simple algorithm I found here: https://stackoverflow.com/a/20679579/4208739, but I've tried many other algorithms as well. All exhibit the same problems.
(Vector2 is class provided by the Unity library, it just holds x and y coordinates as floats. The Vector2.Dot function just calculates the dot product).
//returns the final destination of the intended movement, given the starting position, intended direction of movement, and provided collection of line segments
//slideMax provides a hard cap on number of slides allowed before we give up
Vector2 Move(Vector2 pos, Vector2[] lineStarts, Vector2[] lineEnds, Vector2 moveDir, int slideMax)
{
int slideCount = 0;
while (moveDir != Vector2.zero && slideCount < slideMax)
{
pos = DynamicMove(pos, lineStarts, lineEnds, moveDir, out moveDir);
slideCount++;
}
return pos;
}
//returns what portion of the intended movement can be performed before collision, and the vector of "slide" that the object should follow, if there is a collision
Vector2 DynamicMove(Vector2 pos, Vector2[] lineStarts, Vector2[] lineEnds, Vector2 moveDir, out Vector2 slideDir)
{
slideDir = Vector2.zero;
float moveRemainder = 1f;
for (int i = 0; i < lineStarts.Length; i++)
{
Vector2 tSlide;
float rem = LineProj(pos, moveDir, lineStarts[i], lineEnds[i], out tSlide);
if (rem < moveRemainder)
{
moveRemainder = rem;
slideDir = tSlide;
}
}
return pos + moveDir * moveRemainder;
}
//Calculates point of collision between the intended movement and the passed in line segment, also calculate vector of slide, if applicable
float LineProj(Vector2 pos, Vector2 moveDir, Vector2 lineStart, Vector2 lineEnd, out Vector2 slideDir)
{
slideDir = new Vector2(0, 0);
float start = (lineStart.x - pos.x) * moveDir.y - (lineStart.y - pos.y) * moveDir.x;
float end = (lineEnd.x - pos.x) * moveDir.y - (lineEnd.y - pos.y) * moveDir.x;
if (start < 0 || end > 0)
return 1;
//https://stackoverflow.com/a/20679579/4208739
//Uses Cramer's Rule
float L1A = -moveDir.y;
float L1B = moveDir.x;
float L1C = -(pos.x *(moveDir.y + pos.y) - (moveDir.x + pos.x)*pos.y);
float L2A = lineStart.y - lineEnd.y;
float L2B = lineEnd.x - lineStart.x;
float L2C = -(lineStart.x * lineEnd.y - lineEnd.x * lineStart.y);
float D = L1A * L2B - L1B * L2A;
float Dx = L1C * L2B - L1B * L2C;
float Dy = L1A * L2C - L1C * L2A;
if (D == 0)
return 1;
Vector2 inter = new Vector2(Dx / D, Dy / D);
if (Vector2.Dot(inter - pos, moveDir) < 0)
return 1;
float t = (inter - pos).magnitude / moveDir.magnitude;
if (t > 1)
return 1;
slideDir = (1 - t) * Vector2.Dot((lineEnd - lineStart).normalized, moveDir.normalized) * (lineEnd - lineStart).normalized;
return t;
}
Is there some way to calculate collision that isn't susceptible to this sort of problem? I imagine I can't totally eradicate floating point error, but is there a way to check that will at least guarantee I collide with ONE of the two line segments at the pocket? Or is there something more fundamentally wrong with going about things in this way?
If anything is unclear I can draw diagrams or write up examples.
EDIT: Having reflected on this issue more, and in response to Eric's answer, I'm wondering if converting my math from floating point to fixed point could solve the issue? In practice I'd really just be converting my values (which can fit comfortably in the range of -100 to 100) to ints, and then performing the math under those constraints? I haven't pieced all the issues together quite yet, but I might give that a try. If anyone has any information about anything like that, I'd be appreciative.
You have a line that, ideally, is aimed exactly at a point, the endpoint of a segment. That means any error in calculation, no matter how small, could say the line misses the point. I see three potential solutions:
Analyze the arithmetic and design it to ensure it is done with no error, perhaps by using extended-precision techniques.
Analyze the arithmetic and design it to ensure it is done with a slight error in favor of collision, perhaps by adding a slight bias toward collision.
Extend the line segment slightly.
It seems like the third would be easiest—the two line segments forming a pocket could just be extended by a bit, so they cross. Then the sliding path would not be aimed at a point; it would be aimed at the interior of a segment, and there would be margin for error.
I am looking to generate some 3D trajectory data for an aircraft simulation.
The idea is that the aircraft takes off at some location x and continues to ascend at some average ascent velocity a_v and angle a_theta until it reaches a maximum altitude m_a. The aircraft would then continue at its m_a until it reaches a certain distance d_d from its destination, at which point it will begin its descent at some angle d_theta with an average descent velocity of d_v. Finally, the aircraft lands at destination y.
I would like the function to return a list of 3D points.
I am looking to implement this in either Python (preferred) or C#.
For illustration purposes:
Does anyone know how I can achieve this? Is there perhaps some open source project which does this? I have been looking for a while now, but have not found anything.
I recommend you to solve the problem in 2 independent steps so that the airplane does not pass through the ground :
Calculate the path on the surface of a sphere.
Interpolate the height along this path.
For 1. you can use the spherical interpolation techniques on Quaternions.
Quaternion slerp(Quaternion v0, Quaternion v1, double t) {
// Only unit quaternions are valid rotations.
// Normalize to avoid undefined behavior.
v0.normalize();
v1.normalize();
// Compute the cosine of the angle between the two vectors.
double dot = dot_product(v0, v1);
const double DOT_THRESHOLD = 0.9995;
if (fabs(dot) > DOT_THRESHOLD) {
// If the inputs are too close for comfort, linearly interpolate
// and normalize the result.
Quaternion result = v0 + t*(v1 – v0);
result.normalize();
return result;
}
// If the dot product is negative, the quaternions
// have opposite handed-ness and slerp won't take
// the shorter path. Fix by reversing one quaternion.
if (dot < 0.0f) {
v1 = -v1;
dot = -dot;
}
Clamp(dot, -1, 1); // Robustness: Stay within domain of acos()
double theta_0 = acos(dot); // theta_0 = angle between input vectors
double theta = theta_0*t; // theta = angle between v0 and result
Quaternion v2 = v1 – v0*dot;
v2.normalize(); // { v0, v2 } is now an orthonormal basis
return v0*cos(theta) + v2*sin(theta);
}
You didn't write any code, so I won't write any either. Python with math package is more than enough to solve this problem.
Required steps:
The plane should fly on a great circle. This means you only need one distance to describe X and Y.
You could place the origin at X and specify Y with a latitude.
Calculate the tangent of the Earth at X, and rotate by a_theta. Find the point where it reaches m_a altitude.
Calculate the tangent of the Earth at Y, and rotate by d_theta. Find the point where it reaches m_a altitude.
Draw an arc between the two previous points, with a radius of EarthRadius + m_a
Every coordinate is known in the 2D of the great circle, you just need to rotate them back to 3D coordinates.
For a list of 3D points, you don't need either a_v, d_v or d_d.
I stumbled on a working concept for a fast rotation & orientation system today, based on a two-term quaternion that represents either a rotation about the X axis (1,0,0) in the form w + ix, a rotation about the Y axis (0,1,0) in the form w + jy, or a rotation about the Z axis (0,0,1) in the form w + kz.
They're similar to complex numbers, but a) are half-angled and double-sided like all quaternions (they're simply quaternions with two of three imaginary terms zeroed out), and b) represent rotations about one of three 3D axes specifically.
My problem and question is...I can't find any representation of such a system online and have no idea what to search for. What are these complex numbers called? Who else has done something like this before? Where can I find more information on the path I'm headed down? It seems too good to be true and I want to find the other shoe before it drops on me.
Practical example I worked out (an orientation quaternion from Tait-Bryan angles):
ZQuat Y, YQuat P, XQuat R; // yaw, pitch, roll
float w = Y.W * P.W;
float x = -Y.Z * P.Y;
float y = Y.W * P.Y;
float z = Y.Z * P.W;
Quaternion O; // orientation
O.W = x * R.W + w * R.X;
O.X = y * R.W + z * R.X;
O.Y = z * R.W - y * R.X;
O.Z = w * R.W - x * R.X;
Quaternions in 2D would degenerate to just being a single component being no diferrent than an rotation angle. That's propably why you do not find anything. With quaternions you do f.e. not have the problem of gimbal lock, appearing when two rotation axes align because of rotation order. In normal 2D space you do not have more than a single rotation axis, so it has neither order (how do you sort a single element) and there are no axes to align. The lack of rotation axes in 2D is because you get a rotation axis when being perpendicular to two other axes.
This gives 3 axes for 3D:
X&Y=>Z
X&Z=>Y
Y&Z=>X
But only one for 2D:
X&Y=>Z
I have successfully implemented the floor clip plane to measure the distance of left foot to the floor, which is fairly accurate. The problem I have is that as I move away from the camera (i.e. left foot Z axis is increased), the foot distance to the floor changes (increases).
Note: The floor itself is not tilted nor the Kinect stand.
I tested it with Kinect 1 and had the same result. The subject's head height (Y axis) also changes value as I move away or get closer to the camera. It does not matter of the camera is tilted or line of sight. the D value in the FloorClipPlane equation shows a constant number during the test.
A = bodyFrame.FloorClipPlane.X;
B = bodyFrame.FloorClipPlane.Y;
C = bodyFrame.FloorClipPlane.Z;
D = bodyFrame.FloorClipPlane.W;
distanceLeftFoot = A * leftFootPosX + B * leftFootPosY + C * leftFootPosZ + D;
Just to let you know, I have coordinate mapping between depth and colour. Not sure if that has anything to do with the issue.
The FloorClipPlane is expressed in hessian normal form - as explained in the docs. Specifically, your A, B, and C values compromise the unit vector from camera origin (center of the Kinect) to floor plane such that it produces a perpendicular intersection with the floor plane. D is the magnitude of that vector (distance from camera origin to floor plane).
Even if you think the floor is flat and the Kinect is parallel to the ground, you have a perspective warping problem which means the body location (measured in depth space) is going to change as you come closer and further.
To fix this you need to provide as input both your 3D coordinate values and the floor plane, which will then give you back what you want, a measured distance from floor plane to joint:
// j is your joint - left foot or any other joint
float x = j.Position.X;
float y = j.Position.Y;
float z = j.Position.Z;
float distance = (Math.Abs((x * floorPlane.X) + (y * floorPlane.Y) + (z * floorPlane.Z) + floorPlane.W))/((float)Math.Sqrt((Math.Pow(floorPlane.X,2)) + (Math.Pow(floorPlane.Y, 2)) + (Math.Pow(floorPlane.Z, 2))));
I hope this helps you. Can't elaborate further what influence your mapping from depth to color might be doing here without seeing what you are specifically doing
I know this is probably a very simple question, but I can't seem to figure it out. First of all, I want to specify that I did look over Google and SO for half an hour or so without finding the answer to my question(yes, I am serious).
Basically, I want to rotate a Vector2 around a point(which, in my case, is always the (0, 0) vector). So, I tried to make a function to do it with the parameters being the point to rotate and the angle(in degrees) to rotate by.
Here's a quick drawing showing what I'm trying to achieve:
I want to take V1(red vector), rotate it by an angle A(blue), to obtain a new vector (V2, green). In this example I used one of the simplest case: V1 on the axis, and a 90 degree angle, but I want the function to handle more "complicated" cases too.
So here's my function:
public static Vector2 RotateVector2(Vector2 point, float degrees)
{
return Vector2.Transform(point,
Matrix.CreateRotationZ(MathHelper.ToRadians(degrees)));
}
So, what am I doing wrong? When I run the code and call this function with the (0, -1) vector and a 90 degrees angle, I get the vector (1, 4.371139E-08) ...
Also, what if I want to accept a point to rotate around as a parameter too? So that the rotation doesn't always happen around (0, 0)...
Chris Schmich's answer regarding floating point precision and using radians is correct. I suggest an alternate implementation for RotateVector2 and answer the second part of your question.
Building a 4x4 rotation matrix to rotate a vector will cause a lot of unnecessary operations. The matrix transform is actually doing the following but using a lot of redundant math:
public static Vector2 RotateVector2(Vector2 point, float radians)
{
float cosRadians = (float)Math.Cos(radians);
float sinRadians = (float)Math.Sin(radians);
return new Vector2(
point.X * cosRadians - point.Y * sinRadians,
point.X * sinRadians + point.Y * cosRadians);
}
If you want to rotate around an arbitrary point, you first need to translate your space so that the point to be rotated around is the origin, do the rotation and then reverse the translation.
public static Vector2 RotateVector2(Vector2 point, float radians, Vector2 pivot)
{
float cosRadians = (float)Math.Cos(radians);
float sinRadians = (float)Math.Sin(radians);
Vector2 translatedPoint = new Vector2();
translatedPoint.X = point.X - pivot.X;
translatedPoint.Y = point.Y - pivot.Y;
Vector2 rotatedPoint = new Vector2();
rotatedPoint.X = translatedPoint.X * cosRadians - translatedPoint.Y * sinRadians + pivot.X;
rotatedPoint.Y = translatedPoint.X * sinRadians + translatedPoint.Y * cosRadians + pivot.Y;
return rotatedPoint;
}
Note that the vector arithmetic has been inlined for maximum speed.
So, what am I doing wrong? When I run the code and call this function with the (0, -1) vector and a 90 degrees angle, I get the vector (1, 4.371139E-08) ...
Your code is correct, this is just a floating point representation issue. 4.371139E-08 is essentially zero (it's 0.0000000431139), but the transformation did not produce a value that was exactly zero. This is a common problem with floating point that you should be aware of. This SO answer has some additional good points about floating point.
Also, if possible, you should stick with radians instead of using degrees. This is likely introducing more error into your computations.