#include <cmath>
#include <cstdlib>
#include <cstdio>
#include <cassert>
#include "textureData.h"
const int IMAGE_WIDTH = 64;
typedef struct Normal
{
Normal() : x(0), y(0), z(0) {}
Normal(float xx) : x(xx), y(xx), z(xx) {}
Normal(float xx, float yy, float zz) : x(xx), y(yy), z(zz) {}
Normal operator * (const float &r) const
{ return Normal(x*r, y*r, z*r); }
Normal operator - () const
{ return Normal(-x, -y, -z); }
float x, y, z;
};
typedef struct Vector
{
Vector() : x(0), y(0), z(0) {}
Vector(float xx) : x(xx), y(xx), z(xx) {}
Vector(float xx, float yy, float zz) : x(xx), y(yy), z(zz) {}
Vector(const Normal &n) : x(n.x), y(n.y), z(n.z) {}
Vector cross(const Vector &v) const {return
Vector((y*v.z)-(z*v.y),(z*v.x)-(x*v.z),(x*v.y)-(y*v.x)); }
float length() const { return sqrtf(x*x+y*y+z*z); }
float dot(const Vector &v) const { return x*v.x+y*v.y+z*v.z; }
Vector operator * (const float &r) const
{ return Vector(x*r, y*r, z*r); }
float x, y, z;
Vector operator - (const Vector &v) const
{ return Vector(x - v.x, y - v.y, z - v.z); }
Vector operator + (const Vector &v) const
{ return Vector(x + v.x, y + v.y, z + v.z); }
Vector operator - () const
{ return Vector(-x, -y, -z); }
};
typedef struct Point
{
Point() : x(0), y(0), z(0) {}
Point(float xx) : x(xx), y(xx), z(xx) {}
Point(float xx, float yy, float zz) : x(xx), y(yy), z(zz) {}
Vector operator - (const Point &p) const
{ return Vector(x - p.x, y - p.y, z - p.z); }
Point operator - (const Vector &v) const
{ return Point(x - v.x, y - v.y, z - v.z); }
Point operator + (const Vector &v) const
{ return Point(x+v.x, y+v.y, z+v.z); }
float x, y, z;
};
void Normalize(Vector *v)
{
float len2, lenInv;
len2 = v->x*v->x + v->y*v->y + v->z*v->z;
if (len2) {
lenInv = 1.f/sqrtf(len2);
v->x *= lenInv;
v->y *= lenInv;
v->z *= lenInv;
}
}
typedef struct Color
{
Color() : r(0), g(0), b(0) {}
Color(float rr) : r(rr), g(rr), b(rr) {}
Color(float rr, float gg, float bb) : r(rr), g(gg), b(bb) {}
Color operator * (const float &f) const { return Color(r * f, g * f, b * f); }
Color operator + (const Color &c) const
{ return Color(r + c.r, g + c.g, b + c.b); }
Color operator * (const Color &c) const
{ return Color(r * c.r, g * c.g, b * c.b); }
Color& operator *= (const Color &c)
{ r *= c.r, g *= c.g, b *= c.b; return *this;}
Color& operator *= (const float &f)
{ r *= f, g *= f, b *= f; return *this;}
float r, g, b;
};
typedef struct Ray
{
Vector direction;
Point origin;
};
typedef enum MATERIAL_TYPE { MATTE, DIFFUSE, GLASS, TEXTDIF, TEXTMATTE };
typedef struct Object
{
Object(const Point &c, const float &r,
const Color &col = Color(1), MATERIAL_TYPE matType = MATTE,
float l = 0, float ior = 1.3) :
center(c), radius(r), radius2(r*r), color(col), materialType(matType),
isLight(l), indexOfRefraction(ior) {}
Point center;
float radius, radius2;
Color color;
MATERIAL_TYPE materialType;
int isLight;
float indexOfRefraction;
};
Color getColor(const Object *object,const Ray *ray, float *t)
{
if (object->materialType == TEXTDIF || object->materialType == TEXTMATTE) {
float distance = *t;
Point pnt = ray->origin + ray->direction * distance;
Point oc = object->center;
Vector ve = Point(oc.x,oc.y,oc.z+1) - oc;
Normalize(&ve);
Vector vn = Point(oc.x,oc.y+1,oc.z) - oc;
Normalize(&vn);
Vector vp = pnt - oc;
Normalize(&vp);
double phi = acos(-vn.dot(vp));
float v = phi / M_PI;
float u;
float num1 = (float)acos(vp.dot(ve));
float num = (num1 /(float) sin(phi));
float theta = num /(float) (2 * M_PI);
if (theta < 0 || theta == NAN) {theta = 0;}
if (vn.cross(ve).dot(vp) > 0) {
u = theta;
}
else {
u = 1 - theta;
}
int x = (u * IMAGE_WIDTH) -1;
int y = (v * IMAGE_WIDTH) -1;
int p = (y * IMAGE_WIDTH + x)*3;
return Color(TEXT_DATA[p+2],TEXT_DATA[p+1],TEXT_DATA[p]);
}
else {
return object->color;
}
};
typedef struct Light
{
const Object *object;
};
static void computePrimRay(const int &i, const int &j, const int &imageWidth,
const int &imageHeight, const float &frameAspectRatio, Ray *ray)
{
float fov = 45;
float angle = tan(fov * 0.5f * M_PI / 180.0f);
ray->origin = Point(0);
float dx = 2 * frameAspectRatio/(float)imageWidth;
float dy = 2 / (float)imageHeight;
ray->direction.x = angle * ((i + 0.5) * dx - frameAspectRatio);
ray->direction.y = angle * (-(j + 0.5) * dy + 1);
ray->direction.z = 1;
Normalize(&ray->direction);
}
int Intersect(const Object *object, const Ray *ray, float *t)
{
// use geometric method
Vector oc = object->center - ray->origin;
// square distance to center of sphere
float oc2 = oc.dot(oc);
// distance to point on ray closest to sphere center
float tca = oc.dot(ray->direction);
bool outside = oc2 > object->radius2;
if (tca < 0 && outside) return 0;
// square distance from sphere center to closest point on ray
float d2 = oc2 - tca*tca;
// square distance from perpendicular bisector to center
float thc = object->radius2 - d2;
if (thc < 0)
return 0;
if (outside)
*t = tca - sqrtf(thc);
else
*t = tca + sqrtf(thc);
if (*t < 0) return 0;
return 1;
}
#ifdef INFINITY
#undef INFINITY
#endif
#define INFINITY 1e6
#define MAX_DEPTH_LEVEL 3
void snell(
const Vector &incidentRay,
const Normal &surfaceNormal,
const double &n1,
const double &n2,
Vector *reflectionDir,
Vector *refractionDir )
{
float n1n2 = n1 / n2;
float cost1 = incidentRay.dot(surfaceNormal);
float cost2 = sqrt(1.0 - (n1n2 * n1n2) * (1.0 - cost1 * cost1));
*reflectionDir = incidentRay - surfaceNormal * (2 * cost1);
*refractionDir = incidentRay * n1n2 + surfaceNormal * (cost2 - n1n2 * cost1);
}
void fresnel( const float &etai, const float &etat, const float &cosi,
const float &cost, float *Kr )
{
float Rp = ((etat * cosi) - (etai * cost)) / ((etat * cosi) + (etai * cost));
float Rs = ((etai * cosi) - (etat * cost)) / ((etai * cosi) + (etat * cost));
*Kr = ( Rp*Rp + Rs*Rs ) * .5;
}
static Color Trace(
const Ray *ray,
const int &depth,
const Object **objects,
const int &numObjects,
const Light *light,
const Color &bgColor)
{
if (depth > MAX_DEPTH_LEVEL) {
return bgColor;
}
float bias = 0.001;
const Object *object = NULL;
float minDistance = INFINITY;
Point pHit;
Normal nHit;
float t;
for (int k = 0; k < numObjects; k++) {
if (Intersect(objects[k], ray, &t)) {
if (t < minDistance) {
minDistance = t;
object = objects[k];
}
}
}
if (object == NULL)
return bgColor;
pHit = ray->origin + ray->direction * minDistance;
nHit.x = pHit.x - object->center.x;
nHit.y = pHit.y - object->center.y;
nHit.z = pHit.z - object->center.z;
Normalize((Vector*)&nHit);
if (object->materialType == GLASS) {
// compute reflection and refraction direction
Ray reflectionRay, refractionRay;
reflectionRay.origin = pHit + nHit * bias;
refractionRay.origin = pHit - nHit * bias;
snell(ray->direction, -nHit, 1.0, object->indexOfRefraction,
&reflectionRay.direction, &refractionRay.direction);
Normalize(&reflectionRay.direction);
Normalize(&refractionRay.direction);
float cosi = ray->direction.dot(-nHit);
float cost = refractionRay.direction.dot(-nHit);
float Kr;
fresnel(1.0, object->indexOfRefraction, cosi, cost, &Kr);
if (Kr < 0)
Kr = 0;
if (Kr > 1)
Kr = 1;
Color reflectionColor = Trace(&reflectionRay, depth + 1, objects,
numObjects, light, bgColor);
Color refractionColor = Trace(&refractionRay, depth + 1, objects,
numObjects, light, bgColor);
return object->color * refractionColor * (1-Kr) + reflectionColor * Kr;
}
if (object->materialType == MATTE)
return getColor(object, ray, &t);
Ray shadowRay;
int isInShadow = 0;
shadowRay.origin.x = pHit.x + nHit.x * bias;
shadowRay.origin.y = pHit.y + nHit.y * bias;
shadowRay.origin.z = pHit.z + nHit.z * bias;
shadowRay.direction = light->object->center - pHit;
float len = shadowRay.direction.length();
Normalize(&shadowRay.direction);
float LdotN = shadowRay.direction.dot(nHit);
if (LdotN < 0)
return 0;
Color lightColor = light->object->color;
for (int k = 0; k < numObjects; k++) {
if (Intersect(objects[k], &shadowRay, &t) && !objects[k]->isLight) {
if (objects[k]->materialType == GLASS)
lightColor *= getColor(objects[k], &shadowRay, &t); // attenuate light color by glass color
else
isInShadow = 1;
break;
}
}
lightColor *= 1.f/(len*len);
return (isInShadow) ? 0 : getColor(object, &shadowRay, &t) * lightColor * LdotN;
}
#define MAX_OBJECTS 4
static const Object* createNewObject(const Point &p, const float &r,
const Color &col, MATERIAL_TYPE matType, int isLight, Object **&objects,
int &numObjects)
{
assert(numObjects <= MAX_OBJECTS);
Object *object = new Object(p, r, col, matType, isLight);
objects[numObjects] = object;
numObjects++;
return object;
}
int main (int argc, char * const argv[])
{
static const int imageWidth = 640;
static const int imageHeight = 480;
// scene data (objects/light)
Object **objects = new Object*[MAX_OBJECTS];
int numObjects = 0;
createNewObject(
Point(0, 0, 15), 3, Color(.5, .7, .5), GLASS, 0, objects, numObjects);
createNewObject(
Point(-4.5, 4.5, 17), 1.8, Color(1, .3, .3), TEXTDIF, 0, objects, numObjects);
createNewObject(
Point(2, -2, 19), 2, Color(.3, 1, .3), DIFFUSE, 0, objects, numObjects);
Light light;
light.object = createNewObject(
Point(3, 3, 13), .5, Color(50), MATTE, 1, objects, numObjects);
// main render loop
assert(imageWidth >= imageHeight);
float frameAspectRatio = imageWidth/(float)imageHeight;
Color **pixels;
pixels = new Color*[imageWidth];
Color bgColor(0.5f, 0.62f, 0.78f);
for (int i = 0; i < imageWidth; ++i)
pixels[i] = new Color[imageHeight];
for (int j = 0; j < imageHeight; ++j) {
for (int i = 0; i < imageWidth; ++i) {
// compute primary ray direction
Ray primRay;
computePrimRay(i, j, imageWidth, imageHeight, frameAspectRatio, &primRay);
pixels[i][j] = Trace(&primRay, 0, (const Object**)objects, numObjects,
&light, bgColor);
// clamp
if (pixels[i][j].r > 1.f) pixels[i][j].r = 1.f;
if (pixels[i][j].g > 1.f) pixels[i][j].g = 1.f;
if (pixels[i][j].b > 1.f) pixels[i][j].b = 1.f;
}
}
// save to disk
FILE *fp;
fp = fopen("./raytrace.ppm", "w");
if (fp != NULL) {
fprintf(fp, "%s\n", "P6");
fprintf(fp, "%d %d\n", imageWidth, imageHeight);
fprintf(fp, "%d\n", 255);
char r, g, b;
float gamma = 1; // mac
for (int j = 0; j < imageHeight; ++j) {
for (int i = 0; i < imageWidth; ++i) {
pixels[i][j].r = 255 * powf(pixels[i][j].r, gamma);
pixels[i][j].g = 255 * powf(pixels[i][j].g, gamma);
pixels[i][j].b = 255 * powf(pixels[i][j].b, gamma);
r = (pixels[i][j].r > 255) ? 255 : pixels[i][j].r;
g = (pixels[i][j].g > 255) ? 255 : pixels[i][j].g;
b = (pixels[i][j].b > 255) ? 255 : pixels[i][j].b;
fprintf(fp, "%c%c%c", r, g, b);
}
}
fclose(fp);
}
// free memory
for (int i = 0; i < imageWidth; ++i)
delete [] pixels[i];
delete [] pixels;
for (int i = 0; i < numObjects; ++i)
delete objects[i];
delete [] objects;
return 0;
}