Problem with PathTracing ShadowRay, Spheres all black - opengl

So i'm making a raytracer in OpenGL, fully shader based, and i'm struggling to know where the problem is with my Shadow rays. If i multiply the radiance of the object by the shadowRay output, it seems like only the "edge" of the sphere is lighten up
I verified multiple times the code without finding where the problem comes from.
This is what i get:
vec3 TraceShadowRay(vec3 hitPoint, vec3 normal, Object objects[3])
{
Light pointLight;
pointLight.position = vec3(0, 80, 0);
pointLight.intensity = 2;
Ray ShadowRay;
ShadowRay.origin = hitPoint + normal * 1e-4;
ShadowRay.dir = normalize(pointLight.position - ShadowRay.origin);
ShadowRay.t = 100000;
//ShadowRay.dir = vec3(0, 1, 0);
for(int i = 0; i < 3; ++i)
{
if(objects[i].type == 0)
{
if(interectSphere(objects[i].position, objects[i].radius, ShadowRay))
{
return vec3(0);
}
}
if(objects[i].type == 1)
{
if(intersectPlane(objects[i].normal, objects[i].position, ShadowRay))
{
return vec3(0);
}
}
}
float AngleNormalShadow = dot(ShadowRay.dir, normal);
clamp(AngleNormalShadow, 0, 1);
return GetLight(ShadowRay.origin, pointLight);// * AngleNormalShadow;
}
The getLight function:
vec3 GetLight(vec3 origin, Light light)
{
return vec3(1, 1, 1) * light.intensity;
//float dist = sqrt( ((origin.x - light.position.x) * (origin.x - light.position.x)) + ((origin.y - light.position.y) * (origin.y - light.position.y)));
//return (vec3(1, 1, 1) * light.intensity) / (4 * M_PI * ((origin - light.position).length * (origin - light.position).length));
}
The intersectSphere function:
bool interectSphere(const vec3 center, float radius, inout Ray r)
{
vec3 o = r.origin;
vec3 d = r.dir;
vec3 v = o - center;
float b = 2 * dot(v, d);
float c = dot(v, v) - radius*radius;
float delta = b*b - 4 * c;
if(delta < 1e-4)
return false;
float t1 = (-b - sqrt(delta))/2;
float t2 = (-b + sqrt(delta))/2;
if(t1 < t2)
{
r.t = t1;
r.t2 = t2;
}
else if(t2 < t1)
{
r.t = t2;
r.t2 = t1;
}
r.reflectionNormal = normalize((r.origin + r.dir * r.t) - center);
return true;
}
The result expected is a nice shaded sphere with light coming from the top of the spheres

Could it be a missing negation? Looks like interectSphere() returns true when there is a collision, but the calling code in TraceShadowRay() bails out when it returns true.
old:
if(interectSphere(objects[i].position, objects[i].radius, ShadowRay))
{
return vec3(0);
}
new:
if(!interectSphere(objects[i].position, objects[i].radius, ShadowRay))
{
return vec3(0);
}

Related

Finding point inside disk defined by a radius

