Weird raytracing behavior for diffuse materials - c++

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.

Related

Why doesn't my Gradient descent algorithm converge? (For Logistic Regression)

I have an assignment which says to implement logistic regression in c++ using gradient descent. Part of the assignment is to make the gradient descent stop when the magnitude of the gradient is below 10e-07.
I have to minimize: //chart.googleapis.com/chart?cht=tx&chl=L(w)%20%3D%20%5Cfrac%7B1%7D%7BN%7D%5Csum%20log(1%20%2B%20exp(-y_%7Bi%7Dw%5E%7BT%7Dx_%7Bi%7D))
However my gradient descent keeps stopping due to max iterations surpassed. I have tried with various max iteration thresholds, and they all max out. I think there is something wrong with my code, since logistic regression is supposedly an easy task for gradient descent due to the concave nature of its cost function, the gradient descent should easily find the minium.
I am using the armadillo library for matrices and vectors.
#include "armadillo.hpp"
using namespace arma;
double Log_Likelihood(Mat<double>& x, Mat<int>& y, Mat<double>& w)
{
Mat<double> L;
double L_sum = 0;
for (int i = 0; i < x.n_rows; i++)
{
L = log(1 + exp(-y[i] * w * x.row(i).t() ));
L_sum += as_scalar(L);
}
return L_sum / x.n_rows;
}
Mat<double> Gradient(Mat<double>& x, Mat<int>& y, Mat<double>& w)
{
Mat<double> grad(1, x.n_cols);
for (int i = 0; i < x.n_rows; i++)
{
grad = grad + (y[i] * (1 / (1 + exp(y[i] * w * x.row(i).t()))) * x.row(i));
}
return -grad / x.n_rows;
}
void fit(Mat<double>& x, Mat<int>& y, double alpha = 0.05, double threshold = pow(10, -7), int maxiter = 10000)
{
w.set_size(1, x.n_cols);
w = x.row(0);
int iter = 0;
double log_like = 0;
while (true)
{
log_like = Log_Likelihood(x, y, w);
if (iter % 1000 == 0)
{
std::cout << "Iter: " << iter << " -Log likelihood = " << log_like << " ||dL/dw|| = " << norm( Gradient(x, y, w), 2) << std::endl;
}
iter++;
if ( norm( Gradient(x, y, w), 2) < threshold)
{
std::cout << "Magnitude of gradient below threshold." << std::endl;
break;
}
if (iter == maxiter)
{
std::cout << "Max iterations surpassed." << std::endl;
break;
}
w = w - (alpha * Gradient(x, y, w));
}
}
I want the gradient descent to stop because the magnitude of the gradient falls below 10e-07.
My labels are {1, -1}.
Verify that your loglikelihood is increasing towards convergence by recording or plotting the values at every iteration, and also check that the norm of the gradient is going towards 0. You should be doing gradient ascent, so add the gradient instead of subtracting it. If the norm of the gradient consistently increases it means you are not going in a direction towards the optimum. If on the other hand, the norm of the gradient "jumps around" but doesn't go to 0, then you should reduce your stepsize/learning rate alpha and try again.
Plotting and analyzing these values will be helpful to debug and analyze your algorithm.

Tell if point belongs to ray in 3D space

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

Error with min/max arguments of uniform_real_distribution c++

