I have this problem: verify if point belongs to ray in 3D. After some math research, I've coded the solution, but it seems that it just doesn't work.
That's the illustration. P is the point. E - the end-point of ray. V - directional vector of the ray.
double x, y, z, e1, e2, e3, v1, v2, v3, d, xVectorFromEToP,
dirVectorMagnitude, vectorEPMagnitude, yVectorFromEToP, zVectorFromEToP,
cpX, cpY, cpZ;
cin >> x >> y >> z >> e1 >> e2 >> e3 >> v1 >> v2 >> v3;
// HERE I'M FORMING THE EP vector - from point P to end-point E
xVectorFromEToP = x - e1;
yVectorFromEToP = y - e2;
zVectorFromEToP = z - e3;
//HERE I'M CALCULATING CROSS-PRODUCT of THE VECTORS: EP and V
cpX = ((v2 * zVectorFromEToP) - (v3 * yVectorFromEToP));
cpY = ((v1 * zVectorFromEToP) - (v3 * xVectorFromEToP)) * -1;
cpZ = ((v1 * yVectorFromEToP) - (v2 * xVectorFromEToP));
// HERE I'M CALCULATING MAGNITUDES OF THOSE VECTORS AND DEBUGGING IN COUT
vectorsEpVMagnitude = sqrt(pow(cpX, 2) + pow(cpY, 2) + pow(cpZ, 2));
dirVectorMagnitude = sqrt(pow(v1, 2) + pow(v2, 2) + pow(v3, 2));
cout << "EP: " << vectorsEpVMagnitude << endl;
cout << "dir: " << dirVectorMagnitude << endl;
// final formula for calculating distance
d = vectorsEpVMagnitude / dirVectorMagnitude;
// precision is 1e-8: 1 means belong, otherwise - 0;
if (d < 1e-8) {
cout << "distance: " << d << endl;
cout << 1;
} else {
cout << "distance: " << d << endl;
cout << 0;
}
I have sample inputs: 1) P(2.0 1.0 0.0), E(2.0 1.0 1.0), V(0.0 0.0 1.0) should be 0;
2) P(2.0 1.0 0.0), E(2.0 1.0 1.0), V(0.0 0.0 -1.0) should be 1!
However both of them have distance equal to 0, while as stated they should have different distance. I would appreciate any help, clarification, etc.
Your code calculates distance to infinite line (it looks fine), so in the both cases point lies on the line (essentially it is the same line).
Edit: Note that point lies on the ray in the second case, not in the first case, as Amir Krasnic noticed in comments.
To check whether projection of P lies on the ray, calculate scalar (dot) product of EP and V and look at its sign.
If it is positive, then projection of P lies on the ray, and d = vectorsEpVMagnitude / dirVectorMagnitude; is valid result
If negative - point lies back from the ray (behind?), in this case just calculate EP length
Related
I have been reading and trying out "Ray tracing in one weekend" by Peter Shirley. Everything has been going great until the diffuse material part. Basically, instead of a diffuse material, my algorithm seems to only be casting shadows from a specific angle and I have no idea from where the problem could originate from.
I have normally been following the book step by step.
The previous sections give the correct results and the only code I have added from the last section to the diffuse material one are the functions below.
Here are the specific parts of the code for diffuse material, which basically reflect the ray into a random direction, chosen from a sphere that is tangent to the collision point (Sorry if my explanation isn't clear enough).
This is the function that take a random point from a sphere tangent to the collision point.
vec3 random_in_unitSphere(){
vec3 p;
std::default_random_engine generator;
std::uniform_real_distribution<float> distribution(0.0, 1.0);
do{
p = 2.0*vec3(distribution(generator),distribution(generator),distribution(generator)) - vec3(1,1,1);
}while (p.squared_length() >= 1.0);
return p;
}
This is the function that calculate the color of a pixel (By casting rays until it hits nothing)
vec3 color(const Ray& r,Hitable *world){
hit_record rec;
if(world->hit(r,0.0,FLT_MAX,rec)){
vec3 target = rec.p + rec.normal + random_in_unitSphere();
return 0.5*color(Ray(rec.p,target-rec.p),world);
}
else{
vec3 unit_direction = unit_vector(r.direction());
float t = 0.5*(unit_direction.y() + 1.0);
return (1.0-t)*vec3(1.0,1.0,1.0) + t*vec3(0.5,0.7,1.0);
}
}
And this is the loop responsible for casting rays for every pixel of the image.
for(int j = ny-1 ; j >= 0 ; j--){
for(int i = 0; i < nx ; i++){
vec3 col(0,0,0);
for(int s = 0; s < ns ; s++){
float u = float(i+ distribution(generator)) / float(nx);
float v = float(j+ distribution(generator)) / float(ny);
Ray r = camera.getRay(u,v);
vec3 p = r.pointAt(2.0);
col += color(r,world);
}
col /= float(ns);
int ir = int (255.99*col.r());
int ig = int (255.99*col.g());
int ib = int (255.99*col.b());
outfile<< ir << " " << ig << " " << ib << std::endl;
}
}
Here is the expected output : https://imgur.com/im5HNEK
And here is what I get : https://imgur.com/heNjEVV
Thanks !
The problem is simply that every time you generate a random vector, you're using a new, default-initialized psuedorandom number generator. A random number generator contains some state, and this state needs to be preserved in order to see different results over time.
To fix this, simply make your random number generator static in one way or another:
vec3 random_in_unitSphere(){
vec3 p;
static std::default_random_engine generator{std::random_device{}()};
std::uniform_real_distribution<float> distribution(0.0, 1.0);
do{
p = 2.0*vec3(distribution(generator),distribution(generator),distribution(generator)) - vec3(1,1,1);
}while (p.squared_length() >= 1.0);
return p;
}
Here, I've also used std::random_device to (possibly) add some real-world randomness to the generator.
Random direction function looks wrong to me. It looks like it supposed to produce three directional cosines (wx, wy, wz) which are uniform on the sphere with radius=1, such that
wx2+wy2+wz2 = 1
First problem: you construct random engine each time you are entering the function, thus all your values are the same. I just put it in Visual Studio 2017, C++14.1, x64, Win10, and two calls produced
-0.383666 -0.804919 0.0944412
-0.383666 -0.804919 0.0944412
Second problem - it is not a random dimension, length is not equal to 1.
UPDATE
Following Wolfram article http://mathworld.wolfram.com/SpherePointPicking.html, here is the code which fix both problems - it does have RNG as parameter, so state would change. And second, point is now properly sampled on the unit sphere, and could be used as random direction.Just replace tuple with vec3
#include <iostream>
#include <random>
#include <tuple>
std::tuple<float,float,float> random_in_unitSphere(std::mt19937& rng) {
std::uniform_real_distribution<float> distribution{};
float x1, x2, l;
do {
x1 = 2.0f * distribution(rng) - 1.0f;
x2 = 2.0f * distribution(rng) - 1.0f;
l = x1 * x1 + x2 * x2;
} while (l >= 1.0f);
float s = sqrt(1.0f - l);
return std::make_tuple(2.0f*x1*s, 2.0f*x2*s, 1.0f - 2.0f*l);
}
int main() {
std::mt19937 rng{ 987654321ULL };
float wx, wy, wz, squared_length;
std::tie(wx, wy, wz) = random_in_unitSphere(rng);
std::cout << wx << " " << wy << " " << wz << '\n';
squared_length = wx * wx + wy * wy + wz * wz;
std::cout << squared_length << '\n';
std::tie(wx, wy, wz) = random_in_unitSphere(rng);
std::cout << wx << " " << wy << " " << wz << '\n';
squared_length = wx * wx + wy * wy + wz * wz;
std::cout << squared_length << '\n';
return 0;
}
UPDATE II
Second problem is that you generated points uniform INSIDE the unit sphere. So problem is not with directions - your wx, wy, wz are good wrt direction, but with length of you direction vector. Typical raytracing code is like that (in some pseudocode)
auto [x0,y0,z0] = start_new_ray();
auto [wx,wy,wz] = sample_direction();
float path = compute_path_in_geometry(x0,y0,z0,wx,wy,wz); // compute path from start point 0 in the wx,wy,wz direction to next object
// move ray to new surface
x1 = x0 + wx*path;
y1 = y0 + wy*path;
z1 = z0 + wz*path;
// do scattering, illumination, ... at (x1,y1,z1)
If (wx,wy,wz) length is not 1, then length computed as sqrt((x1-x0)2 + (y1-y0)2+(z1-z0)2) WON'T BE equal to path. Your basic geometry rules just breaks.
#include <cmath> \\not sure if I need cmath
#include <iostream>
using namespace std;
this while loop serves to loop the " enter number of terms to approximate.
while (a != 0)
{
here is the Leibniz formula:
double c = 0.00, d = 0.00;
for (int i = 1; i <= a)
{
if (i % 2 != 0)
{
d = 1 / (1 + 2 * (i - 1));
}
else
{
d = -1 / (1 + 2 * (i - 1));
}
c = c + d;
i = i + 1
}
cout.setf(ios::fixed);
cout.setf(ios::showpoint);
cout.precision(5);
cout << "The approximation for Leibniz's Formula is " << c << "
using "<< a <<" terms." << endl;
here is the Wallis formula:
double e = 1.00;
for (int u = 0; u<a; u++)
{
e = e * (2 * a / (2 * a - 1))*(2 * a / (2 * a + 1));
}
cout << "The approximation for Wallis' Formula is " << e << " using
"<< a <<" terms." << endl;
cout << endl;
cout << "Enter the number of terms to approximate (or zero to
quit):" << endl;
cin >> a;
}
For a=1 I am getting 1.0000 in the first formula output and 0.00000 in the second formula output
A line like this
d = 1 / (1 + 2 * (i - 1));
will use integer arithmetics to calculate the result, and then convert the int result to a double.
Change it to
d = 1.0 / (1 + 2 * (i - 1));
or even
d = 1.0 / (1.0 + 2.0 * (i - 1.0));
There are many mistakes in this code. First, comments in c++ use //, not \\.
#include <cmath> //not sure if I need cmath
You have to have two semicolons in for statements, even if you don't need loop-expression.
for (int i = 1; i <= a;)
The d will evaluate to 0 for every i that is greater than 1. You are using integer division, when you clearly want floating point division. You have to tell that to the compiler like this.
d = 1.0 / (1 + 2 * (i - 1));
When the left argument of division operator is double compiler will know, that you want to perform a floating point division. If it would be int as in your code, integer division would be performed and result converted to double.
Also in the Wallis formula you misplaced a for u, and also u parameter should start at 1, not 0. Also the integer division problem persists here.
double e = 1.00;
for (int u = 1; u<a; u++)
{
e = e * (2.0 * u / (2.0 * u - 1))*(2.0 * u / (2.0 * u + 1));
}
If you fix this all, the program starts to output valid results.
I would like to ask a very short question, and it is as follows: in finding the cube root of a number (both neg. and pos.) in C++, how does one restrict the output to real solutions only?
I am currently writing a program to solve a cubic with Cardano's formula, and one of the intermediate variables I am using randomly outputs the complex and real cube roots - and I only need the real roots.
(E.g. in evaluating the cube root of -0.0127378, the three roots would be 0.11677095+0.202253218i, −0.2335419, 0.11677095−0.202253218i - I wish to ignore the complex ones for substitution into a later formula)
Thank you!
EDIT: Solved it! :) I created a signum function and tweaked the sign after taking the power of the absolute value of SPrime and TPrime, so now it carries forward only the real cube root.
/* ... */
#include <iostream>
#include <cmath>
#include <complex>
#include <cstdio>
#include <cassert>
using namespace std;
int signum(std::complex<double> z)
{
if (z.real() < 0 || z.imag() < 0) return -1;
else if (z.real() >= 0 || z.imag() >= 0) return 1;
}
// POST: The function is intended to solve a cubic equation with coefficients a, b, c and d., such that
// ax^3 + bx^2 + cx + d = 0. If there exist infinitely many solutions, we output -1, i.e. if a=b=c=d=0
// (trivial solution).
void solve(std::complex<double> a, std::complex<double> b, std::complex<double> c, std::complex<double> d, std::complex<double>& x1, std::complex<double>& x2, std::complex<double>& x3)
{
complex<double> i = complex<double> (0, 1.0);
// Consider implementing Cardano's method for obtaining the solution of a degree 3 polynomial, as suggested
// We must hence define the discriminant D of such an equation through complex doubles Q and R
std::complex<double> Q;
Q = (3.0*a*c - pow(b, 2)) / (9.0*pow(a, 2));
cout << "Q=" << Q << endl;
std::complex<double> R;
R = (9.0*a*b*c - 27.0*d*pow(a, 2) - 2.0*pow(b, 3)) / (54.0*pow(a, 3));
cout << "R=" << R << endl;
std::complex<double> D;
D = pow(Q, 3) + pow(R, 2);
// Possible types of output for discriminant
if (abs(D) < 0.0)
{
cout << "The cubic has three distinct, real roots." << endl;
}
else if (abs(D) == 0.0)
{
cout << "The cubic has three real roots, at least two of which are equal." << endl;
}
else if (abs(D) > 0.0)
{
cout << "The cubic has one real root and two complex conjugate roots." << endl;
}
// Defining two further complex double variables S and T, which are required to obtain the final solution for x1, x2 and x3
std::complex<double> S;
std::complex<double> SPrime;
SPrime = R+sqrt(Q*Q*Q + R*R);
cout << "SPrime=" << SPrime << endl;
if (signum(SPrime) == -1)
{
S = (-1)*pow(abs(SPrime), 0.3333333333333);
}
else if (signum(SPrime) == 1)
{
S = pow(abs(SPrime), 0.3333333333333);
}
cout << "S=" << S << endl;
std::complex<double> T;
std::complex<double> TPrime;
TPrime = (R-sqrt(Q*Q*Q + R*R));
if (signum(TPrime) == -1)
{
T = (-1)*pow(abs(TPrime), 0.3333333333333);
}
else if (signum(TPrime) == 1)
{
T = pow(abs(TPrime), 0.3333333333333);
}
cout << "T=" << T << endl;
cout << "TPrime= " << TPrime << endl;
// Expressions for the solutions
x1 = S + T - (b/(3.0*a));
x2 = (-0.5)*(S + T) - (b/(3.0*a)) + (sqrt(3.0)*0.5)*(S - T)*i;
x3 = conj(x2);
if (abs(x1) < 0.000000000001)
{
x1 = 0;
}
}
// Driver code
int main ()
{
// Taking user input for a, b, c and d
std::complex<double> a, b, c, d, x1, x2, x3;
cout << "Please enter the coefficients of the polynomial in successive order." << endl;
cin >> a >> b >> c >> d;
solve (a, b, c, d, x1, x2, x3);
cout << x1 << ", " << x2 << ", " << x3 << "." << endl;
return 0;
}
The problem as you're stating it can be solved trivially (with real numbers the cubic root of -x is the opposite of the cubic root of x):
double cuberoot(double x) {
if (x < 0) {
return -pow(-x, 1.0/3.0);
} else if (x > 0) {
return pow(x, 1.0/3.0);
} else {
return 0;
}
}
If the input is instead in general complex z and you're looking for the "most real" (principal) cubic root the same reasoning can be applied using complex pow version to either z or -z depending on the sign of the real part:
std::complex<double> cuberoot(std::complex<double> z) {
if (z.real() < 0) {
return -pow(-z, 1.0/3.0);
} else {
return pow(z, 1.0/3.0);
}
}
Problems with your code:
As you allow complex coefficients, the discussion of the discriminant becomes slightly meaningless, it is only of value for real coefficients.
abs(D) is always non-negative. If D==0, then there is a double root, more can not be said in the case of complex coefficients.
You can avoid a lot of code by utilizing that S*T=-Q. One would have to care that the computation of u=T^3 returns the larger of the roots of 0==u^2 - 2*R*u - Q^3 or (u-R)^2 = D = R^2+Q^3
rtD = sqrt(D);
T = cuberoot( R + (abs(R+rtD)>=abs(R-rtD)) ? rtD : -rtD );
S = (abs(T)<epsilon) ? 0 : -Q/T;
Because of abs(R)<=abs(T)^3 and abs(D)<=abs(T)^6
one gets also abs(Q)<=2^(1/3)*abs(T)^2 resulting in
abs(S)=abs(Q/T) <= 2^(1/3)*abs(T)
For S=-Q/T to fail one would thus need a serious case
of extremely small floating point numbers in R, Q
and thus T. Quantitatively, for double even
the threshold epsilon=1e-150 should be safe.
On cube root variants:
For esthetic reasons one might want T as close to a coordinate axis as possible. A cube root function achieving this would be
std::complex<double> cuberoot(std::complex<double> z) {
double r=abs(z), phi=arg(z);
double k = round(2*phi/pi);
// closest multiple of pi/2
// an equivalent angle is (phi-k*pi/2) - k*3*pi/2
return std::polar( pow(r,1.0/3), (phi-k*pi/2)/3 - k*pi/2 );
}
so that abs(phi-k*pi/2)<=pi/4, and thus the angle to the next coordinate axis of the cube root is smaller than pi/12=15°. cuberoot(i) returns -i, cuberoot(-1) returns -1, a point at 60° returns a cube root at (60°-90°)/3-90°=-100°, etc.
I've been attempting to build a Runge Kutta fourth order integrator to model simple projectile motion. My code is as follows
double rc4(double initState, double (*eqn)(double,double),double now,double dt)
{
double k1 = eqn(initState,now);
double k2 = eqn(initState + k1*dt/2.0,now + dt/2.0);
double k3 = eqn(initState + k2*dt/2.0,now + dt/2.0);
double k4 = eqn(initState + k3*dt, now + dt);
return initState + (dt/6.0) * (k1 + 2*k2 + 2*k3 + k4);
}
This is called within a while loop
while (time <= duration && yPos >=0)
{
xPos = updatePosX(xPos,vx,timeStep);
yPos = updatePosY(yPos,vy,timeStep);
vx = rc4(vx,updateVelX,time,timeStep);
vy = rc4(vy,updateVelY,time,timeStep);
cout << "x Pos: " << xPos <<"\t y Pos: " << yPos << endl;
time+=timeStep;
myFile << xPos << " " << yPos << " " << vx << " " << vy << endl;
}
However, contrary to what should happen my results simply blow up. What's going on here?
Your rk4 code looks right. But only for scalar differential equations.
What you most certainly have is a system of coupled differential equations in a dimension greater than 1. Here you have to apply the integration method in its vector form. That is, x,y,vx,vy are combined into a 4 dimensional (phase) state vector and the system function is vector valued, k1,...k4 are vectors etc.
As an advanced note, time <= duration is sensible to rounding errors accumulated in the repetitions of time+=timeStep;. Better use time <= duration-timeStep/2 to have time at the end of the loop close to duration.
Reading the code on the closed previous question I see that you have problems with the idea of a differential equation. You should not use the result of the Euler step as acceleration in the RK4 implementation. The system for ballistic motion without air friction is
dotx = vx
doty = vy
dotvx = 0
dotvy = -g
which you would have to implement in vector form as something like
eqn(t, [x,y,vx,vy]) // where X = array of double of dimension 4
{ return [vx,vy,0,-g]; }
Implementing this simple root-finding algorithm.
http://en.wikipedia.org/wiki/Durand%E2%80%93Kerner_method
I cannot for the life of me figure out what's wrong with my implementation. The roots keep blowing up and no sign of convergence. Any suggestions?
Thanks.
#include <iostream>
#include <complex>
using namespace std;
typedef complex<double> dcmplx;
dcmplx f(dcmplx x)
{
// the function we are interested in
double a4 = 3;
double a3 = -3;
double a2 = 1;
double a1 = 0;
double a0 = 100;
return a4 * pow(x,4) + a3 * pow(x,3) + a2 * pow(x,2) + a1 * x + a0;
}
int main()
{
dcmplx p(.9,2);
dcmplx q(.1, .5);
dcmplx r(.7,1);
dcmplx s(.3, .5);
dcmplx p0, q0, r0, s0;
int max_iterations = 20;
bool done = false;
int i=0;
while (i<max_iterations && done == false)
{
p0 = p;
q0 = q;
r0 = r;
s0 = s;
p = p0 - f(p0)/((p0-q0)*(p0-r0)*(p0-s0));
q = q0 - f(q0)/((q0-p)*(q0-r0)*(q0-s0));
r = r0 - f(r0)/((r0-p)*(r0-q)*(r0-s0));
s = s0 - f(s0)/((s0-p)*(s0-q)*(s0-r));
// if convergence within small epsilon, declare done
if (abs(p-p0)<1e-5 && abs(q-q0)<1e-5 && abs(r-r0)<1e-5 && abs(s-s0)<1e-5)
done = true;
i++;
}
cout<<"roots are :\n";
cout << p << "\n";
cout << q << "\n";
cout << r << "\n";
cout << s << "\n";
cout << "number steps taken: "<< i << endl;
return 0;
}
A half year late: The solution to the enigma is that the denominator should be an approximation of the derivative of the polynomial, and thus needs to contain the leading coefficient a4 as factor.
Alternatively, one can divide the polynomial value by a4 in the return statement, so that the polynomial is effectively normed, i.e., has leading coefficient 1.
Note that the example code in wikipedia by Bo Jacoby is the Seidel-type variant of the method, the classical formulation is the Jordan-like method where all new approximations are simultaneously computed from the old approximation. Seidel can have faster convergence than the order 2 that the formulation as a multidimensional Newton method provides for Jacobi.
However, for large degrees Jacobi can be accelerated using fast polynomial multiplication algorithms for the required multi-point evaluations of polynomial values and the products in the denominators.
Ah, the problem was that the coefficients of an N-degree polynomial have to be specified as
1*x^N + a*x^(N-1) + b*x^(N-2) ... etc + z;
where 1 is the coefficient of the largest degree term. Otherwise the first root will never converge.
You haven't implemented for formulae correctly. For instance
s = s0 - f(s0)/((s0-p0)*(s0-q0)*(s0-r0));
should be
s = s0 - f(s0)/((s0-p)*(s0-q)*(s0-r));
Look again at the wiki article