I have come across a shader that I am trying to understand where a point is found within a disk defined by a radius.
How does this function DiskPoint work?
float HASHVALUE = 0.0f;
vec2 RandomHashValue()
{
return fract(sin(vec2(HASHVALUE += 0.1, HASHVALUE += 0.1)) * vec2(43758.5453123, 22578.1459123));
}
vec2 DiskPoint(in float radius, in float x1, in float x2)
{
float P = radius * sqrt(1.0f - x1);
float theta = x2 * 2.0 * PI;
return vec2(P * cos(theta), P * sin(theta));
}
void main()
{
HASHVALUE = (uvCoords.x * uvCoords.y) * 64.0;
HASHVALUE += fract(time) * 64.0f;
for (int i = 0; i < samples; ++i)
{
vec2 hash = RandomHashValue();
vec2 disk = DiskPoint(sampleRadius, hash.x, hash.y);
//....
}
}
I see it like this:
vec2 DiskPoint(in float radius, in float x1, in float x2)
{
// x1,x2 are "uniform randoms" in range <0,+1>
float P = radius * sqrt(1.0f - x1); // this will scale x1 into P with range <0,radius> but change the distribution to uniform number of points inside disc
float theta = x2 * 2.0 * PI; // this will scale x2 to theta in range <0,6.28>
return vec2(P * cos(theta), P * sin(theta)); // and this just use the above as polar coordinates with parametric circle equation ...
// returning "random" point inside disc with uniform density ...
}
but I just guessed and not tested it so take that in mind
Based on #Spektre´s suggestion I drew the outcome of this function on a Canvas.
the code looks like this:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
class vec2{
constructor(x, y){
this.x = x;
this.y = y;
}
}
function DiskPoint(radius, x1, x2){
var p = radius * Math.sqrt(1.0 - x1);
var theta = x2 * 2.0 * 3.14;
return new vec2(p * Math.cos(theta), p * Math.sin(theta));
}
ctx.fillStyle = "#FF0000";
ctx.fillRect(100, 100, 1, 1);
for(var i = 0; i < 100; i++){
for(var j = 0; j < 100; j++){
var point = DiskPoint(0.5, i/100, j/100);
ctx.fillRect(point.x * 100 + 100, point.y * 100 + 100, 1, 1);
console.log(point.x, point.y);
}
}
This is the created Pattern:
It looks like it finds for every (x1,x2) a point in the circle of given radius.
Hope this will help!

Ray transformation in a Ray - OBB intersection test

I've implemented an algorithm that tests for a Ray - AABB intersection and it works fine. But when I try to transform Ray to the AABB's local space (making this a Ray - OBB test), I can't get correct results. I've studied several forums and other resources, but still missing something. (Some sources suggesting to apply inverted transformation to the ray origin and its end, and only then calc. direction, other - to apply transformation to origin and direction). Can someone point in the right direction (no pun intended)?
Here goes two functions responsible for the math:
1) Calculating inverses and other things to perform tests
bool Ray::intersectsMesh(const Mesh& mesh, const Transformation& transform) {
float largestNearIntersection = std::numeric_limits<float>::min();
float smallestFarIntersection = std::numeric_limits<float>::max();
glm::mat4 modelTransformMatrix = transform.modelMatrix();
Box boundingBox = mesh.boundingBox();
glm::mat4 inverse = glm::inverse(transform.modelMatrix());
glm::vec4 newOrigin = inverse * glm::vec4(mOrigin, 1.0);
newOrigin /= newOrigin.w;
mOrigin = newOrigin;
mDirection = glm::normalize(inverse * glm::vec4(mDirection, 0.0));
glm::vec3 xAxis = glm::vec3(glm::column(modelTransformMatrix, 0));
glm::vec3 yAxis = glm::vec3(glm::column(modelTransformMatrix, 1));
glm::vec3 zAxis = glm::vec3(glm::column(modelTransformMatrix, 2));
glm::vec3 OBBTranslation = glm::vec3(glm::column(modelTransformMatrix, 3));
printf("trans x %f y %f z %f\n", OBBTranslation.x, OBBTranslation.y, OBBTranslation.z);
glm::vec3 delta = OBBTranslation - mOrigin;
bool earlyFalseReturn = false;
calculateIntersectionDistances(xAxis, delta, boundingBox.min.x, boundingBox.max.x, &largestNearIntersection, &smallestFarIntersection, &earlyFalseReturn);
if (smallestFarIntersection < largestNearIntersection || earlyFalseReturn) { return false; }
calculateIntersectionDistances(yAxis, delta, boundingBox.min.y, boundingBox.max.y, &largestNearIntersection, &smallestFarIntersection, &earlyFalseReturn);
if (smallestFarIntersection < largestNearIntersection || earlyFalseReturn) { return false; }
calculateIntersectionDistances(zAxis, delta, boundingBox.min.z, boundingBox.max.z, &largestNearIntersection, &smallestFarIntersection, &earlyFalseReturn);
if (smallestFarIntersection < largestNearIntersection || earlyFalseReturn) { return false; }
return true;
}
2) Helper function (probably not needed here as its relates only to AABB tests and works fine)
void Ray::calculateIntersectionDistances(const glm::vec3& axis,
const glm::vec3& delta,
float minPointOnAxis,
float maxPointOnAxis,
float *largestNearIntersection,
float *smallestFarIntersection,
bool *earlyFalseRerutn)
{
float divident = glm::dot(axis, delta);
float denominator = glm::dot(mDirection, axis);
if (fabs(denominator) > 0.001f) {
float t1 = (divident + minPointOnAxis) / denominator;
float t2 = (divident + maxPointOnAxis) / denominator;
if (t1 > t2) { std::swap(t1, t2); }
*smallestFarIntersection = std::min(t2, *smallestFarIntersection);
*largestNearIntersection = std::max(t1, *largestNearIntersection);
} else if (-divident + minPointOnAxis > 0.0 || -divident + maxPointOnAxis < 0.0) {
*earlyFalseRerutn = true;
}
}
As it turned out, the ray's world -> model transformation was correct. The bug was in the intersection test. I had to completely replace the intersection code, because I wasn't able to identify the bug in the old code, unfortunately.
Ray transformation code:
glm::mat4 inverse = glm::inverse(transform.modelMatrix());
glm::vec4 start = inverse * glm::vec4(mOrigin, 1.0);
glm::vec4 direction = inverse * glm::vec4(mDirection, 0.0);
direction = glm::normalize(direction);
And the Ray - AABB test was stolen from here