I have a function to generate a (pseudo) random walk on a square lattice where the walk should not breach the boundaries of this square, full function below:
/**
* #brief Performs a single random walk returning the final distance from the origin
*
* Completes a random walk on a square lattice using the mersenne twister engine based pseudo-random
* number-generator (PRNG). The walk will not breach the boundaries of the square size provided to
* the function. The random walk starts at the origin and ends after some parameterised number of steps.
* Position co-ordinates of the walk for each iteration are sent to an output file.
*
* #param squareSideLength Length of square lattice side
* #param steps Number of steps to compute random walk up to
* #param engine Mersenne Twister engine typedef (used for generating random numbers locally)
* #param distribution Default distribution of random walk
* #param outputFile [Default nullptr] Pointer to file to write co-ordinate data of random walk to
* #return final distance of the particle from the origin
*/
double randomWalkSquareLattice(int squareSideLength, int steps, std::mt19937& engine, std::uniform_real_distribution<double>& distribution, std::ofstream* outputFile = nullptr) {
// store the half-length of the square lattice
const int halfSquareLength = squareSideLength / 2;
// initialise co-ordinates to the origin
double positionX = 0.0;
double positionY = 0.0;
// assign the default distribution to distDefault
std::uniform_real_distribution<double> distDefault = distribution;
// loop over a number of iterations given by the steps parameter
for (int i = 0; i < steps; i++) {
std::cout << positionX << "\t" << positionY << std::endl;
// if the x-position of the particle is >= to positive
// half square lattice length then generate decremental
// random number (avoiding breaching the boundary)
if (positionX >= halfSquareLength) {
double offset = positionX - halfSquareLength;
std::cout << std::endl << offset << std::endl;
std::uniform_real_distribution<double> distOffset(-offset, -1.0);
positionX += distOffset(engine);
}
// else if the x-position of the particle is <= to negative
// half square lattice length then generate incremental random
// number (avoiding breaching the boundary)
else if (positionX <= -halfSquareLength) {
double offset = std::abs(positionX + halfSquareLength);
std::cout << std::endl << offset << std::endl;
std::uniform_real_distribution<double> distOffset(offset, 1.0);
positionX += distOffset(engine);
}
// else (in case where x-position of particle is not touching
// the lattice boundary) generate default random number
else {
positionX += distDefault(engine);
}
// if the y-position of the particle is >= to positive
// half square lattice length then generate decremental
// random number (avoiding breaching the boundary)
if (positionY >= halfSquareLength) {
double offset = positionY - halfSquareLength;
std::cout << std::endl << offset << std::endl;
std::uniform_real_distribution<double> distOffset(-offset, -1.0);
positionY += distOffset(engine);
}
// else if the y-position of the particle is <= to negative
// half square lattice length then generate incremental
// random number (avoiding breaching the boundary)
else if (positionY <= -halfSquareLength) {
double offset = std::abs(positionY + halfSquareLength);
std::cout << std::endl << offset << std::endl;
std::uniform_real_distribution<double> distOffset(offset, 1.0);
positionY += distOffset(engine);
}
// else (in case where y-position of particle is not touching
// the lattice boundary) generate default random number
else {
positionY += distDefault(engine);
}
// if an outputFile is supplied to the function, then write data to it
if (outputFile != nullptr) {
*outputFile << positionX << "\t" << positionY << std::endl;
}
}
// compute final distance of particle from origin
double endDistance = std::sqrt(positionX*positionX + positionY*positionY);
return endDistance;
}
Where the conditionals seen in the method prevent the walk exiting the boundaries. However, when this is called with a sufficient number of steps (so that any one of these conditionals is executed) I get an error saying:
invalid min and max arguments for uniform_real
Note that the dist I send to this function is:
std::uniform_real_distribution<double> dist(-1.0,1.0);
And so (as you can see from the values printed to the terminal) the issue is not that the offset will ever be larger than the max value given to the distOffset in any of the conditional cases.
Is the issue that I cannot give u_r_d a double value of arbitrary precision? Or is something else at play here that I am missing?
Edit: I should add that these are the values used in main():
int main(void) {
std::uniform_real_distribution<double> dist(-1.0, 1.0);
std::random_device randDevice;
std::mt19937 engine(randDevice());
//std::cout << dist(engine) << std::endl;
// Dimensions of Square Lattice
const int squareLength = 100;
// Number of Steps in Random Walk
const int nSteps = 10000;
randomWalkSquareLattice(squareLength, nSteps, engine, dist);
}
uniform_real_distribution(a,b); requires that a ≤ b.
If positionX == halfSquareLength, then,
double offset = positionX - halfSquareLength;
is the same as saying
double offset = positionX - positionX;
and offset will be zero.
This results in
std::uniform_real_distribution<double> distOffset(-0.0, -1.0);
and violates a ≤ b.
Here is the solution I came up with, seems to work for all test cases so far:
/**
* #brief Performs a single random walk returning the final distance from the origin
*
* Completes a random walk on a square lattice using the mersenne twister engine based pseudo-random
* number-generator (PRNG). The walk will not breach the boundaries of the square size provided to
* the function. The random walk starts at the origin and ends after some parameterised number of steps.
* Position co-ordinates of the walk for each iteration are sent to an output file.
*
* #param squareSideLength Length of square lattice side
* #param steps Number of steps to compute random walk up to
* #param engine Mersenne Twister engine typedef (used for generating random numbers locally)
* #param distribution Default distribution of random walk
* #param outputFile [Default nullptr] Pointer to file to write co-ordinate data of random walk to
* #return final distance of the particle from the origin
*/
double randomWalkSquareLattice(int squareSideLength, int steps, std::mt19937& engine, std::uniform_real_distribution<double>& distribution, std::ofstream* outputFile = nullptr) {
// store the half-length of the square lattice
const int halfSquareLength = squareSideLength / 2;
// initialise co-ordinates to the origin
double positionX = 0.0;
double positionY = 0.0;
// assign the default distribution to distDefault
std::uniform_real_distribution<double> distDefault = distribution;
std::uniform_real_distribution<double> distBound(0.0, 1.0);
double oS;
// loop over a number of iterations given by the steps parameter
for (int i = 0; i < steps; i++) {
//std::cout << positionX << "\t" << positionY << std::endl;
positionX += distDefault(engine);
positionY += distDefault(engine);
// if the x-position of the particle is >= to positive
// half square lattice length then generate decremental
// random number (avoiding breaching the boundary)
if (positionX >= halfSquareLength) {
oS = distBound(engine);
double offset = positionX - halfSquareLength;
double desiredOffset = -(oS + offset);
if (desiredOffset < -1.0) {
double offsetFromNegUnity = desiredOffset + 1.0;
desiredOffset -= offsetFromNegUnity;
}
positionX += desiredOffset;
}
// else if the x-position of the particle is <= to negative
// half square lattice length then generate incremental random
// number (avoiding breaching the boundary)
else if (positionX <= -halfSquareLength) {
oS = distBound(engine);
double offset = std::abs(positionX + halfSquareLength);
double desiredOffset = offset+oS;
if (desiredOffset > 1.0) {
double offsetFromUnity = desiredOffset - 1.0;
desiredOffset -= offsetFromUnity;
}
positionX += desiredOffset;
}
// if the y-position of the particle is >= to positive
// half square lattice length then generate decremental
// random number (avoiding breaching the boundary)
if (positionY >= halfSquareLength) {
oS = distBound(engine);
double offset = positionY - halfSquareLength;
double desiredOffset = -(offset+oS);
if (desiredOffset < -1.0) {
double offsetFromNegUnity = desiredOffset + 1.0;
desiredOffset -= offsetFromNegUnity;
}
positionY += desiredOffset;
}
// else if the y-position of the particle is <= to negative
// half square lattice length then generate incremental
// random number (avoiding breaching the boundary)
else if (positionY <= -halfSquareLength) {
oS = distBound(engine);
double offset = std::abs(positionY + halfSquareLength);
double desiredOffset = offset+oS;
if (desiredOffset > 1.0) {
double offsetFromUnity = desiredOffset - 1.0;
desiredOffset -= offsetFromUnity;
}
positionY += desiredOffset;
}
// if an outputFile is supplied to the function, then write data to it
if (outputFile != nullptr) {
*outputFile << positionX << "\t" << positionY << std::endl;
}
}
// compute final distance of particle from origin
double endDistance = std::sqrt(positionX*positionX + positionY*positionY);
return endDistance;
}
Here, an offset was generated randomly on the interval (0,1) and the difference from the boundary by which a x or y position breached was added to this offset to create a double value which would have a minimum of this breaching difference and (after an additional nested conditional check) a maximum of 1.0 (or -1.0 for opposite boundary).