Ray tracing obj files diffuse shading issue

The repository (GitHub)
I'm having issues with my diffuse shading for my models (It does not arise when rendering primitives.
What's interesting here to note is I think that when you look at the left reflective sphere, the shading appears normal (I might be wrong on this, basing on observation).
Low poly bunny and a triangle
Low poly bunny and 2 reflective spheres
Cube and 2 reflective spheres
I'm not sure what I'm doing wrong, as the normals are calculated each time I create a triangle in the constructor. I am using tinyobjloader to load my models, here is the TriangleMesh intersection algorithm.
FPType TriangleMesh::GetIntersection(const Ray &ray)
{
for(auto &shape : shapes)
{
size_t index_offset = 0;
for(size_t f = 0; f < shape.mesh.num_face_vertices.size(); ++f) // faces (triangles)
{
int fv = shape.mesh.num_face_vertices[f];
tinyobj::index_t &idx0 = shape.mesh.indices[index_offset + 0]; // v0
tinyobj::index_t &idx1 = shape.mesh.indices[index_offset + 1]; // v1
tinyobj::index_t &idx2 = shape.mesh.indices[index_offset + 2]; // v2
Vec3d &v0 = Vec3d(attrib.vertices[3 * idx0.vertex_index + 0], attrib.vertices[3 * idx0.vertex_index + 1], attrib.vertices[3 * idx0.vertex_index + 2]);
Vec3d &v1 = Vec3d(attrib.vertices[3 * idx1.vertex_index + 0], attrib.vertices[3 * idx1.vertex_index + 1], attrib.vertices[3 * idx1.vertex_index + 2]);
Vec3d &v2 = Vec3d(attrib.vertices[3 * idx2.vertex_index + 0], attrib.vertices[3 * idx2.vertex_index + 1], attrib.vertices[3 * idx2.vertex_index + 2]);
Triangle tri(v0, v1, v2);
if(tri.GetIntersection(ray))
return tri.GetIntersection(ray);
index_offset += fv;
}
}
}
The Triangle Intersection algorithm.
FPType Triangle::GetIntersection(const Ray &ray)
{
Vector3d v0v1 = v1 - v0;
Vector3d v0v2 = v2 - v0;
Vector3d pvec = ray.GetDirection().Cross(v0v2);
FPType det = v0v1.Dot(pvec);
// ray and triangle are parallel if det is close to 0
if(abs(det) < BIAS)
return false;
FPType invDet = 1 / det;
FPType u, v;
Vector3d tvec = ray.GetOrigin() - v0;
u = tvec.Dot(pvec) * invDet;
if(u < 0 || u > 1)
return false;
Vector3d qvec = tvec.Cross(v0v1);
v = ray.GetDirection().Dot(qvec) * invDet;
if(v < 0 || u + v > 1)
return false;
FPType t = v0v2.Dot(qvec) * invDet;
if(t < BIAS)
return false;
return t;
}
I think this is because when I'm handling all my object intersections, the triangle mesh is regarded as 1 object only, so it only returns 1 normal, when I'm trying to get the object normals: code
Color Trace(const Vector3d &origin, const Vector3d &direction, const std::vector<std::shared_ptr<Object>> &sceneObjects, const int indexOfClosestObject,
const std::vector<std::shared_ptr<Light>> &lightSources, const int &depth = 0)
{
if(indexOfClosestObject != -1 && depth <= DEPTH) // not checking depth for infinite mirror effect (not a lot of overhead)
{
std::shared_ptr<Object> sceneObject = sceneObjects[indexOfClosestObject];
Vector3d normal = sceneObject->GetNormalAt(origin);
screenshot of debug
EDIT: I have solved the issue and now shading works properly: https://github.com/MrCappuccino/Tracey/blob/testing/src/TriangleMesh.cpp#L35-L48
If you iterate all your faces and return on the first face you hit, it may happen that you hit faces which are behind other faces and therefore not really the face you want to hit, so you would have to measure the length of your ray and return the intersection for the shortest ray.

Converting 2D Noise to 3D

I've recently started experimenting with noise (simple perlin noise), and have run into a slight problem with animating it. So far come I've across an awesome looking 3d noise (https://github.com/ashima/webgl-noise) that I could use in my project but that I understood nothing of, and a bunch of tutorials that explain how to create simple 2d noise.
For the 2d noise, I originally used the following fragment shader:
uniform sampler2D al_tex;
varying vec4 varying_pos; //Actual coords
varying vec2 varying_texcoord; //Normalized coords
uniform float time;
float rand(vec2 co) { return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453); }
float ease(float p) { return 3*p*p - 2*p*p*p; }
float cnoise(vec2 p, int wavelength)
{
int ix1 = (int(varying_pos.x) / wavelength) * wavelength;
int iy1 = (int(varying_pos.y) / wavelength) * wavelength;
int ix2 = (int(varying_pos.x) / wavelength) * wavelength + wavelength;
int iy2 = (int(varying_pos.y) / wavelength) * wavelength + wavelength;
float x1 = ix1 / 1280.0f;
float y1 = iy1 / 720.0f;
float x2 = ix2 / 1280.0f;
float y2 = iy2 / 720.0f;
float xOffset = (varying_pos.x - ix1) / wavelength;
float yOffset = (varying_pos.y - iy1) / wavelength;
xOffset = ease(xOffset);
yOffset = ease(yOffset);
float t1 = rand(vec2(x1, y1));
float t2 = rand(vec2(x2, y1));
float t3 = rand(vec2(x2, y2));
float t4 = rand(vec2(x1, y2));
float tt1 = mix(t1, t2, xOffset);
float tt2 = mix(t4, t3, xOffset);
return mix(tt1, tt2, yOffset);
}
void main()
{
float t = 0;
int minFreq = 0;
int noIterations = 8;
for (int i = 0; i < noIterations; i++)
t += cnoise(varying_texcoord, int(pow(2, i + minFreq))) / pow(2, noIterations - i);
gl_FragColor = vec4(vec3(t), 1);
}
The result that I got was this:
Now, I want to animate it with time. My first thought was to change the rand function to take a vec3 instead of vec2, and then change my cnoise function accordingly, to interpolate values in the z direction too. With that goal in mind, I made this:
sampler2D al_tex;
varying vec4 varying_pos;
varying vec2 varying_texcoord;
uniform float time;
float rand(vec3 co) { return fract(sin(dot(co, vec3(12.9898, 78.2332, 58.5065))) * 43758.5453); }
float ease(float p) { return 3*p*p - 2*p*p*p; }
float cnoise(vec3 pos, int wavelength)
{
ivec3 iPos1 = (ivec3(pos) / wavelength) * wavelength; //The first value that I'll sample to interpolate
ivec3 iPos2 = iPos1 + wavelength; //The second value
vec3 transPercent = (pos - iPos1) / wavelength; //Transition percent - A float in [0-1) indicating how much of each of the above values will contribute to final result
transPercent.x = ease(transPercent.x);
transPercent.y = ease(transPercent.y);
transPercent.z = ease(transPercent.z);
float t1 = rand(vec3(iPos1.x, iPos1.y, iPos1.z));
float t2 = rand(vec3(iPos2.x, iPos1.y, iPos1.z));
float t3 = rand(vec3(iPos2.x, iPos2.y, iPos1.z));
float t4 = rand(vec3(iPos1.x, iPos2.y, iPos1.z));
float t5 = rand(vec3(iPos1.x, iPos1.y, iPos2.z));
float t6 = rand(vec3(iPos2.x, iPos1.y, iPos2.z));
float t7 = rand(vec3(iPos2.x, iPos2.y, iPos2.z));
float t8 = rand(vec3(iPos1.x, iPos2.y, iPos2.z));
float tt1 = mix(t1, t2, transPercent.x);
float tt2 = mix(t4, t3, transPercent.x);
float tt3 = mix(t5, t6, transPercent.x);
float tt4 = mix(t8, t7, transPercent.x);
float tt5 = mix(tt1, tt2, transPercent.y);
float tt6 = mix(tt3, tt4, transPercent.y);
return mix(tt5, tt6, transPercent.z);
}
float fbm(vec3 p)
{
float t = 0;
int noIterations = 8;
for (int i = 0; i < noIterations; i++)
t += cnoise(p, int(pow(2, i))) / pow(2, noIterations - i);
return t;
}
void main()
{
vec3 p = vec3(varying_pos.xy, time);
float t = fbm(p);
gl_FragColor = vec4(vec3(t), 1);
}
However, on doing this, the animation feels... strange. It's as though I'm watching a slideshow of perlin noise slides, with the individual slides fading in. All other perlin noise examples that I have tried (like https://github.com/ashima/webgl-noise) are actually animated with time - you can actually see it being animated, and don't just feel like the images are fading in, and not being actually animated. I know that I could just use the webgl-noise shader, but I want to make one for myself, and for some reason, I'm failing miserably. Could anyone tell me where I am going wrong, or suggest me on how I can actually animate it properly with time?
You should proably include z in the sin function:
float rand(vec3 co) { return fract(sin(dot(co.xy ,vec2(12.9898,78.233)) + co.z) * 43758.5453); }
Apparently the somewhat random numbers are prime numbers. This is to avoid patterns in the noise. I found another prime number, 94418953, and included that in the sin/dot function. Try this:
float rand(vec3 co) { return fract(sin(dot(co.xyz ,vec3(12.9898,78.233, 9441.8953))) * 43758.5453); }
EDIT: You don't take into account wavelength on the z axis. This means that all your iterations will have the same interpolation distance. In other words, you will get the fade effect you're describing. Try calculating z the same way you calculate x and y:
int iz1 = (int(p.z) / wavelength) * wavelength;
int iz2 = (int(p.z) / wavelength) * wavelength + wavelength;
float z1 = iz1 / 720.0f;
float z2 = iz2 / 720.0f;
float zOffset = (varying_pos.z - iz1) / wavelength;
This means however that the z value will variate the same rate that y will. So if you want it to scale from 0 to 1 then you should proably multiply z with 720 before passing it into the noise function.
check this code. it's a simple version of 3d noise:
// Here are some easy to understand noise gens... the D line in cubic interpolation (rounding)
function rndng ( n: float ): float
{//random proportion -1, 1 ... many people use Sin to take
//linearity out of a pseudo random, exp n*n is faster on central processor.
var e = ( n *321.9234)%1;
return (e*e*111.07546)%2-1;
}
function lerps(o:float, v:float, alpha:float):float
{
o += ( v - o ) * alpha;
return o;
}
//3d ----------------
function lnz ( vtx: Vector3 ): float //3d perlin noise code fast
{
vtx= Vector3 ( Mathf.Abs(vtx.x) , Mathf.Abs(vtx.y) , Mathf.Abs(vtx.z) ) ;
var I = Vector3 (Mathf.Floor(vtx.x),Mathf.Floor(vtx.y),Mathf.Floor(vtx.z));
var D = Vector3(vtx.x%1,vtx.y%1,vtx.z%1);
D = Vector3(D.x*D.x*(3.0-2.0*D.x),D.y*D.y*(3.0-2.0*D.y),D.z*D.z*(3.0-2.0*D.z));
var W = I.x + I.y*71.0 + 125.0*I.z;
return lerps(
lerps( lerps(rndng(W+0.0),rndng(W+1.0),D.x) , lerps(rndng(W+71.0),rndng(W+72.0),D.x) , D.y)
,
lerps( lerps(rndng(W+125.0),rndng(W+126.0),D.x) , lerps(rndng(W+153.0),rndng(W+154.0),D.x) , D.y)
,
D.z
);
}
//1d ----------------
function lnzo ( vtx: Vector3 ): float //perlin noise, same as unityfunction version
{
var total = 0.0;
for (var i:int = 1; i < 5; i ++)
{
total+= lnz2(Vector3 (vtx.x*(i*i),0.0,vtx.z*(i*i)))/(i*i);
}
return total*5;
}
//2d 3 axis honeycombe noise ----------------
function lnzh ( vtx: Vector3 ): float // perlin noise, 2d, with 3 axes at 60'instead of 2 x y axes
{
vtx= Vector3 ( Mathf.Abs(vtx.z) , Mathf.Abs(vtx.z*.5-vtx.x*.866) , Mathf.Abs(vtx.z*.5+vtx.x*.866) ) ;
var I = Vector3 (Mathf.Floor(vtx.x),Mathf.Floor(vtx.y),Mathf.Floor(vtx.z));
var D = Vector3(vtx.x%1,vtx.y%1,vtx.z%1);
//D = Vector3(D.x*D.x*(3.0-2.0*D.x),D.y*D.y*(3.0-2.0*D.y),D.z*D.z*(3.0-2.0*D.z));
var W = I.x + I.y*71.0 + 125.0*I.z;
return lerps(
lerps( lerps(rndng(W+0.0),rndng(W+1.0),D.x) , lerps(rndng(W+71.0),rndng(W+72.0),D.x) , D.y)
,
lerps( lerps(rndng(W+125.0),rndng(W+126.0),D.x) , lerps(rndng(W+153.0),rndng(W+154.0),D.x) , D.y)
,
D.z
);
}
//2d ----------------
function lnz2 ( vtx: Vector3 ): float // i think this is 2d perlin noise
{
vtx= Vector3 ( Mathf.Abs(vtx.x) , Mathf.Abs(vtx.y) , Mathf.Abs(vtx.z) ) ;
var I = Vector3 (Mathf.Floor(vtx.x),Mathf.Floor(vtx.y),Mathf.Floor(vtx.z));
var D = Vector3(vtx.x%1,vtx.y%1,vtx.z%1);
D = Vector3(D.x*D.x*(3.0-2.0*D.x),D.y*D.y*(3.0-2.0*D.y),D.z*D.z*(3.0-2.0*D.z));
var W = I.x + I.y*71.0 + 125.0*I.z;
return lerps(
lerps( lerps(rndng(W+0.0),rndng(W+1.0),D.x) , lerps(rndng(W+71.0),rndng(W+72.0),D.x) , D.z)
,
lerps( rndng(W+125.0), rndng(W+126.0),D.x)
,
D.z
);
}

Sphere Collision Algorithm-Overal Velocities Keep increacing (C++, OpenGL)

Im having trouble with my "Box Full of Sphere Particles" project. ive created a spheres[number_of_spheres][position,radius,velocity..] array. A bounding box (sides parallel to orthonormal axis).
Spheres that collide with the box have their velocity reversed on the dimention of the collision. that works ok.
For the sphere-to-sphere collision its pretty simple, i detect the collision by comparing distance to sum of radii. Now im trying to make them simply exchange velocities on the axis of collision. But the simulation runs fine for a while then the speeds keep increasing till its out of control.
bool CollisionDetect(int i, int j)
{
if(i==j)
{
return false;
}
float xx = (spheres[i][0]-spheres[j][0])*(spheres[i][0]-spheres[j][0]);
float yy = (spheres[i][1]-spheres[j][1])*(spheres[i][1]-spheres[j][1]);
float zz = (spheres[i][2]-spheres[j][2])*(spheres[i][2]-spheres[j][2]);
if( (xx + yy + zz) <= (spheres[i][3] + spheres[j][3])*(spheres[i][3] + spheres[j][3]) )
{
return true;
}
else
{
return false;
}
}
void CollisionSolve(int i, int j)
{
float m1,m2,m21;
Vec3 vel1,vel2,pos1,pos2; //spheres info
Vec3 nv1n,nv1b,nv1t;
Vec3 nv2n,nv2b,nv2t;
Vec3 N,T,B,X,Y,Z; //collision plane and world orthonormal basis
X.x=1;
X.y=0;
X.z=0;
Y.x=0;
Y.y=1;
Y.z=0;
Z.x=0;
Z.y=0;
Z.z=1;
pos1.x = spheres[i][0];
pos2.x = spheres[j][0];
pos1.y = spheres[i][1];
pos2.y = spheres[j][1];
pos1.z = spheres[i][2];
pos2.z = spheres[j][2];
vel1.x = spheres[i][4];
vel2.x = spheres[j][4];
vel1.y = spheres[i][5];
vel2.y = spheres[j][5];
vel1.z = spheres[i][6];
vel2.z = spheres[j][6];
m1 = spheres[i][8];
m2 = spheres[j][8]; //mass (for later)
N = minus(pos2,pos1); //get N vector (connecting centers of spheres)
N = Normalize(N);
T=X;
B = crossProduct(N,T); //find first perpendicular axis to N
if (B.x==0 && B.y==0 && B.z==0) //then vector parallel to X axis -
{
T=Y; //try Y axis
B = crossProduct(N,T);
}
T = crossProduct(N,B); //find second perpendicular axis to N
T = Normalize(T);
B = Normalize(B);
if (simplespherecollision)
{
nv1n = projectUonV (vel1 , N);
nv2n = projectUonV (vel2 , N);
vel1 = minus (vel1,nv1n);
vel2 = minus (vel2,nv2n);
vel1 = plus (vel1,nv2n);
vel2 = plus (vel2,nv1n);
//simply switch speed (for test)
}
/*/---THIS IS COMMENTED OUT (FIRST METHOD USED - DIDNT WORK)--------------------------------------------
nv1n = projectUonV (vel1 , N);
nv2n = projectUonV (vel2 , N);
nv1t = projectUonV (vel1 , T);
nv2t = projectUonV (vel2 , T);
nv1b = projectUonV (vel1 , B);
nv2b = projectUonV (vel2 , B); //project velocities on new orthonormal basis
vel1 = plus(nv1t , plus(nv1b , nv2n)); //project velocities back to world basis
vel2 = plus(nv2t , plus( nv2b , nv1n)); //by adding the sub vectors with swiched Xn
/*/----------------------------------------------------------------------
spheres[i][4] = vel1.x;
spheres[i][5] = vel1.y;
spheres[i][6] = vel1.z;
spheres[j][4] = vel2.x;
spheres[j][5] = vel2.y;
spheres[j][6] = vel2.z; //reasign velocities to spheres
}
}
And this is the Vec3 struct (just in case)
struct Vec3
{
float x, y, z;
};
Vec3 crossProduct(const Vec3& v1, const Vec3& v2)
{
Vec3 r;
r.x = (v1.y*v2.z) - (v1.z*v2.y);
r.y = (v1.z*v2.x) - (v1.x*v2.z);
r.z = (v1.x*v2.y) - (v1.y*v2.x);
return r;
}
Vec3 minus(const Vec3& v1, const Vec3& v2)
{
Vec3 r;
r.x = v1.x - v2.x;
r.y = v1.y - v2.y;
r.z = v1.z - v2.z;
return r;
}
Vec3 plus(const Vec3& v1, const Vec3& v2)
{
Vec3 r;
r.x = v1.x + v2.x;
r.y = v1.y + v2.y;
r.z = v1.z + v2.z;
return r;
}
double dotProduct(const Vec3& v1, const Vec3& v2)
{
return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
}
Vec3 scale(const Vec3& v, double a)
{
Vec3 r;
r.x = v.x * a;
r.y = v.y * a;
r.z = v.z * a;
return r;
}
Vec3 projectUonV(const Vec3& u, const Vec3& v)
{
Vec3 r;
r = scale(v,dotProduct(u,v));
return r;
}
int distanceSquared(const Vec3& v1, const Vec3& v2)
{
Vec3 delta = minus(v2, v1);
return dotProduct(delta, delta);
}
Vec3 Normalize (const Vec3& v)
{
Vec3 r;
r=v;
int mag = sqrt(dotProduct(v,v));
r.x = v.x/mag;
r.y = v.y/mag;
r.z = v.z/mag;
return r;
}
And this is my Render function witch draws the spheres and the bounding box and calls the other functions
void Render()
{
//CLEARS FRAME BUFFER ie COLOR BUFFER& DEPTH BUFFER (1.0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clean up the colour of the window
// and the depth buffer
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(-MAX_X/2,-MAX_Y/2,-MAX_Z*4);
glTranslatef (dx,0,dz);
//------------------------------------------------------------------------------------------------
//-----------------------------BOUNDING BOX----------------------------------------------------
glPushMatrix();
glTranslatef(MAX_X/2,MAX_Y/2,MAX_Z/2);
glColor4f(0.9,0.6,0.8,0.3);
glScalef(MAX_X,MAX_Y,MAX_Z);
glutSolidCube(1);
glutWireCube(1);
glPopMatrix();
//-----------------------------WALL COLLISION DETECTION-----ALWAYS ON--------------------------------
for (int i = 0; i<PART_NUM; i++)
{
if(spheres[i][0] > (MAX_X - spheres[i][3]) && spheres[i][4] > 0)
{
spheres[i][4] = spheres[i][4]*-1;
wallcollcount++;
}
if(spheres[i][1] > (MAX_Y - spheres[i][3]) && spheres[i][5] > 0)
{
spheres[i][5] = spheres[i][5]*-1;
wallcollcount++;
}
if(spheres[i][2] > (MAX_Z - spheres[i][3]) && spheres[i][6] > 0)
{
spheres[i][6] = spheres[i][6]*-1;
wallcollcount++;
}
if(spheres[i][0] < spheres[i][3] && spheres[i][4] < 0)
{
spheres[i][4] = spheres[i][4]*-1;
wallcollcount++;
}
if(spheres[i][1] < spheres[i][3] && spheres[i][5] < 0)
{
spheres[i][5] = spheres[i][5]*-1;
wallcollcount++;
}
if(spheres[i][2] < spheres[i][3] && spheres[i][6] < 0)
{
spheres[i][6] = spheres[i][6]*-1;
wallcollcount++;
}
//--------------------------------------Sphere ColDit -------------------------
if (spherecollision || simplespherecollision)
{
for(int j=i+1; j<PART_NUM; j++)
{
if(CollisionDetect(i,j))
{
spherecollcount++;
CollisionSolve(i,j);
}
}
}
//-----------------------------------------------DRAW--------------------------------
glPushMatrix();
glTranslatef(spheres[i][0],spheres[i][1],spheres[i][2]);
glColor3f( (float) (i+1)/(PART_NUM) , 1-(float)(i+1)/(PART_NUM) , 0.5);
glutSolidSphere(spheres[i][3], 18,18);
glPopMatrix();
}
//---------------------------------------------------------------------------------------------------------------/
glutSwapBuffers(); // All drawing commands applied to the
// hidden buffer, so now, bring forward
// the hidden buffer and hide the visible one
}
There are two things conserved in a collision:
momentum
and
energy
So far your calculations just consider the momentum (a simply speed swap assumes a so called central collision and identical masses of the spheres). If computers were infinitely precise this would work out perfectly. But computers have only limited precisions and so roundoff errors will creep in.
The simple solution for that problem is to correct the momentum part (which is mass · velocity) for the deviation in energy (1/2 mass · velocity²). This works because energy depends by the square of the velocity so small deviations on the speeds involved will create large energy deviations which you can use for correcting the calculation.
I.e. you calculate the total energy before the collision and then after the collision. Then you take the ratio sqrt(E_before / E_after) and scale the speeds after the calculation with that.
If you want to be really accurate you could do relativistic momentum and energy transfer ;)