Exploding Runge Kutta Method

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]; }

Simulation of a point mass in a box (3D space)

I would like to simulate a point mass within a closed box. There is no friction and the point mass obeys the impact law. So there are only elastic collisions with the walls of the box. The output of the program is the time, position (rx,ry ,rz) and velocity (vx,vy,vz). I plot the trajectory by using GNUplot.
The problem I have now is, that the point mass gets energy from somewhere. So their jumps get each time more intense.
Is someone able to check my code?
/* Start of the code */
#include <iostream>
#include <cmath>
#include <iomanip>
using namespace std;
struct pointmass
{
double m; // mass
double r[3]; // coordinates
double v[3]; // velocity
};
// Grav.constant
const double G[3] = {0, -9.81, 0};
int main()
{
int Time = 0; // Duration
double Dt = 0; // Time steps
pointmass p0;
cerr << "Duration: ";
cin >> Time;
cerr << "Time steps: ";
cin >> Dt;
cerr << "Velocity of the point mass (vx,vy,vz)? ";
cin >> p0.v[0];
cin >> p0.v[1];
cin >> p0.v[2];
cerr << "Initial position of the point mass (x,y,z)? ";
cin >> p0.r[0];
cin >> p0.r[1];
cin >> p0.r[2];
for (double i = 0; i<Time; i+=Dt)
{
cout << i << setw(10);
for (int j = 0; j<=2; j++)
{
////////////position and velocity///////////
p0.r[j] = p0.r[j] + p0.v[j]*i + 0.5*G[j]*i*i;
p0.v[j] = p0.v[j] + G[j]*i;
///////////////////reflection/////////////////
if(p0.r[j] >= 250)
{
p0.r[j] = 500 - p0.r[j];
p0.v[j] = -p0.v[j];
}
else if(p0.r[j] <= 0)
{
p0.r[j] = -p0.r[j];
p0.v[j] = -p0.v[j];
}
//////////////////////////////////////////////
}
/////////////////////Output//////////////////
for(int j = 0; j<=2; j++)
{
cout << p0.r[j] << setw(10);
}
for(int j = 0; j<=2; j++)
{
cout << p0.v[j] << setw(10);
}
///////////////////////////////////////////////
cout << endl;
}
}
F = ma
a = F / m
a dt = F / m dt
a dt is acceleration over a fixed time - the change in velocity for that frame.
You are setting it to F / m i
it is that i which is wrong, as comments have suggested. It needs to be the duration of a frame, not the duration of the entire simulation so far.
I am a little concerned about the time loop along with other commenters - make sure that it represents an increment of time, not a growing duration.
Still, I think the main problem is you are changing the sign of all three components of velocity
on reflection.
That's not consistent with the laws of physics -conservation of linear momentum and energy - at the boundaries.
To see this, consider the case if your particle is moving in just the x-y plane (velocity in z is zero) and about to hit the wall at x= L.
The collision looks like this:
The force exerted on the point mass by the wall acts perpendicular to the wall. So there is no change in the momentum component of the particle parallel to the wall.
Applying conservation of linear momentum and kinetic energy, and assuming a perfectly elastic collision, you will find that
The component of velocity perpendicular to the wall DOES change sign
The component of velocity parallel to the wall DOES NOT change sign
In three dimensions, to have an accurate simulation, you have to work out the momentum components parallel and perpendicular to the wall on collision and code the resulting velocity changes.
In other words, this code:
///////////////////reflection/////////////////
if(p0.r[j] >= 250)
{
p0.r[j] = 500 - p0.r[j];
p0.v[j] = -p0.v[j];
}
else if(p0.r[j] <= 0)
{
p0.r[j] = -p0.r[j];
p0.v[j] = -p0.v[j];
}
//////////////////////////////////////////////
does not model the physics of reflection correctly. To fix it here is an outline of what to do:
Take the reflection checks out of the loop over x,y,z coordinates (but still within the time loop)
The collision condition for all six walls needs to be checked,
according to the direction of the normal vector to the wall.
For example for the right wall of the cube defined by X=250, 0<=Y<250, 0<=Z<250, the normal vector is in the negative X direction. For the left wall defined by X=0, 0<=Y<250, 0<=Z<250, the normal vector is in the positive X direction.
So on reflection from those two walls, the X component of velocity changes sign because it is normal (perpendicular) to the wall, but the Y and Z components do NOT change sign because they are parallel to the wall.
Apply similar considerations at the top and bottom wall (constant Y), and front and back wall (constant Z), of the cube -left as exercise to work out the normals to those surfaces.
Finally you shouldn't change sign of the position vector components on reflection, just the velocity vector. Instead recompute the next value of the position vector given the new velocity.
OK, so there are a few issues. The others have pointed out the need to use Dt rather than i for the integration step.
However, you are correct in stating that there is an issue with the reflection and energy conservation. I've added an explicit track of that below.
Note that the component wise computation of the reflection is actually fine other than the energy issue.
The problem was that during a reflection the acceleration due to gravity changes. In the case of the particle hitting the floor, it was acquiring kinetic energy equal to that it would have had if it had kept falling, but the new position had higher potential energy. So the energy would increase by exactly twice the potential energy difference between the floor and the new position. A bounce off the roof would have the opposite effect.
As noted below, once strategy would be to compute the actual time of reflection. However, actually working directly with energy is much simpler as well as more robust. However, please note although the the simple energy version below ensures that the speed and position are consistent, it actually does not have the correct position. For most purposes that may not actually matter. If you really need the correct position, I think we need to solve for the bounce time.
/* Start of the code */
#include <iostream>
#include <cmath>
#include <iomanip>
using namespace std;
struct pointmass
{
double m; // mass
double r[3]; // coordinates
double v[3]; // velocity
};
// Grav.constant
const double G[3] = { 0, -9.81, 0 };
int main()
{
// I've just changed the initial values to speed up unit testing; your code worked fine here.
int Time = 50; // Duration
double Dt = 1; // Time steps
pointmass p0;
p0.v[0] = 23;
p0.v[1] = 40;
p0.v[2] = 15;
p0.r[0] = 100;
p0.r[1] = 200;
p0.r[2] = 67;
for (double i = 0; i<Time; i += Dt)
{
cout << setw(10) << i << setw(10);
double energy = 0;
for (int j = 0; j <= 2; j++)
{
double oldR = p0.r[j];
double oldV = p0.v[j];
////////////position and velocity///////////
p0.r[j] = p0.r[j] + p0.v[j] * Dt + 0.5*G[j] * Dt*Dt;
p0.v[j] = p0.v[j] + G[j] * Dt;
///////////////////reflection/////////////////
if (G[j] == 0)
{
if (p0.r[j] >= 250)
{
p0.r[j] = 500 - p0.r[j];
p0.v[j] = -p0.v[j];
}
else if (p0.r[j] <= 0)
{
p0.r[j] = -p0.r[j];
p0.v[j] = -p0.v[j];
}
}
else
{
// Need to capture the fact that the acceleration switches direction relative to velocity half way through the timestep.
// Two approaches, either
// Try to compute the time of the bounce and work out the detail.
// OR
// Use conservation of energy to get the right speed - much easier!
if (p0.r[j] >= 250)
{
double energy = 0.5*p0.v[j] * p0.v[j] - G[j] * p0.r[j];
p0.r[j] = 500 - p0.r[j];
p0.v[j] = -sqrt(2 * (energy + G[j] * p0.r[j]));
}
else if (p0.r[j] <= 0)
{
double energy = 0.5*p0.v[j] * p0.v[j] - G[j] * p0.r[j];
p0.r[j] = -p0.r[j];
p0.v[j] = sqrt(2*(energy + G[j] * p0.r[j]));
}
}
energy += 0.5*p0.v[j] * p0.v[j] - G[j] * p0.r[j];
}
/////////////////////Output//////////////////
cout << energy << setw(10);
for (int j = 0; j <= 2; j++)
{
cout << p0.r[j] << setw(10);
}
for (int j = 0; j <= 2; j++)
{
cout << p0.v[j] << setw(10);
}
///////////////////////////////////////////////
cout << endl;
}
}