#include #include #include #include #include #include #include #include "geometry.h" #include "constants.h" #include "bitmap.h" // load texture files BITMAPINFOHEADER satinInfo; // texture info header BITMAPINFOHEADER rugInfo; // texture info header BITMAPINFOHEADER metalInfo; // texture info header unsigned char* satin = LoadBitmapFile("satin.bmp", &satinInfo); unsigned char* rug = LoadBitmapFile("rug.bmp", &rugInfo); unsigned char* metal = LoadBitmapFile("metal.bmp", &rugInfo); const int satinSize = 280; const int rugSize = 256; const int metalSize = 512; // Utility functions ************************************************************ // This tests zero-ness for floats bool isZero(const float x) { return (fabsf(x) < EPSILON); } // returns how many values are indistinguishable from zero (as floats) in a series // of three numbers. The boolean array "destination" gets filled as a side effect // upon calling this function. The values are true if the corresponding element // is a zero, and false otherwise. int whichAreZero(bool * destination, const float x, const float y, const float z) { bool* dest = destination; int numberOfZeros = 0; // in the following logical tests, the assignment // operator "=" instead of the logical "==" is precisely what // we want and is not mistaken. if (*dest++ = isZero(x)) numberOfZeros++; if(*dest++ = isZero(y)) numberOfZeros++; if(*dest = isZero(z)) numberOfZeros++; return numberOfZeros; } // Point3 class members ******************************************** Point3::Point3(): _x(0.0), _y(0.0), _z(0.0){} Point3::Point3(float x, float y, float z):_x(x), _y(y), _z(z){} Point3::Point3(const Point3 &toBeCopied): _x(toBeCopied._x), _y(toBeCopied._y), _z(toBeCopied._z){} bool Point3::IsZero() { return ((fabsf(_x) < EPSILON) & (fabsf(_y) < EPSILON) & (fabsf(_z) < EPSILON)); } Point3 Point3::operator + (const Point3 &p) { return Point3(_x + p._x, _y + p._y, _z + p._z); } Point3 Point3::operator - (const Point3 &p) { return Point3(_x - p._x, _y - p._y, _z - p._z); } // Euclidian distance between two points in 3D float distance(const Point3 &p1, const Point3 &p2) { Vector3 difference(p1.GetX() - p2.GetX(), p1.GetY() - p2.GetY(), p1.GetZ() - p2.GetZ()); return difference.Norm(); } // this scales the point by f Point3 Point3::operator *(float f) { return Point3(_x*f, _y*f, _z*f); } ostream& operator << (ostream &os, const Point3 &p) { return os << "("<> (istream &is, Point3 &p) { return is >> p._x>> p._y>> p._z; } // Vector3 member functions ********************************** Vector3::Vector3(float x, float y, float z) { // origin is at (0, 0, 0) _endPoint.SetX(x); _endPoint.SetY(y); _endPoint.SetZ(z); } Vector3::Vector3(Point3 tip) { _endPoint = tip; } Vector3::Vector3(Point3 tip, Point3 origin) { Point3 pt = tip - origin; _endPoint = pt; } Vector3::Vector3(const Vector3 &toBeCopied) { _endPoint = toBeCopied._endPoint; } bool Vector3::IsZero() { return ((fabsf(_endPoint.GetX()) < EPSILON) & (fabsf(_endPoint.GetY()) < EPSILON) & (fabsf(_endPoint.GetZ()) < EPSILON)); } Vector3 Vector3::operator + (const Vector3 &vec) { Point3 pt = this->_endPoint + vec._endPoint; return Vector3(pt); } Vector3 Vector3::operator - (const Vector3 &vec) { Point3 pt = this->_endPoint - vec._endPoint; return Vector3(pt); } Vector3 Vector3::operator -() { float x = _endPoint.GetX(); float y = _endPoint.GetY(); float z = _endPoint.GetZ(); return Vector3(-x, -y, -z); } float Vector3::Norm() { return (float) sqrt(_endPoint.GetX()*_endPoint.GetX() + _endPoint.GetY()*_endPoint.GetY() + _endPoint.GetZ()*_endPoint.GetZ()); } void Vector3::Normalize() { float norm = Norm(); // this is a safeguard against // possible calls with a null vector if (norm < EPSILON) norm = EPSILON; _endPoint.SetX(_endPoint.GetX()/norm); _endPoint.SetY(_endPoint.GetY()/norm); _endPoint.SetZ(_endPoint.GetZ()/norm); } float Vector3::Dot(const Vector3 &vec) { return (float)(_endPoint.GetX() * vec._endPoint.GetX() + _endPoint.GetY() * vec._endPoint.GetY() + _endPoint.GetZ() * vec._endPoint.GetZ()); } // non-member dot product float Dot(const Vector3 &vec1, const Vector3 &vec2) { return (float)(vec1.GetX() * vec2.GetX() + vec1.GetY() * vec2.GetY() + vec1.GetZ() * vec2.GetZ()); } // this scales the vector by f Vector3 Vector3::operator *(float f) { float x = _endPoint.GetX() * f; float y = _endPoint.GetY() * f; float z = _endPoint.GetZ() * f; return Vector3(x, y, z); } Vector3 Vector3::Cross(const Vector3 &w) { float x = _endPoint.GetY() * w._endPoint.GetZ() - _endPoint.GetZ() * w._endPoint.GetY(); float y = _endPoint.GetZ() * w._endPoint.GetX() - _endPoint.GetX() * w._endPoint.GetZ(); float z = _endPoint.GetX() * w._endPoint.GetY() - _endPoint.GetY() * w._endPoint.GetX(); // neglect small entries if (fabsf(x)< EPSILON) x = 0.0; if (fabsf(y)< EPSILON) y = 0.0; if (fabsf(z)< EPSILON) z = 0.0; return Vector3(x, y, z); } // non-member cross product Vector3 Cross(const Vector3 &vec1, const Vector3 &vec2) { float x = vec1.GetY() * vec2.GetZ() - vec1.GetZ() * vec2.GetY(); float y = vec1.GetZ() * vec2.GetX() - vec1.GetX() * vec2.GetZ(); float z = vec1.GetX() * vec2.GetY() - vec1.GetY() * vec2.GetX(); // neglect small entries if (fabsf(x)< EPSILON) x = 0.0; if (fabsf(y)< EPSILON) y = 0.0; if (fabsf(z)< EPSILON) z = 0.0; return Vector3(x, y, z); } // the Hadamard product is a term-by-term product of // each vector element. It can be useful in certain // situations. Vector3 Hadamard(const Vector3 &vec1, const Vector3 &vec2) { float x = vec1.GetX() * vec2.GetX(); float y = vec1.GetY() * vec2.GetY(); float z = vec1.GetZ() * vec2.GetZ(); return Vector3(x, y, z); } // This is the perpendicular projection of a point on // a line passing through two points. If we define a vector // a as from "lineP1" to "point" and a vector b as from // "lineP1" to "lineP2", the perpendicular projection is: // ||a x b|| / ||b|| float distanceFromLine(const Point3 &lineP1, const Point3 &lineP2, const Point3 &point) { Vector3 line(lineP2, lineP1); Vector3 toBeProjected(point, lineP1); Vector3 cross = Cross(line, toBeProjected); return cross.Norm()/line.Norm(); } // This is the usual projection of a point on a line passing // by "lineP1" and "lineP2". The formula is // proj = a . b / ||b|| float projection(const Point3 &lineP1, const Point3 &lineP2, const Point3 &point) { Vector3 line(lineP2, lineP1); Vector3 toBeProjected(point, lineP1); return Dot(line, toBeProjected) / line.Norm(); } ostream& operator << (ostream &os, const Vector3 &p) { return os << "<"<"; } istream& operator >> (istream &is, Vector3 &v) { float x, y, z; is >> x >> y >> z; v.SetX(x); v.SetY(y); v.SetZ(z); return is; } // NOTE: Here we suppose that the incoming vector vec is // pointing TOWARDS the surface, and that the normal // is pointing OUTSIDE of the surface. Do not use this // function if it is not the case! // NOTE 2: before using, test whether the dot product of // vec and normal is negative. This means a reflection // really happens. If the dot product is negative, it means // that the incoming vector vec is coming from the interior // of the surface, so there is no reflection. Vector3 ReflectedVector(const Vector3 &vec, const Vector3 &normal, bool glossy) { // compute a reflection vector Vector3 reflection; Vector3 unitNormal = normal; unitNormal.Normalize(); Vector3 unitVec = vec; unitVec.Normalize(); float factor = 2 * Dot(unitVec, unitNormal); float x = -unitNormal.GetX() * factor + unitVec.GetX(); float y = -unitNormal.GetY() * factor + unitVec.GetY(); float z = -unitNormal.GetZ() * factor + unitVec.GetZ(); reflection.SetX(x); reflection.SetY(y); reflection.SetZ(z); if (!glossy) { return reflection; } else { // compute a perturbation vector that is small compared // to the norm of reflection // get numbers between 0 and 100 x = rand()%100; y = rand()%100; z = rand()%100; // get numbers between -1 and 1 x = (x-50)/50; y = (y-50)/50; z = (z-50)/50; // multiply by GLOSS, the degree of glossiness x *= GLOSS; y *= GLOSS; z *= GLOSS; Vector3 perturbation(x, y, z); return (reflection + perturbation); } } // This function was written using the pseudocode and suggestions from // Peter Shirley's book, pages 163-164. // This assumes that a ray enters the surface from air (refractive index // of 1). When a ray exits the material, the refractIndex input to this // function must be set with the reciprocal (1/x) of the material // refractive index. If pure reflection occurs, the function returns // false. If there is a transmitted ray, the function returns true and // sets the last parameter to the transmission direction. bool RefractedVector(const Vector3 &vec, const Vector3 &normal, const float &refractIndex, Vector3 &transmitted) { float dDotn = Dot(vec, normal); float radicand = 1-((1-dDotn*dDotn)/(refractIndex*refractIndex)); if (radicand < 0.0) return false; else { float x = vec.GetX() - normal.GetX() * dDotn; float y = vec.GetY() - normal.GetY() * dDotn; float z = vec.GetZ() - normal.GetZ() * dDotn; transmitted.SetX((x/refractIndex) - (normal.GetX() * sqrt(radicand))); transmitted.SetY((y/refractIndex) - (normal.GetY() * sqrt(radicand))); transmitted.SetZ((z/refractIndex) - (normal.GetZ() * sqrt(radicand))); return true; } } bool areVectorsProportional(const Vector3 &vec1, const Vector3 &vec2, float &t) { // extract vector coordinates float x1 = vec1.GetX(); float y1 = vec1.GetY(); float z1 = vec1.GetZ(); float x2 = vec2.GetX(); float y2 = vec2.GetY(); float z2 = vec2.GetZ(); // build arrays having ones when a zero entry is // found in each vector and zeros otherwise bool zerosInVec1[3]; bool zerosInVec2[3]; int numZerosInVec1 = whichAreZero(zerosInVec1, x1, y1, z1); int numZerosInVec2 = whichAreZero(zerosInVec2, x2, y2, z2); // check whether the two boolean vectors are identical; // if not, return false since they cannot be proportional // example: (1, 1, 0) is not proportional to (1, 0, 0) for (int i = 0; i < 3; i++) { if (zerosInVec1[i] != zerosInVec2[i]) return false; } // in what follows, we are now sure that both vec1 and vec2 // have nonzero elements in the same positions. We need to // test proportionality of these nonzero elements. // first find out the location of zero positions in both vectors bool xIsZero = false; bool yIsZero = false; bool zIsZero = false; if (zerosInVec1[0]) xIsZero = true; if (zerosInVec1[1]) yIsZero = true; if (zerosInVec1[2]) zIsZero = true; // if we have found two zeros, then the two // vectors are automatically proportional. // example: (27, 0, 0) and (-2, 0, 0). Compute // the proportionality factor t and return. if (numZerosInVec1 == 2) { if (!xIsZero) t = x2/x1; if (!yIsZero) t = y2/y1; if (!zIsZero) t = z2/z1; return true; } // if we have found two nonzero positions in each // vector, check proportionality. Example: // (x1, y1, 0) and (x2, y2, 0) are proportional only if // (x1/x2 - y1/y2) == 0 if (numZerosInVec1 == 1) { if (xIsZero) // proportionality may occur in y and z { if (isZero(y1/y2 - z1/z2)) { t = y2/y1; return true; } else return false; } if (yIsZero) // proportionality may occur in x and z { if (isZero(z1/z2 - x1/x2)) { t = z2/z1; return true; } else return false; } if (zIsZero) // proportionality may occur in x and y { if (isZero(x1/x2 - y1/y2)) { t = y2/y1; return true; } else return false; } } // if we have found three nonzero positions, check // whether proportionality is same for three positions if (isZero(x1/x2 - y1/y2) & isZero(y1/y2 - z1/z2)) { t = x2/x1; return true; } else return false; } // Interval member functions **************************************** Interval::Interval(float l, float h) { if (l < h) { low = l; high = h; } else { low = h; high = l; } } ostream& operator << (ostream &os, const Interval &i) { os << "[ " << i.low << ", " << i.high << "] "; return os; } // returns the null interval [0, 0] if empty Interval Intersection(Interval i1, Interval i2) { Interval inter; // disjoint case if ((i1.high < i2.low) | (i1.low > i2.high)) return inter; // i1 containing i2 if ((i1.low < i2.low)&(i1.high > i2.high)) { inter.low = i2.low; inter.high = i2.high; return inter; } // i2 containing i1 if ((i2.low < i1.low)&(i2.high > i1.high)) { inter.low = i1.low; inter.high = i1.high; return inter; } // i1 extending lower than i2 if (i1.high > i2.low) { inter.low = i2.low; inter.high = i1.high; return inter; } // i2 extending lower than i1 if (i2.high > i1.low) { inter.low = i1.low; inter.high = i2.high; return inter; } return inter; } // RGBColor member functions ************************************ RGBColor::RGBColor(): _red(0.0), _green(0.0), _blue(0.0){} RGBColor::RGBColor(float red, float green, float blue) { if(red > 1.0) red = 1.0; if(green > 1.0) green = 1.0; if(blue > 1.0) blue = 1.0; if(red < 0.0) red = 0.0; if(green < 0.0) green = 0.0; if(blue < 0.0) blue = 0.0; _red = red; _green = green; _blue = blue; } RGBColor::RGBColor(const RGBColor& toBeCopied): _red(toBeCopied._red), _green(toBeCopied._green), _blue(toBeCopied._blue){} void RGBColor::SetRed(const float &r) { _red = r; if (_red > 1.0) _red = 1.0; if (_red < 0.0) _red = 0.0; } void RGBColor::SetGreen(const float &g) { _green = g; if (_green > 1.0) _green = 1.0; if (_green < 0.0) _green = 0.0; } void RGBColor::SetBlue(const float &b) { _blue = b; if (_blue > 1.0) _blue = 1.0; if (_blue < 0.0) _blue = 0.0; } RGBColor RGBColor::operator +(const RGBColor& C) { float red = _red + C.GetRed(); float green = _green + C.GetGreen(); float blue = _blue + C.GetBlue(); if(red > 1.0) red = 1.0; if(green > 1.0) green = 1.0; if(blue > 1.0) blue = 1.0; return RGBColor(red, green, blue); } RGBColor RGBColor::operator -(const RGBColor& C) { float red = _red - C.GetRed(); float green = _green - C.GetGreen(); float blue = _blue - C.GetBlue(); if(red < 0.0) red = 0.0; if(green < 0.0) green = 0.0; if(blue < 0.0) blue = 0.0; return RGBColor(red, green, blue); } void RGBColor::operator += (const RGBColor& C) { _red += C.GetRed(); _green += C.GetGreen(); _blue += C.GetBlue(); if(_red > 1.0) _red = 1.0; if(_green > 1.0) _green = 1.0; if(_blue > 1.0) _blue = 1.0; } void RGBColor::operator -= (const RGBColor& C) { _red -= C.GetRed(); _green -= C.GetGreen(); _blue -= C.GetBlue(); if(_red < 0.0) _red = 0.0; if(_green < 0.0) _green = 0.0; if(_blue < 0.0) _blue = 0.0; } RGBColor RGBColor::operator *(const float &num) { float red = _red * num; float green = _green * num; float blue = _blue * num; if(red > 1.0) red = 1.0; if(green > 1.0) green = 1.0; if(blue > 1.0) blue = 1.0; if(red < 0.0) red = 0.0; if(green < 0.0) green = 0.0; if(blue < 0.0) blue = 0.0; return RGBColor(red, green, blue); } RGBColor RGBColor::operator /(const float &num) { assert(num != 0); float red = _red / num; float green = _green / num; float blue = _blue / num; if(red > 1.0) red = 1.0; if(green > 1.0) green = 1.0; if(blue > 1.0) blue = 1.0; if(red < 0.0) red = 0.0; if(green < 0.0) green = 0.0; if(blue < 0.0) blue = 0.0; return RGBColor(red, green, blue); } // dot product of colors float RGBColor::operator *(const RGBColor &C) { return (float)(_red * C.GetRed() + _green * C.GetGreen() + _blue * C.GetBlue()); } RGBColor Hadamard(const RGBColor &c1, const RGBColor &c2) { float red = c1.GetRed() * c2.GetRed(); float green = c1.GetGreen() * c2.GetGreen(); float blue = c1.GetBlue() * c2.GetBlue(); if(red > 1.0) red = 1.0; if(green > 1.0) green = 1.0; if(blue > 1.0) blue = 1.0; if(red < 0.0) red = 0.0; if(green < 0.0) green = 0.0; if(blue < 0.0) blue = 0.0; return RGBColor(red, green, blue); } ostream& operator << (ostream &os, const RGBColor &C) { return os << "red: "<> (istream &is, RGBColor &C) { float r, g, b; is >> r >> g >> b; C.SetRed(r); C.SetGreen(g); C.SetBlue(b); return is; } // Material class member functions ******************************************* Material::Material() { _ambi = _diff = _spec = _refl = _specExp = _refr = _redAtt = _greenAtt = _blueAtt = 0.0; _metal = _glossy = _dielectric = false; _texture = 0; } Material::Material(const RGBColor& C, float ambi, float diff, float spec, float specExp, float refl, float refr, float redAtt, float greenAtt, float blueAtt, bool metal, bool glossy, bool dielectric, int tex) { _materialColor = C; if (ambi > 1.0) ambi = 1.0; if (ambi < 0.0) ambi = 0.0; _ambi = ambi; if (diff > 1.0) diff = 1.0; if (diff < 0.0) diff = 0.0; _diff = diff; if (spec > 1.0) spec = 1.0; if (spec < 0.0) spec = 0.0; _spec = spec; if (specExp < 1.0) specExp = 1.0; if (specExp > 200.0) specExp = 200.0; _specExp = specExp; if (refl > 1.0) refl = 1.0; if (refl < 1.0) refl = 0.0; _refl = refl; _refr = refr; if (redAtt < 0.0) redAtt = 0.0; _redAtt = redAtt; if (greenAtt < 0.0) greenAtt = 0.0; _greenAtt = greenAtt; if (blueAtt < 0.0) blueAtt = 0.0; _blueAtt = blueAtt; _metal = metal; _glossy = glossy; _dielectric = dielectric; _texture = tex; } Material::Material(float red, float green, float blue, float ambi, float diff, float spec, float specExp, float refl, float refr, float redAtt, float greenAtt, float blueAtt, bool metal, bool glossy, bool dielectric, int tex) { if(red > 1.0) red = 1.0; if(red < 0.0) red = 0.0; if(green > 1.0) green = 1.0; if(green < 0.0) green = 0.0; if(blue > 1.0) blue = 1.0; if(blue < 0.0) blue = 0.0; _materialColor.SetRed(red); _materialColor.SetGreen(green); _materialColor.SetBlue(blue); if (ambi > 1.0) ambi = 1.0; if (ambi < 0.0) ambi = 0.0; _ambi = ambi; if (diff > 1.0) diff = 1.0; if (diff < 0.0) diff = 0.0; _diff = diff; if (spec > 1.0) spec = 1.0; if (spec < 0.0) spec = 0.0; _spec = spec; if (specExp < 1.0) specExp = 1.0; if (specExp > 200.0) specExp = 200.0; _specExp = specExp; if (refl > 1.0) refl = 1.0; if (refl < 1.0) refl = 0.0; _refl = refl; _refr = refr; if (redAtt < 0.0) redAtt = 0.0; _redAtt = redAtt; if (greenAtt < 0.0) greenAtt = 0.0; _greenAtt = greenAtt; if (blueAtt < 0.0) blueAtt = 0.0; _blueAtt = blueAtt; _metal = metal; _glossy = glossy; _dielectric = dielectric; _texture = tex; } Material::Material(const Material& toBeCopied) { _materialColor = toBeCopied.GetMaterialColor(); _ambi = toBeCopied.GetAmbi(); _diff = toBeCopied.GetDiff(); _spec = toBeCopied.GetSpec(); _specExp = toBeCopied.GetSpecExp(); _refl = toBeCopied.GetRefl(); _refr = toBeCopied.GetRefr(); _redAtt = toBeCopied.GetRedAtt(); _greenAtt = toBeCopied.GetGreenAtt(); _blueAtt = toBeCopied.GetBlueAtt(); _metal = toBeCopied.GetMetal(); _glossy = toBeCopied.GetGlossy(); _dielectric = toBeCopied.GetDielectric(); _texture = toBeCopied.GetTexture(); } void Material::SetAmbi(float ambi) { if (ambi > 1.0) ambi = 1.0; if (ambi < 0.0) ambi = 0.0; _ambi = ambi; } void Material::SetDiff(float diff) { if (diff > 1.0) diff = 1.0; if (diff < 0.0) diff = 0.0; _diff = diff; } void Material::SetSpec(float spec) { if (spec > 1.0) spec = 1.0; if (spec < 0.0) spec = 0.0; _spec = spec; } void Material::SetSpecExp(float specExp) { if (specExp < 1.0) specExp = 1.0; if (specExp > 200.0) specExp = 200.0; _specExp = specExp; } void Material::SetRefl(float refl) { if (refl > 1.0) refl = 1.0; if (refl < 0.0) refl = 0.0; _refl = refl; } ostream& operator << (ostream &os, const Material &M) { return os << "\nred: "<> (istream &is, Material &M) { RGBColor C; is >> C; M.SetMaterialColor(C); float ambi, diff, spec, specExp, refl, refr, redAtt, greenAtt, blueAtt; int m, g, d; bool metal = false; bool glossy = false; bool dielectric = false; is >> ambi >> diff >> spec >> specExp>> refl >> refr; is >> redAtt >> greenAtt >> blueAtt; is >> m >> g >> d; if (m == 1) metal = true; if (g == 1) glossy = true; if (d == 1) dielectric = true; if (ambi > 1.0) ambi = 1.0; if (ambi < 0.0) ambi = 0.0; if (diff > 1.0) diff = 1.0; if (diff < 0.0) diff = 0.0; M.SetAmbi(ambi); M.SetDiff(diff); M.SetSpec(spec); M.SetSpecExp(specExp); M.SetRefl(refl); M.SetRefr(refr); M.SetRedAtt(redAtt); M.SetGreenAtt(greenAtt); M.SetBlueAtt(blueAtt); M.SetMetal(metal); M.SetGlossy(glossy); M.SetDielectric(dielectric); return is; } // Ray class member functions ********************************* // In this class, any attempt to set the lineDirection vector // to zero will cause an error from assert(). Ray::Ray(Point3 pointOnRay, Point3 origin) { _origin = origin; _rayDirection = pointOnRay - origin; assert(!_rayDirection.IsZero()); } Ray::Ray(Vector3 direction, Point3 origin) { _origin = origin; _rayDirection = direction; assert(!_rayDirection.IsZero()); } Ray::Ray(const Ray& toBeCopied) { _origin = toBeCopied.GetOrigin(); _rayDirection = toBeCopied.GetRayDirection(); } // intersection with point. This function returns false // if the point tested is the origin of the ray. bool Ray::IsPointOnRay(const Point3 &point, float &t) { t = 0.0; // default value if no intersection // we solve the equation: // origin + t*rayDirection = testPoint, // so the rayDirection vector should be proportional // to (testPoint - origin) // get line direction coordinates float xDir = _rayDirection.GetX(); float yDir = _rayDirection.GetY(); float zDir = _rayDirection.GetZ(); // get difference (testPoint - linePoint) coordinates float xDiff = point.GetX() - _origin.GetX(); float yDiff = point.GetY() - _origin.GetY(); float zDiff = point.GetZ() - _origin.GetZ(); // report no intersection if the point to be tested // is the same as the origin of the ray if (isZero(xDiff) & isZero(yDiff) & isZero(zDiff)) return false; // test of proportionality between rayDirection and // (testPoint - origin) Vector3 vec1(xDir, yDir, zDir); Vector3 vec2(xDiff, yDiff, zDiff); if (areVectorsProportional(vec1, vec2, t)) return true; else return false; } // since a point on a ray is described as // point = origin + t*direction, this function // returns a point on a ray for a given t. Point3 Ray::RayPoint(const float &t) { float x = _origin.GetX() + t * _rayDirection.GetX(); float y = _origin.GetY() + t * _rayDirection.GetY(); float z = _origin.GetZ() + t * _rayDirection.GetZ(); Point3 pt(x, y, z); return pt; } // same but non-member function void RayPoint(const Ray &ray, const float &t, Point3 &pt) { float x = ray.GetOrigin().GetX() + t * ray.GetRayDirection().GetX(); float y = ray.GetOrigin().GetY() + t * ray.GetRayDirection().GetY(); float z = ray.GetOrigin().GetZ() + t * ray.GetRayDirection().GetZ(); pt.SetX(x); pt.SetY(y); pt.SetZ(z); return; } ostream& operator << (ostream &os, const Ray &R) { return os << "ray origin: ("<"; } // Sphere class member functions **************************************** Sphere::Sphere() { _radius = 0.0; } Sphere::Sphere(Point3 center, float radius, Material sphereMaterial) { _center = center; _radius = radius; _material = sphereMaterial; } Sphere::Sphere(float x, float y, float z, float radius, Material sphereMaterial) { _center.SetX(x); _center.SetY(y); _center.SetZ(z); _radius = radius; _material = sphereMaterial; } Sphere::Sphere(const Sphere& toBeCopied) { _center = toBeCopied.GetCenter(); _radius = toBeCopied.GetRadius(); _material = toBeCopied.GetMaterial(); } bool Sphere::IsPointOnSphere(const Point3 &point) { return (isZero((distance(point, _center) - _radius))); } // if the point is not on sphere, this returns an // empty normal void Sphere::SphereNormal(const Point3 &pt, Vector3 &normal) { normal = Vector3(pt, _center); normal.Normalize(); return; } // given a ray r(t) = origin + t*direction, // and a sphere (point - center) = radius^2, // the intersection is defined as the locus // (origin + t*direction - radius)^2 = radius^2, // which solves as a quadratic equation in t // At^2 + Bt + C = 0, where // A = direction.direction // B = 2*direction.(origin - center) // C = (origin - center)^2 - radius^2. // the quadratic equation is then solved. // If the ray intersects the Sphere in two points, // only the nearest from the ray's origin is returned. // The parameters minT and maxT compose the allowed hit // range of ray parameter t. bool Sphere::Hit(const Ray &ray, Point3 &point, const float &minT, const float &maxT, float &t, Vector3 &normal) { Vector3 direction = ray.GetRayDirection(); Vector3 difference(ray.GetOrigin(), _center); float A = direction.Dot(direction); float B = 2 * direction.Dot(difference); float C = difference.Dot(difference) - _radius*_radius; float discriminant = B*B - 4*A*C; // first determine if there is an intersection or not if (discriminant < 0.0) return false; else { // degenerate case of a single intersection if (isZero(discriminant)) { t = -B/(2*A); if ((t > minT) & (t < maxT)) { // set point to intersection RayPoint(ray, t, point); SphereNormal(point, normal); return true; } else return false; } // double intersection; return closest point float t1; float t2; t1 = (float)(-B + sqrt(discriminant))/(2*A); t2 = (float)(-B - sqrt(discriminant))/(2*A); // first case of both t1 and t2 in allowed range if ((t1 > minT) & (t1 < maxT) & (t2 > minT) & (t2 < maxT)) { if (t1 < t2) t = t1; else t = t2; RayPoint(ray, t, point); SphereNormal(point, normal); return true; } else { if((t1 > minT) & (t1 < maxT)) { t = t1; RayPoint(ray, t, point); SphereNormal(point, normal); return true; } if ((t2 > minT) & (t2 < maxT)) { t = t2; RayPoint(ray, t, point); SphereNormal(point, normal); return true; } } t = INFINITY; return false; } } // This is the same as the previous function, but does less calculations. // This is useful for shadowing purposes, where we are not interested to // know the t value and the surface normal. bool Sphere::QuickHit(const Ray &ray, const float &minT, const float &maxT) { float t; Vector3 direction = ray.GetRayDirection(); Vector3 difference(ray.GetOrigin(), _center); float A = direction.Dot(direction); float B = 2 * direction.Dot(difference); float C = difference.Dot(difference) - _radius*_radius; float discriminant = B*B - 4*A*C; // first determine if there is an intersection or not if (discriminant < 0.0) return false; else { // degenerate case of a single intersection if (isZero(discriminant)) { t = -B/(2*A); if ((t > minT) & (t < maxT)) return true; else return false; } // double intersection; return closest point float t1; float t2; t1 = (float)(-B + sqrt(discriminant))/(2*A); t2 = (float)(-B - sqrt(discriminant))/(2*A); if (((t1 > minT) & (t1 < maxT)) | ((t2 > minT) & (t2 < maxT))) return true; else return false; } } RGBColor Sphere::AmbientLight(const Point3 &hitPoint) { // int textureSize; // float objX, objY; // int i, j; float x, y, z; // float red; // float green; // float blue; float ambient = _material.GetAmbi(); // case of no texture if (_material.GetTexture() == 0) { x = _material.GetMaterialColor().GetRed() * ambient * AMBIENT_RED; y = _material.GetMaterialColor().GetGreen()* ambient * AMBIENT_GREEN; z = _material.GetMaterialColor().GetBlue() * ambient * AMBIENT_BLUE; } /* else // texture is applied { // determine coordinates of hitpoint relative to // the lower side of the rectangle; these are going // to map directly to the texture objX = projection(_p1, _p2, hitPoint); objY = distanceFromLine(_p1, _p2, hitPoint); switch (_material.GetTexture()) { case 1: textureSize = satinSize; // find corresponding bitmap coordinates (range from 0 to textureSize1) // by doing a simple windowing transform i = (int)(objX - minX) * ((textureSize - 1)/(maxX - minX)); j = (int)(objY - minY) * ((textureSize - 1)/(maxY - minY)); // find colors in satin bitmap file corresponding to bitmap coordinates red = (float)satin[3*(i + textureSize*j) + RED]/255; green = (float)satin[3*(i + textureSize*j) + GREEN]/255; blue = (float)satin[3*(i + textureSize*j) + BLUE]/255; x = red * ambient * AMBIENT_RED; y = green* ambient * AMBIENT_GREEN; z = blue * ambient * AMBIENT_BLUE; break; case 2: textureSize = rugSize; // find corresponding bitmap coordinates (range from 0 to textureSize1) // by doing a simple windowing transform i = (int)(objX - minX) * ((textureSize - 1)/(maxX - minX)); j = (int)(objY - minY) * ((textureSize - 1)/(maxY - minY)); // find colors in satin bitmap file corresponding to bitmap coordinates red = (float)rug[3*(i + textureSize*j) + RED]/255; green = (float)rug[3*(i + textureSize*j) + GREEN]/255; blue = (float)rug[3*(i + textureSize*j) + BLUE]/255; x = red * ambient * AMBIENT_RED; y = green* ambient * AMBIENT_GREEN; z = blue * ambient * AMBIENT_BLUE; break; case 3: textureSize = metalSize; // find corresponding bitmap coordinates (range from 0 to textureSize1) // by doing a simple windowing transform i = (int)(objX - minX) * ((textureSize - 1)/(maxX - minX)); j = (int)(objY - minY) * ((textureSize - 1)/(maxY - minY)); // find colors in satin bitmap file corresponding to bitmap coordinates red = (float)metal[3*(i + textureSize*j) + RED]/255; green = (float)metal[3*(i + textureSize*j) + GREEN]/255; blue = (float)metal[3*(i + textureSize*j) + BLUE]/255; x = red * ambient * AMBIENT_RED; y = green* ambient * AMBIENT_GREEN; z = blue * ambient * AMBIENT_BLUE; break; } } */ return RGBColor(x, y, z); } RGBColor Sphere::DiffuseLight(const Vector3 &normal, const Vector3 &lightDirection, const RGBColor &lightColor, const Vector3 &eyeDirection, const Point3 &hitPoint) { // in what follows, I consider the texture as // aligned to the first edge. The texture is applied // properly only if the Quad is rectangular. TO DO: // make texture work in the general case. /* // These four things are what changes in the general case float minX = 0; float maxX = distance(_p1, _p2); float minY = 0; float maxY = distance(_p1, _p4); */ // int textureSize; // float objX, objY; // int i, j; // float red; // float green; // float blue; Vector3 correctedNormal = normal; float diffuse = _material.GetDiff(); // flip normal if it points away from viewer in the case // of a dielectric // if ((_material.GetDielectric()) & (Dot(eyeDirection, normal) < 0.0)) // correctedNormal = -correctedNormal; // dot product of normal and light float prod = Dot(correctedNormal, lightDirection); // shadow part: return black color if (prod < 0.0) return RGBColor(0.0, 0.0, 0.0); else { float x, y, z; // case of no texture if (_material.GetTexture() == 0) { x = _material.GetMaterialColor().GetRed() * diffuse * lightColor.GetRed() * prod; y = _material.GetMaterialColor().GetGreen() * diffuse * lightColor.GetGreen()* prod; z = _material.GetMaterialColor().GetBlue() * diffuse * lightColor.GetBlue() * prod; } /* else // texture is applied { // determine coordinates of hitpoint relative to // the lower side of the rectangle; these are going // to map directly to the texture objX = projection(_p1, _p2, hitPoint); objY = distanceFromLine(_p1, _p2, hitPoint); switch (_material.GetTexture()) { case 1: textureSize = satinSize; // find corresponding bitmap coordinates (range from 0 to textureSize1) // by doing a simple windowing transform i = (int)(objX - minX) * ((textureSize - 1)/(maxX - minX)); j = (int)(objY - minY) * ((textureSize - 1)/(maxY - minY)); // find colors in satin bitmap file corresponding to bitmap coordinates red = (float)satin[3*(i + textureSize*j) + RED]/255; green = (float)satin[3*(i + textureSize*j) + GREEN]/255; blue = (float)satin[3*(i + textureSize*j) + BLUE]/255; x = red * diffuse * lightColor.GetRed() * prod; y = green* diffuse * lightColor.GetGreen() * prod; z = blue * diffuse * lightColor.GetBlue() * prod; break; case 2: textureSize = rugSize; // find corresponding bitmap coordinates (range from 0 to textureSize1) // by doing a simple windowing transform i = (int)(objX - minX) * ((textureSize - 1)/(maxX - minX)); j = (int)(objY - minY) * ((textureSize - 1)/(maxY - minY)); // find colors in satin bitmap file corresponding to bitmap coordinates red = (float)rug[3*(i + textureSize*j) + RED]/255; green = (float)rug[3*(i + textureSize*j) + GREEN]/255; blue = (float)rug[3*(i + textureSize*j) + BLUE]/255; x = red * diffuse * lightColor.GetRed() * prod; y = green* diffuse * lightColor.GetGreen() * prod; z = blue * diffuse * lightColor.GetBlue() * prod; break; case 3: textureSize = metalSize; // find corresponding bitmap coordinates (range from 0 to textureSize1) // by doing a simple windowing transform i = (int)(objX - minX) * ((textureSize - 1)/(maxX - minX)); j = (int)(objY - minY) * ((textureSize - 1)/(maxY - minY)); // find colors in satin bitmap file corresponding to bitmap coordinates red = (float)metal[3*(i + textureSize*j) + RED]/255; green = (float)metal[3*(i + textureSize*j) + GREEN]/255; blue = (float)metal[3*(i + textureSize*j) + BLUE]/255; x = red * diffuse * lightColor.GetRed() * prod; y = green* diffuse * lightColor.GetGreen() * prod; z = blue * diffuse * lightColor.GetBlue() * prod; break; } } */ return RGBColor(x, y, z); } } // This uses the Phong model for specular highlights. If the material is metallic, // the highlight is influenced by the metal color. If not, the highlight depends // on the light color only. RGBColor Sphere::SpecularLight(const Vector3 &normal, const Vector3 &lightDirection, const RGBColor &lightColor, const Vector3 &eyeDirection, const Point3 &hitPoint) { // in what follows, I consider the texture as // aligned to the first edge. The texture is applied // properly only if the Quad is rectangular. TO DO: // make texture work in the general case. /* // These four things are what changes in the general case float minX = 0; float maxX = distance(_p1, _p2); float minY = 0; float maxY = distance(_p1, _p4); */ // int textureSize; // float objX, objY; // int i, j; // float red; // float green; // float blue; float specular = _material.GetSpec(); float specExp = _material.GetSpecExp(); Vector3 correctedNormal = normal; // flip normal if it points away from viewer in // the case of a dielectric // if ((_material.GetDielectric()) & (Dot(eyeDirection, normal) < 0.0)) // correctedNormal = -correctedNormal; // compute direction of reflected light float factor = 2 * Dot(correctedNormal, lightDirection); float x = correctedNormal.GetX() * factor - lightDirection.GetX(); float y = correctedNormal.GetY() * factor - lightDirection.GetY(); float z = correctedNormal.GetZ() * factor - lightDirection.GetZ(); float dotProd = x * eyeDirection.GetX() + y * eyeDirection.GetY() + z * eyeDirection.GetZ(); // return black if no light hits point if (dotProd < 0.0) return RGBColor(0.0, 0.0, 0.0); else { float factor = (float) pow(dotProd, specExp); // metals have highlights of their own colors if (_material.GetMetal()) { // case of no texture if (_material.GetTexture() == 0) { x = specular * lightColor.GetRed() * factor * _material.GetMaterialColor().GetRed(); y = specular * lightColor.GetGreen() * factor * _material.GetMaterialColor().GetGreen(); z = specular * lightColor.GetBlue() * factor * _material.GetMaterialColor().GetBlue(); } /* else // texture is applied { // determine coordinates of hitpoint relative to // the lower side of the rectangle; these are going // to map directly to the texture objX = projection(_p1, _p2, hitPoint); objY = distanceFromLine(_p1, _p2, hitPoint); switch (_material.GetTexture()) { case 1: textureSize = satinSize; // find corresponding bitmap coordinates (range from 0 to textureSize1) // by doing a simple windowing transform i = (int)(objX - minX) * ((textureSize - 1)/(maxX - minX)); j = (int)(objY - minY) * ((textureSize - 1)/(maxY - minY)); // find colors in satin bitmap file corresponding to bitmap coordinates red = (float)satin[3*(i + textureSize*j) + RED]/255; green = (float)satin[3*(i + textureSize*j) + GREEN]/255; blue = (float)satin[3*(i + textureSize*j) + BLUE]/255; x = specular * lightColor.GetRed() * factor * red; y = specular * lightColor.GetGreen() * factor * green; z = specular * lightColor.GetBlue() * factor * blue; break; case 2: textureSize = rugSize; // find corresponding bitmap coordinates (range from 0 to textureSize1) // by doing a simple windowing transform i = (int)(objX - minX) * ((textureSize - 1)/(maxX - minX)); j = (int)(objY - minY) * ((textureSize - 1)/(maxY - minY)); // find colors in satin bitmap file corresponding to bitmap coordinates red = (float)rug[3*(i + textureSize*j) + RED]/255; green = (float)rug[3*(i + textureSize*j) + GREEN]/255; blue = (float)rug[3*(i + textureSize*j) + BLUE]/255; x = specular * lightColor.GetRed() * factor * red; y = specular * lightColor.GetGreen() * factor * green; z = specular * lightColor.GetBlue() * factor * blue; break; case 3: textureSize = metalSize; // find corresponding bitmap coordinates (range from 0 to textureSize1) // by doing a simple windowing transform i = (int)(objX - minX) * ((textureSize - 1)/(maxX - minX)); j = (int)(objY - minY) * ((textureSize - 1)/(maxY - minY)); // find colors in satin bitmap file corresponding to bitmap coordinates red = (float)metal[3*(i + textureSize*j) + RED]/255; green = (float)metal[3*(i + textureSize*j) + GREEN]/255; blue = (float)metal[3*(i + textureSize*j) + BLUE]/255; x = specular * lightColor.GetRed() * factor * red; y = specular * lightColor.GetGreen() * factor * green; z = specular * lightColor.GetBlue() * factor * blue; break; } // end switch } // end else */ } // end if // non-metals have highlights determined by light color uniquely else { x = specular * lightColor.GetRed() * factor; y = specular * lightColor.GetGreen() * factor; z = specular * lightColor.GetBlue() * factor; } return RGBColor(x, y, z); } } ostream& operator << (ostream &os, const Sphere &S) { return os << "\nsphere center: ("<> (istream &is, Sphere &S) { Point3 center; is >> center; S.SetCenter(center); float radius; is >> radius; S.SetRadius(radius); Material M; is >> M; S.SetMaterial(M); return is; } // Triangle class member functions ************************************** Triangle::Triangle() { a = b = c = d = e = f = 0.0; } Triangle::Triangle(Point3 p1, Point3 p2, Point3 p3, Material TriangleMat) { _p1 = p1; _p2 = p2; _p3 = p3; _normal = Cross(p3 - p2, p1 - p2); _normal.Normalize(); // compute quantities useful for // intersection calculations a = _p1.GetX() - _p2.GetX(); b = _p1.GetY() - _p2.GetY(); c = _p1.GetZ() - _p2.GetZ(); d = _p1.GetX() - _p3.GetX(); e = _p1.GetY() - _p3.GetY(); f = _p1.GetZ() - _p3.GetZ(); _material = TriangleMat; // a flat surface can never be dielectric if(_material.GetDielectric()) _material.SetDielectric(false); } Triangle::Triangle(const Triangle &toBeCopied) { _p1 = toBeCopied.GetP1(); _p2 = toBeCopied.GetP2(); _p3 = toBeCopied.GetP3(); _normal = toBeCopied.GetNormal(); _material = toBeCopied.GetMaterial(); // compute quantities useful for // intersection calculations a = _p1.GetX() - _p2.GetX(); b = _p1.GetY() - _p2.GetY(); c = _p1.GetZ() - _p2.GetZ(); d = _p1.GetX() - _p3.GetX(); e = _p1.GetY() - _p3.GetY(); f = _p1.GetZ() - _p3.GetZ(); } // This code follows the algorithm explained in Peter // Shirley's book "Realistic Ray Tracing", Chapter 2. bool Triangle::Hit(const Ray &ray, Point3 &point, const float &minT, const float &maxT, float &t, Vector3 &norm) { g = ray.GetRayDirection().GetX(); h = ray.GetRayDirection().GetY(); i = ray.GetRayDirection().GetZ(); j = _p1.GetX() - ray.GetOrigin().GetX(); k = _p1.GetY() - ray.GetOrigin().GetY(); l = _p1.GetZ() - ray.GetOrigin().GetZ(); eihf = (e*i - h*f); gfdi = (g*f - d*i); dheg = (d*h - e*g); akjb = (a*k - j*b); jcal = (j*c - a*l); blkc = (b*l - k*c); // determinant for Cramer's rule float M = a * eihf + b * gfdi + c * dheg; if (isZero(M)) return false; else { float beta = j * eihf + k * gfdi + l * dheg; float gamma= i * akjb + h * jcal + g * blkc; t = f * akjb + e * jcal + d * blkc; beta /= M; gamma /= M; t /= -M; // these are the conditions for a hit if ((t > minT) & (t < maxT) & (beta + gamma <= 1) & (beta >= 0) & (gamma >= 0)) { RayPoint(ray, t, point); norm = _normal; return true; } return false; } } // This is the same as the previous function, but does less calculations. // This is useful for shadowing purposes, where we are not interested to // know the t value and the surface normal. bool Triangle::QuickHit(const Ray &ray, const float &minT, const float &maxT) { float t; g = ray.GetRayDirection().GetX(); h = ray.GetRayDirection().GetY(); i = ray.GetRayDirection().GetZ(); j = _p1.GetX() - ray.GetOrigin().GetX(); k = _p1.GetY() - ray.GetOrigin().GetY(); l = _p1.GetZ() - ray.GetOrigin().GetZ(); eihf = (e*i - h*f); gfdi = (g*f - d*i); dheg = (d*h - e*g); akjb = (a*k - j*b); jcal = (j*c - a*l); blkc = (b*l - k*c); // determinant for Cramer's rule float M = a * eihf + b * gfdi + c * dheg; if (isZero(M)) return false; else { float beta = j * eihf + k * gfdi + l * dheg; float gamma= i * akjb + h * jcal + g * blkc; t = f * akjb + e * jcal + d * blkc; beta /= M; gamma /= M; t /= -M; if ((t > minT) & (t < maxT) & (beta + gamma <= 1) & (beta >= 0) & (gamma >= 0)) return true; else return false; } } void Triangle::Translate(float X, float Y, float Z) { _p1.SetX(_p1.GetX() + X); _p1.SetY(_p1.GetY() + Y); _p1.SetZ(_p1.GetZ() + Z); _p2.SetX(_p2.GetX() + X); _p2.SetY(_p2.GetY() + Y); _p2.SetZ(_p2.GetZ() + Z); _p3.SetX(_p3.GetX() + X); _p3.SetY(_p3.GetY() + Y); _p3.SetZ(_p3.GetZ() + Z); } ostream& operator << (ostream &os, const Triangle &T) { return os << "\nTriangle. P1: " << T.GetP1() << ", P2: " << T.GetP2() << ", P3: " << T.GetP3() << ", \nmaterial: " << T.GetMaterial(); } istream& operator >> (istream &is, Triangle &T) { Point3 p1, p2, p3; is >> p1 >> p2 >> p3; T._normal = Cross(p3 - p2, p1 - p2); T._normal.Normalize(); T.SetP1(p1); T.SetP2(p2); T.SetP3(p3); T.a = T.GetP1().GetX() - T.GetP2().GetX(); T.b = T.GetP1().GetY() - T.GetP2().GetY(); T.c = T.GetP1().GetZ() - T.GetP2().GetZ(); T.d = T.GetP1().GetX() - T.GetP3().GetX(); T.e = T.GetP1().GetY() - T.GetP3().GetY(); T.f = T.GetP1().GetZ() - T.GetP3().GetZ(); Material M; is >> M; T.SetMaterial(M); // a flat surface can never be dielectric if(T.GetMaterial().GetDielectric()) T.GetMaterial().SetDielectric(false); return is; } RGBColor Triangle::AmbientLight(const Point3 &hitPoint) { // in what follows, I consider the texture as // aligned to the first edge. // These four things are what changes in the general case float minX = 0; float maxX = distance(_p1, _p2); float minY = 0; float maxY = distanceFromLine(_p1, _p2, _p3); int textureSize; float objX, objY; int i, j; float x, y, z; float red; float green; float blue; float ambient = _material.GetAmbi(); // case of no texture if (_material.GetTexture() == 0) { x = _material.GetMaterialColor().GetRed() * ambient * AMBIENT_RED; y = _material.GetMaterialColor().GetGreen()* ambient * AMBIENT_GREEN; z = _material.GetMaterialColor().GetBlue() * ambient * AMBIENT_BLUE; } else // texture is applied { // determine coordinates of hitpoint relative to // the lower side of the rectangle; these are going // to map directly to the texture objX = projection(_p1, _p2, hitPoint); objY = distanceFromLine(_p1, _p2, hitPoint); switch (_material.GetTexture()) { case 1: textureSize = satinSize; // find corresponding bitmap coordinates (range from 0 to textureSize1) // by doing a simple windowing transform i = (int)(objX - minX) * ((textureSize - 1)/(maxX - minX)); j = (int)(objY - minY) * ((textureSize - 1)/(maxY - minY)); // find colors in satin bitmap file corresponding to bitmap coordinates red = (float)satin[3*(i + textureSize*j) + RED]/255; green = (float)satin[3*(i + textureSize*j) + GREEN]/255; blue = (float)satin[3*(i + textureSize*j) + BLUE]/255; x = red * ambient * AMBIENT_RED; y = green* ambient * AMBIENT_GREEN; z = blue * ambient * AMBIENT_BLUE; break; case 2: textureSize = rugSize; // find corresponding bitmap coordinates (range from 0 to textureSize1) // by doing a simple windowing transform i = (int)(objX - minX) * ((textureSize - 1)/(maxX - minX)); j = (int)(objY - minY) * ((textureSize - 1)/(maxY - minY)); // find colors in satin bitmap file corresponding to bitmap coordinates red = (float)rug[3*(i + textureSize*j) + RED]/255; green = (float)rug[3*(i + textureSize*j) + GREEN]/255; blue = (float)rug[3*(i + textureSize*j) + BLUE]/255; x = red * ambient * AMBIENT_RED; y = green* ambient * AMBIENT_GREEN; z = blue * ambient * AMBIENT_BLUE; break; case 3: textureSize = metalSize; // find corresponding bitmap coordinates (range from 0 to textureSize1) // by doing a simple windowing transform i = (int)(objX - minX) * ((textureSize - 1)/(maxX - minX)); j = (int)(objY - minY) * ((textureSize - 1)/(maxY - minY)); // find colors in satin bitmap file corresponding to bitmap coordinates red = (float)metal[3*(i + textureSize*j) + RED]/255; green = (float)metal[3*(i + textureSize*j) + GREEN]/255; blue = (float)metal[3*(i + textureSize*j) + BLUE]/255; x = red * ambient * AMBIENT_RED; y = green* ambient * AMBIENT_GREEN; z = blue * ambient * AMBIENT_BLUE; break; } } return RGBColor(x, y, z); } RGBColor Triangle::DiffuseLight(const Vector3 &normal, const Vector3 &lightDirection, const RGBColor &lightColor, const Vector3 &eyeDirection, const Point3 &hitPoint) { // in what follows, I consider the texture as // aligned to the first edge. float minX = 0; float maxX = distance(_p1, _p2); float minY = 0; float maxY = distanceFromLine(_p1, _p2, _p3); int textureSize; float objX, objY; int i, j; float red; float green; float blue; float diffuse = _material.GetDiff(); // dot product of normal and light float prod = Dot(normal, lightDirection); // shadow part: return black color if (prod < 0.0) return RGBColor(0.0, 0.0, 0.0); else { float x, y, z; // case of no texture if (_material.GetTexture() == 0) { x = _material.GetMaterialColor().GetRed() * diffuse * lightColor.GetRed() * prod; y = _material.GetMaterialColor().GetGreen() * diffuse * lightColor.GetGreen()* prod; z = _material.GetMaterialColor().GetBlue() * diffuse * lightColor.GetBlue() * prod; } else // texture is applied { // determine coordinates of hitpoint relative to // the lower side of the rectangle; these are going // to map directly to the texture objX = projection(_p1, _p2, hitPoint); objY = distanceFromLine(_p1, _p2, hitPoint); switch (_material.GetTexture()) { case 1: textureSize = satinSize; // find corresponding bitmap coordinates (range from 0 to textureSize1) // by doing a simple windowing transform i = (int)(objX - minX) * ((textureSize - 1)/(maxX - minX)); j = (int)(objY - minY) * ((textureSize - 1)/(maxY - minY)); // find colors in satin bitmap file corresponding to bitmap coordinates red = (float)satin[3*(i + textureSize*j) + RED]/255; green = (float)satin[3*(i + textureSize*j) + GREEN]/255; blue = (float)satin[3*(i + textureSize*j) + BLUE]/255; x = red * diffuse * lightColor.GetRed() * prod; y = green* diffuse * lightColor.GetGreen() * prod; z = blue * diffuse * lightColor.GetBlue() * prod; break; case 2: textureSize = rugSize; // find corresponding bitmap coordinates (range from 0 to textureSize1) // by doing a simple windowing transform i = (int)(objX - minX) * ((textureSize - 1)/(maxX - minX)); j = (int)(objY - minY) * ((textureSize - 1)/(maxY - minY)); // find colors in satin bitmap file corresponding to bitmap coordinates red = (float)rug[3*(i + textureSize*j) + RED]/255; green = (float)rug[3*(i + textureSize*j) + GREEN]/255; blue = (float)rug[3*(i + textureSize*j) + BLUE]/255; x = red * diffuse * lightColor.GetRed() * prod; y = green* diffuse * lightColor.GetGreen() * prod; z = blue * diffuse * lightColor.GetBlue() * prod; break; case 3: textureSize = metalSize; // find corresponding bitmap coordinates (range from 0 to textureSize1) // by doing a simple windowing transform i = (int)(objX - minX) * ((textureSize - 1)/(maxX - minX)); j = (int)(objY - minY) * ((textureSize - 1)/(maxY - minY)); // find colors in satin bitmap file corresponding to bitmap coordinates red = (float)metal[3*(i + textureSize*j) + RED]/255; green = (float)metal[3*(i + textureSize*j) + GREEN]/255; blue = (float)metal[3*(i + textureSize*j) + BLUE]/255; x = red * diffuse * lightColor.GetRed() * prod; y = green* diffuse * lightColor.GetGreen() * prod; z = blue * diffuse * lightColor.GetBlue() * prod; break; } } return RGBColor(x, y, z); } } // This uses the Phong model for specular highlights. If the material is metallic, // the highlight is influenced by the metal color. If not, the highlight depends // on the light color only. RGBColor Triangle::SpecularLight(const Vector3 &normal, const Vector3 &lightDirection, const RGBColor &lightColor, const Vector3 &eyeDirection, const Point3 &hitPoint) { // in what follows, I consider the texture as // aligned to the first edge. // These four things are what changes in the general case float minX = 0; float maxX = distance(_p1, _p2); float minY = 0; float maxY = distanceFromLine(_p1, _p2, _p3); int textureSize; float objX, objY; int i, j; float red; float green; float blue; float specular = _material.GetSpec(); float specExp = _material.GetSpecExp(); // compute direction of reflected light float factor = 2 * Dot(normal, lightDirection); float x = normal.GetX() * factor - lightDirection.GetX(); float y = normal.GetY() * factor - lightDirection.GetY(); float z = normal.GetZ() * factor - lightDirection.GetZ(); float dotProd = x * eyeDirection.GetX() + y * eyeDirection.GetY() + z * eyeDirection.GetZ(); // return black if no light hits point if (dotProd < 0.0) return RGBColor(0.0, 0.0, 0.0); else { float factor = (float) pow(dotProd, specExp); // metals have highlights of their own colors if (_material.GetMetal()) { // case of no texture if (_material.GetTexture() == 0) { x = specular * lightColor.GetRed() * factor * _material.GetMaterialColor().GetRed(); y = specular * lightColor.GetGreen() * factor * _material.GetMaterialColor().GetGreen(); z = specular * lightColor.GetBlue() * factor * _material.GetMaterialColor().GetBlue(); } else // texture is applied { // determine coordinates of hitpoint relative to // the lower side of the rectangle; these are going // to map directly to the texture objX = projection(_p1, _p2, hitPoint); objY = distanceFromLine(_p1, _p2, hitPoint); switch (_material.GetTexture()) { case 1: textureSize = satinSize; // find corresponding bitmap coordinates (range from 0 to textureSize1) // by doing a simple windowing transform i = (int)(objX - minX) * ((textureSize - 1)/(maxX - minX)); j = (int)(objY - minY) * ((textureSize - 1)/(maxY - minY)); // find colors in satin bitmap file corresponding to bitmap coordinates red = (float)satin[3*(i + textureSize*j) + RED]/255; green = (float)satin[3*(i + textureSize*j) + GREEN]/255; blue = (float)satin[3*(i + textureSize*j) + BLUE]/255; x = specular * lightColor.GetRed() * factor * red; y = specular * lightColor.GetGreen() * factor * green; z = specular * lightColor.GetBlue() * factor * blue; break; case 2: textureSize = rugSize; // find corresponding bitmap coordinates (range from 0 to textureSize1) // by doing a simple windowing transform i = (int)(objX - minX) * ((textureSize - 1)/(maxX - minX)); j = (int)(objY - minY) * ((textureSize - 1)/(maxY - minY)); // find colors in satin bitmap file corresponding to bitmap coordinates red = (float)rug[3*(i + textureSize*j) + RED]/255; green = (float)rug[3*(i + textureSize*j) + GREEN]/255; blue = (float)rug[3*(i + textureSize*j) + BLUE]/255; x = specular * lightColor.GetRed() * factor * red; y = specular * lightColor.GetGreen() * factor * green; z = specular * lightColor.GetBlue() * factor * blue; break; case 3: textureSize = metalSize; // find corresponding bitmap coordinates (range from 0 to textureSize1) // by doing a simple windowing transform i = (int)(objX - minX) * ((textureSize - 1)/(maxX - minX)); j = (int)(objY - minY) * ((textureSize - 1)/(maxY - minY)); // find colors in satin bitmap file corresponding to bitmap coordinates red = (float)metal[3*(i + textureSize*j) + RED]/255; green = (float)metal[3*(i + textureSize*j) + GREEN]/255; blue = (float)metal[3*(i + textureSize*j) + BLUE]/255; x = specular * lightColor.GetRed() * factor * red; y = specular * lightColor.GetGreen() * factor * green; z = specular * lightColor.GetBlue() * factor * blue; break; } // end switch } // end else } // end if // non-metals have highlights determined by light color uniquely else { x = specular * lightColor.GetRed() * factor; y = specular * lightColor.GetGreen() * factor; z = specular * lightColor.GetBlue() * factor; } return RGBColor(x, y, z); } } // Quad class member functions ************************************** Quad::Quad(Point3 p1, Point3 p2, Point3 p3, Point3 p4, Material quadMaterial) { _p1 = p1; _p2 = p2; _p3 = p3; _p4 = p4; _normal = Cross(p2 - p1, p4 - p1); _normal.Normalize(); _material = quadMaterial; // a flat surface can never be dielectric if(quadMaterial.GetDielectric()) _material.SetDielectric(false); // compute plane coefficients _A = _normal.GetX(); _B = _normal.GetY(); _C = _normal.GetZ(); _D = -_A*_p1.GetX() - _B*_p1.GetY() - _C*_p1.GetZ(); } Quad::Quad(const Quad &toBeCopied) { _p1 = toBeCopied.GetP1(); _p2 = toBeCopied.GetP2(); _p3 = toBeCopied.GetP3(); _p4 = toBeCopied.GetP4(); _normal = toBeCopied.GetNormal(); _material = toBeCopied.GetMaterial(); // a flat surface can never be dielectric if(_material.GetDielectric()) _material.SetDielectric(false); _A = toBeCopied._A; _B = toBeCopied._B; _C = toBeCopied._C; _D = -_A*_p1.GetX() - _B*_p1.GetY() - _C*_p1.GetZ(); } bool Quad::Hit(const Ray &ray, Point3 &point, const float &minT, const float &maxT, float &t, Vector3 &normal) { // first determine if the ray is hitting the plane in which // the quad is embedded. This happens if a t satisfies: // A(o1 + t*d1) + B(o2 + t*d2) + C(o3 + t*d3) + D = 0, where // o is the ray's origin and d its direction. Rearranging // this equation gives: t = -(Ao1 + Bo2 + co3)/(normal*d) float denominator = Dot(_normal, ray.GetRayDirection()); float potentialT; // case of a ray that is parallel to the plane if (isZero(denominator)) return false; else { float numerator = -(_A*ray.GetOrigin().GetX() + _B*ray.GetOrigin().GetY() + _C*ray.GetOrigin().GetZ() + _D); potentialT = numerator / denominator; // case where the t is outside the bounds if ((potentialT < minT) | (potentialT > maxT)) return false; } // if we have reached here, it means we have intersected the plane of the Quad. // we now need to determine whether we have hit inside or outside the Quad. // compute the hitPoint (which could be either inside or outside the quad) // We first call RayPoint with a potential hit point that is a local variable // since we do not want to alter the reference parameter "point" before knowing // for sure that this is an internal hit. Point3 potentialHit; RayPoint(ray, potentialT, potentialHit); // we project the Quad upon a main plane (XY, YZ or XZ) by // "forgetting" one coordinate to simplify the problem float x1, y1, x2, y2, x3, y3, x4, y4, hitX, hitY; // "forget" z in this case if(fabsf(_C) > 0.5) { x1 = _p1.GetX(); y1 = _p1.GetY(); x2 = _p2.GetX(); y2 = _p2.GetY(); x3 = _p3.GetX(); y3 = _p3.GetY(); x4 = _p4.GetX(); y4 = _p4.GetY(); hitX = potentialHit.GetX(); hitY = potentialHit.GetY(); } // "forget" x in this case else if(fabsf(_A) > 0.5) { x1 = _p1.GetY(); y1 = _p1.GetZ(); x2 = _p2.GetY(); y2 = _p2.GetZ(); x3 = _p3.GetY(); y3 = _p3.GetZ(); x4 = _p4.GetY(); y4 = _p4.GetZ(); hitX = potentialHit.GetY(); hitY = potentialHit.GetZ(); } else //"forget" y { x1 = _p1.GetX(); y1 = _p1.GetZ(); x2 = _p2.GetX(); y2 = _p2.GetZ(); x3 = _p3.GetX(); y3 = _p3.GetZ(); x4 = _p4.GetX(); y4 = _p4.GetZ(); hitX = potentialHit.GetX(); hitY = potentialHit.GetZ(); } // to intersect a horizontal ray from (hitX, hitY), // it is useful to translate all the points such that // (hitX, hitY) = (0, 0) x1 = x1 - hitX; y1 = y1 - hitY; x2 = x2 - hitX; y2 = y2 - hitY; x3 = x3 - hitX; y3 = y3 - hitY; x4 = x4 - hitX; y4 = y4 - hitY; // after this translation, the new problem is: is // (0, 0) inside the Quad? We consider each side. // For instance, consider (x1, y1) and (x2, y2): // if y1 and y2 have the same sign, we have no // intersection. If the signs are different, we // test whether the intersection abscissa is strictly // positive, and we have an intersection only in this case. int numIntersections = 0; // consider (x1, y1) and (x2, y2) if ((y1*y2 < 0.0) & ((x1*y2 - x2*y1)*(y2 - y1) > 0.0)) numIntersections++; // consider (x2, y2) and (x3, y3) if ((y2*y3 < 0.0) & ((x2*y3 - x3*y2)*(y3 - y2) > 0.0)) numIntersections++; // consider (x3, y3) and (x4, y4) if ((y3*y4 < 0.0) & ((x3*y4 - x4*y3)*(y4 - y3) > 0.0)) numIntersections++; // consider (x4, y4) and (x1, y1) if ((y4*y1 < 0.0) & ((x4*y1 - x1*y4)*(y1 - y4) > 0.0)) numIntersections++; // if parity is odd then we have an interior point if(numIntersections % 2 == 1) { t = potentialT; normal = _normal; point = potentialHit; return true; } else return false; } bool Quad::QuickHit(const Ray &ray, const float &minT, const float &maxT) { // first determine if the ray is hitting the plane in which // the quad is embedded. This happens if a t satisfies: // A(o1 + t*d1) + B(o2 + t*d2) + C(o3 + t*d3) + D = 0, where // o is the ray's origin and d its direction. Rearranging // this equation gives: t = -(Ao1 + Bo2 + co3)/(normal*d) float denominator = Dot(_normal, ray.GetRayDirection()); float potentialT; // case of a ray that is parallel to the plane if (isZero(denominator)) return false; else { float numerator = -(_A*ray.GetOrigin().GetX() + _B*ray.GetOrigin().GetY() + _C*ray.GetOrigin().GetZ() + _D); potentialT = numerator / denominator; // case where the t is outside the bounds if ((potentialT < minT) | (potentialT > maxT)) return false; } // if we have reached here, it means we have intersected the plane of the Quad. // we now need to determine whether we have hit inside or outside the Quad. // compute the hitPoint (which could be either inside or outside) Point3 potentialHit; RayPoint(ray, potentialT, potentialHit); // we project the Quad upon a main plane (XY, YZ or XZ) by // "forgetting" one coordinate to simplify the problem float x1, y1, x2, y2, x3, y3, x4, y4, hitX, hitY; // "forget" z in this case if(fabsf(_C) > 0.5) { x1 = _p1.GetX(); y1 = _p1.GetY(); x2 = _p2.GetX(); y2 = _p2.GetY(); x3 = _p3.GetX(); y3 = _p3.GetY(); x4 = _p4.GetX(); y4 = _p4.GetY(); hitX = potentialHit.GetX(); hitY = potentialHit.GetY(); } // "forget" x in this case else if(fabsf(_A) > 0.5) { x1 = _p1.GetY(); y1 = _p1.GetZ(); x2 = _p2.GetY(); y2 = _p2.GetZ(); x3 = _p3.GetY(); y3 = _p3.GetZ(); x4 = _p4.GetY(); y4 = _p4.GetZ(); hitX = potentialHit.GetY(); hitY = potentialHit.GetZ(); } else //else "forget" y { x1 = _p1.GetX(); y1 = _p1.GetZ(); x2 = _p2.GetX(); y2 = _p2.GetZ(); x3 = _p3.GetX(); y3 = _p3.GetZ(); x4 = _p4.GetX(); y4 = _p4.GetZ(); hitX = potentialHit.GetX(); hitY = potentialHit.GetZ(); } // to intersect a horizontal ray from (hitX, hitY), // it is useful to translate all the points such that // (hitX, hitY) = (0, 0) x1 = x1 - hitX; y1 = y1 - hitY; x2 = x2 - hitX; y2 = y2 - hitY; x3 = x3 - hitX; y3 = y3 - hitY; x4 = x4 - hitX; y4 = y4 - hitY; // after this translation, the new problem is: is // (0, 0) inside the Quad? We consider each side. // For instance, consider (x1, y1) and (x2, y2): // if y1 and y2 have the same sign, we have no // intersection. If the signs are different, we // test whether the intersection abscissa is strictly // positive, and we have an intersection only in this case. int numIntersections = 0; // consider (x1, y1) and (x2, y2) if ((y1*y2 < 0.0) & ((x1*y2 - x2*y1)/(y2 - y1) > 0.0)) numIntersections++; // consider (x2, y2) and (x3, y3) if ((y2*y3 < 0.0) & ((x2*y3 - x3*y2)/(y3 - y2) > 0.0)) numIntersections++; // consider (x3, y3) and (x4, y4) if ((y3*y4 < 0.0) & ((x3*y4 - x4*y3)/(y4 - y3) > 0.0)) numIntersections++; // consider (x4, y4) and (x1, y1) if ((y4*y1 < 0.0) & ((x4*y1 - x1*y4)/(y1 - y4) > 0.0)) numIntersections++; // if parity is odd then we have an interior point return (numIntersections%2 == 1); } RGBColor Quad::AmbientLight(const Point3 &hitPoint) { // in what follows, I consider the texture as // aligned to the first edge. The texture is applied // properly only if the Quad is rectangular. TO DO: // make texture work in the general case. // These four things are what changes in the general case float minX = 0; float maxX = distance(_p1, _p2); float minY = 0; float maxY = distance(_p1, _p4); int textureSize; float objX, objY; int i, j; float x, y, z; float red; float green; float blue; float ambient = _material.GetAmbi(); // case of no texture if (_material.GetTexture() == 0) { x = _material.GetMaterialColor().GetRed() * ambient * AMBIENT_RED; y = _material.GetMaterialColor().GetGreen()* ambient * AMBIENT_GREEN; z = _material.GetMaterialColor().GetBlue() * ambient * AMBIENT_BLUE; } else // texture is applied { // determine coordinates of hitpoint relative to // the lower side of the rectangle; these are going // to map directly to the texture objX = projection(_p1, _p2, hitPoint); objY = distanceFromLine(_p1, _p2, hitPoint); switch (_material.GetTexture()) { case 1: textureSize = satinSize; // find corresponding bitmap coordinates (range from 0 to textureSize1) // by doing a simple windowing transform i = (int)(objX - minX) * ((textureSize - 1)/(maxX - minX)); j = (int)(objY - minY) * ((textureSize - 1)/(maxY - minY)); // find colors in satin bitmap file corresponding to bitmap coordinates red = (float)satin[3*(i + textureSize*j) + RED]/255; green = (float)satin[3*(i + textureSize*j) + GREEN]/255; blue = (float)satin[3*(i + textureSize*j) + BLUE]/255; x = red * ambient * AMBIENT_RED; y = green* ambient * AMBIENT_GREEN; z = blue * ambient * AMBIENT_BLUE; break; case 2: textureSize = rugSize; // find corresponding bitmap coordinates (range from 0 to textureSize1) // by doing a simple windowing transform i = (int)(objX - minX) * ((textureSize - 1)/(maxX - minX)); j = (int)(objY - minY) * ((textureSize - 1)/(maxY - minY)); // find colors in satin bitmap file corresponding to bitmap coordinates red = (float)rug[3*(i + textureSize*j) + RED]/255; green = (float)rug[3*(i + textureSize*j) + GREEN]/255; blue = (float)rug[3*(i + textureSize*j) + BLUE]/255; x = red * ambient * AMBIENT_RED; y = green* ambient * AMBIENT_GREEN; z = blue * ambient * AMBIENT_BLUE; break; case 3: textureSize = metalSize; // find corresponding bitmap coordinates (range from 0 to textureSize1) // by doing a simple windowing transform i = (int)(objX - minX) * ((textureSize - 1)/(maxX - minX)); j = (int)(objY - minY) * ((textureSize - 1)/(maxY - minY)); // find colors in satin bitmap file corresponding to bitmap coordinates red = (float)metal[3*(i + textureSize*j) + RED]/255; green = (float)metal[3*(i + textureSize*j) + GREEN]/255; blue = (float)metal[3*(i + textureSize*j) + BLUE]/255; x = red * ambient * AMBIENT_RED; y = green* ambient * AMBIENT_GREEN; z = blue * ambient * AMBIENT_BLUE; break; } } return RGBColor(x, y, z); } RGBColor Quad::DiffuseLight(const Vector3 &normal, const Vector3 &lightDirection, const RGBColor &lightColor, const Vector3 &eyeDirection, const Point3 &hitPoint) { // in what follows, I consider the texture as // aligned to the first edge. The texture is applied // properly only if the Quad is rectangular. TO DO: // make texture work in the general case. // These four things are what changes in the general case float minX = 0; float maxX = distance(_p1, _p2); float minY = 0; float maxY = distance(_p1, _p4); int textureSize; float objX, objY; int i, j; float red; float green; float blue; float diffuse = _material.GetDiff(); // dot product of normal and light float prod = Dot(normal, lightDirection); // shadow part: return black color if (prod < 0.0) return RGBColor(0.0, 0.0, 0.0); else { float x, y, z; // case of no texture if (_material.GetTexture() == 0) { x = _material.GetMaterialColor().GetRed() * diffuse * lightColor.GetRed() * prod; y = _material.GetMaterialColor().GetGreen() * diffuse * lightColor.GetGreen()* prod; z = _material.GetMaterialColor().GetBlue() * diffuse * lightColor.GetBlue() * prod; } else // texture is applied { // determine coordinates of hitpoint relative to // the lower side of the rectangle; these are going // to map directly to the texture objX = projection(_p1, _p2, hitPoint); objY = distanceFromLine(_p1, _p2, hitPoint); switch (_material.GetTexture()) { case 1: textureSize = satinSize; // find corresponding bitmap coordinates (range from 0 to textureSize1) // by doing a simple windowing transform i = (int)(objX - minX) * ((textureSize - 1)/(maxX - minX)); j = (int)(objY - minY) * ((textureSize - 1)/(maxY - minY)); // find colors in satin bitmap file corresponding to bitmap coordinates red = (float)satin[3*(i + textureSize*j) + RED]/255; green = (float)satin[3*(i + textureSize*j) + GREEN]/255; blue = (float)satin[3*(i + textureSize*j) + BLUE]/255; x = red * diffuse * lightColor.GetRed() * prod; y = green* diffuse * lightColor.GetGreen() * prod; z = blue * diffuse * lightColor.GetBlue() * prod; break; case 2: textureSize = rugSize; // find corresponding bitmap coordinates (range from 0 to textureSize1) // by doing a simple windowing transform i = (int)(objX - minX) * ((textureSize - 1)/(maxX - minX)); j = (int)(objY - minY) * ((textureSize - 1)/(maxY - minY)); // find colors in satin bitmap file corresponding to bitmap coordinates red = (float)rug[3*(i + textureSize*j) + RED]/255; green = (float)rug[3*(i + textureSize*j) + GREEN]/255; blue = (float)rug[3*(i + textureSize*j) + BLUE]/255; x = red * diffuse * lightColor.GetRed() * prod; y = green* diffuse * lightColor.GetGreen() * prod; z = blue * diffuse * lightColor.GetBlue() * prod; break; case 3: textureSize = metalSize; // find corresponding bitmap coordinates (range from 0 to textureSize1) // by doing a simple windowing transform i = (int)(objX - minX) * ((textureSize - 1)/(maxX - minX)); j = (int)(objY - minY) * ((textureSize - 1)/(maxY - minY)); // find colors in satin bitmap file corresponding to bitmap coordinates red = (float)metal[3*(i + textureSize*j) + RED]/255; green = (float)metal[3*(i + textureSize*j) + GREEN]/255; blue = (float)metal[3*(i + textureSize*j) + BLUE]/255; x = red * diffuse * lightColor.GetRed() * prod; y = green* diffuse * lightColor.GetGreen() * prod; z = blue * diffuse * lightColor.GetBlue() * prod; break; } } return RGBColor(x, y, z); } } // This uses the Phong model for specular highlights. If the material is metallic, // the highlight is influenced by the metal color. If not, the highlight depends // on the light color only. RGBColor Quad::SpecularLight(const Vector3 &normal, const Vector3 &lightDirection, const RGBColor &lightColor, const Vector3 &eyeDirection, const Point3 &hitPoint) { // in what follows, I consider the texture as // aligned to the first edge. The texture is applied // properly only if the Quad is rectangular. TO DO: // make texture work in the general case. // These four things are what changes in the general case float minX = 0; float maxX = distance(_p1, _p2); float minY = 0; float maxY = distance(_p1, _p4); int textureSize; float objX, objY; int i, j; float red; float green; float blue; float specular = _material.GetSpec(); float specExp = _material.GetSpecExp(); // compute direction of reflected light float factor = 2 * Dot(normal, lightDirection); float x = normal.GetX() * factor - lightDirection.GetX(); float y = normal.GetY() * factor - lightDirection.GetY(); float z = normal.GetZ() * factor - lightDirection.GetZ(); float dotProd = x * eyeDirection.GetX() + y * eyeDirection.GetY() + z * eyeDirection.GetZ(); // return black if no light hits point if (dotProd < 0.0) return RGBColor(0.0, 0.0, 0.0); else { float factor = (float) pow(dotProd, specExp); // metals have highlights of their own colors if (_material.GetMetal()) { // case of no texture if (_material.GetTexture() == 0) { x = specular * lightColor.GetRed() * factor * _material.GetMaterialColor().GetRed(); y = specular * lightColor.GetGreen() * factor * _material.GetMaterialColor().GetGreen(); z = specular * lightColor.GetBlue() * factor * _material.GetMaterialColor().GetBlue(); } else // texture is applied { // determine coordinates of hitpoint relative to // the lower side of the rectangle; these are going // to map directly to the texture objX = projection(_p1, _p2, hitPoint); objY = distanceFromLine(_p1, _p2, hitPoint); switch (_material.GetTexture()) { case 1: textureSize = satinSize; // find corresponding bitmap coordinates (range from 0 to textureSize1) // by doing a simple windowing transform i = (int)(objX - minX) * ((textureSize - 1)/(maxX - minX)); j = (int)(objY - minY) * ((textureSize - 1)/(maxY - minY)); // find colors in satin bitmap file corresponding to bitmap coordinates red = (float)satin[3*(i + textureSize*j) + RED]/255; green = (float)satin[3*(i + textureSize*j) + GREEN]/255; blue = (float)satin[3*(i + textureSize*j) + BLUE]/255; x = specular * lightColor.GetRed() * factor * red; y = specular * lightColor.GetGreen() * factor * green; z = specular * lightColor.GetBlue() * factor * blue; break; case 2: textureSize = rugSize; // find corresponding bitmap coordinates (range from 0 to textureSize1) // by doing a simple windowing transform i = (int)(objX - minX) * ((textureSize - 1)/(maxX - minX)); j = (int)(objY - minY) * ((textureSize - 1)/(maxY - minY)); // find colors in satin bitmap file corresponding to bitmap coordinates red = (float)rug[3*(i + textureSize*j) + RED]/255; green = (float)rug[3*(i + textureSize*j) + GREEN]/255; blue = (float)rug[3*(i + textureSize*j) + BLUE]/255; x = specular * lightColor.GetRed() * factor * red; y = specular * lightColor.GetGreen() * factor * green; z = specular * lightColor.GetBlue() * factor * blue; break; case 3: textureSize = metalSize; // find corresponding bitmap coordinates (range from 0 to textureSize1) // by doing a simple windowing transform i = (int)(objX - minX) * ((textureSize - 1)/(maxX - minX)); j = (int)(objY - minY) * ((textureSize - 1)/(maxY - minY)); // find colors in satin bitmap file corresponding to bitmap coordinates red = (float)metal[3*(i + textureSize*j) + RED]/255; green = (float)metal[3*(i + textureSize*j) + GREEN]/255; blue = (float)metal[3*(i + textureSize*j) + BLUE]/255; x = specular * lightColor.GetRed() * factor * red; y = specular * lightColor.GetGreen() * factor * green; z = specular * lightColor.GetBlue() * factor * blue; break; } // end switch } // end else } // end if // non-metals have highlights determined by light color uniquely else { x = specular * lightColor.GetRed() * factor; y = specular * lightColor.GetGreen() * factor; z = specular * lightColor.GetBlue() * factor; } return RGBColor(x, y, z); } } ostream& operator << (ostream &os, const Quad &Q) { return os << "\nQuad. P1: " << Q.GetP1() << ", P2: " << Q.GetP2() << ", \nP3: " << Q.GetP3() << ", P4: " << Q.GetP4()<<", \nnormal: "<< Q.GetNormal() << "\nmaterial: " << Q.GetMaterial(); } istream& operator >> (istream &is, Quad &Q) { Point3 p1, p2, p3, p4; Vector3 normal; Material M; is >> p1 >> p2 >> p3 >> p4 >> normal >> M; Q.SetP1(p1); Q.SetP2(p2); Q.SetP3(p3); Q.SetP4(p4); Q._normal = normal; Q._normal.Normalize(); Q.SetMaterial(M); // a flat surface can never be dielectric if(Q.GetMaterial().GetDielectric()) Q.GetMaterial().SetDielectric(false); Q._A = Q._normal.GetX(); Q._B = Q._normal.GetY(); Q._C = Q._normal.GetZ(); Q._D = -Q._A*Q.GetP1().GetX() - Q._B*Q.GetP1().GetY() - Q._C*Q.GetP1().GetZ(); return is; } // this is an ad hoc transform to rotate // and translate the train quads in correct // place for viewing. void Quad::TrainTransform() { // scale float scale = 20; _p1 = _p1 * scale; _p2 = _p2 * scale; _p3 = _p3 * scale; _p4 = _p4 * scale; // rotate 90 degrees around x axis float tempY1 = _p1.GetY(); float tempY2 = _p2.GetY(); float tempY3 = _p3.GetY(); float tempY4 = _p4.GetY(); _p1.SetY(_p1.GetZ()); _p2.SetY(_p2.GetZ()); _p3.SetY(_p3.GetZ()); _p4.SetY(_p4.GetZ()); _p1.SetZ(tempY1); _p2.SetZ(tempY2); _p3.SetZ(tempY3); _p4.SetZ(tempY4); // translate Point3 translation(-400.0, 52.0, -900.0); _p1 = _p1 + translation; _p2 = _p2 + translation; _p3 = _p3 + translation; _p4 = _p4 + translation; // recompute normal _normal = Cross(_p2 - _p1, _p4 - _p1); _normal.Normalize(); // compute plane coefficients _A = _normal.GetX(); _B = _normal.GetY(); _C = _normal.GetZ(); _D = -_A*_p1.GetX() - _B*_p1.GetY() - _C*_p1.GetZ(); } void Quad::Translate(float X, float Y, float Z) { _p1.SetX(_p1.GetX() + X); _p1.SetY(_p1.GetY() + Y); _p1.SetZ(_p1.GetZ() + Z); _p2.SetX(_p2.GetX() + X); _p2.SetY(_p2.GetY() + Y); _p2.SetZ(_p2.GetZ() + Z); _p3.SetX(_p3.GetX() + X); _p3.SetY(_p3.GetY() + Y); _p3.SetZ(_p3.GetZ() + Z); _p4.SetX(_p4.GetX() + X); _p4.SetY(_p4.GetY() + Y); _p4.SetZ(_p4.GetZ() + Z); } // rotate around the Y axis void Quad::RotateY(float radians) { float c = cos(radians); float s = sin(radians); _p1.SetX(_p1.GetX() * c + _p1.GetZ() * s); _p1.SetY(_p1.GetY()); _p1.SetZ(_p1.GetX() * -s + _p1.GetZ() * c); _p2.SetX(_p2.GetX() * c + _p2.GetZ() * s); _p2.SetY(_p2.GetY()); _p2.SetZ(_p2.GetX() * -s + _p2.GetZ() * c); _p3.SetX(_p3.GetX() * c + _p3.GetZ() * s); _p3.SetY(_p3.GetY()); _p3.SetZ(_p3.GetX() * -s + _p3.GetZ() * c); _p4.SetX(_p4.GetX() * c + _p4.GetZ() * s); _p4.SetY(_p4.GetY()); _p4.SetZ(_p4.GetX() * -s + _p4.GetZ() * c); // recompute normal _normal = Cross(_p2 - _p1, _p4 - _p1); _normal.Normalize(); // compute plane coefficients _A = _normal.GetX(); _B = _normal.GetY(); _C = _normal.GetZ(); _D = -_A*_p1.GetX() - _B*_p1.GetY() - _C*_p1.GetZ(); } // rotate around the Y axis void Triangle::RotateY(float radians) { float c = cos(radians); float s = sin(radians); _p1.SetX(_p1.GetX() * c + _p1.GetZ() * s); _p1.SetY(_p1.GetY()); _p1.SetZ(_p1.GetX() * -s + _p1.GetZ() * c); _p2.SetX(_p2.GetX() * c + _p2.GetZ() * s); _p2.SetY(_p2.GetY()); _p2.SetZ(_p2.GetX() * -s + _p2.GetZ() * c); _p3.SetX(_p3.GetX() * c + _p3.GetZ() * s); _p3.SetY(_p3.GetY()); _p3.SetZ(_p3.GetX() * -s + _p3.GetZ() * c); // recompute normal _normal = Cross(_p2 - _p1, _p3 - _p1); _normal.Normalize(); a = _p1.GetX() - _p2.GetX(); b = _p1.GetY() - _p2.GetY(); c = _p1.GetZ() - _p2.GetZ(); d = _p1.GetX() - _p3.GetX(); e = _p1.GetY() - _p3.GetY(); f = _p1.GetZ() - _p3.GetZ(); } // this is an ad hoc transform to rotate // and translate the train quads in correct // place for viewing. void Triangle::TrainTransform() { // scale float scale = 20; _p1 = _p1 * scale; _p2 = _p2 * scale; _p3 = _p3 * scale; // rotate 90 degrees around x axis float tempY1 = _p1.GetY(); float tempY2 = _p2.GetY(); float tempY3 = _p3.GetY(); _p1.SetY(_p1.GetZ()); _p2.SetY(_p2.GetZ()); _p3.SetY(_p3.GetZ()); _p1.SetZ(tempY1); _p2.SetZ(tempY2); _p3.SetZ(tempY3); // translate Point3 translation(-400.0, 52.0, -900.0); _p1 = _p1 + translation; _p2 = _p2 + translation; _p3 = _p3 + translation; // recompute normal _normal = Cross(_p2 - _p1, _p3 - _p1); _normal.Normalize(); a = _p1.GetX() - _p2.GetX(); b = _p1.GetY() - _p2.GetY(); c = _p1.GetZ() - _p2.GetZ(); d = _p1.GetX() - _p3.GetX(); e = _p1.GetY() - _p3.GetY(); f = _p1.GetZ() - _p3.GetZ(); } // Canvas class member functions **************************************** Canvas::Canvas(int width, int height) { _width = width; _height = height; // allocate 3 bytes of memory for each pixel, one for // each of the red, green and blue values _canvas = new GLubyte[3 * _width * _height]; } Canvas::~Canvas() { delete[] _canvas; } void Canvas::drawPixel(int x, int y, GLfloat red, GLfloat green, GLfloat blue) { // do not draw pixel of outside of canvas if ((x < 0) || (x >= _width) || (y < 0) || (y >= _height)) return; // set color of an individual pixel: we see that the memory // array contains the pixels line by line _canvas[3*_width*(y) + 3*(x) + RED] = (char)(red * 255); _canvas[3*_width*(y) + 3*(x) + GREEN] = (char)(green * 255); _canvas[3*_width*(y) + 3*(x) + BLUE] = (char)(blue * 255); } void Canvas::drawPixel(int x, int y, RGBColor &color) { // do not draw pixel of outside of canvas if ((x < 0) || (x >= _width) || (y < 0) || (y >= _height)) return; float red = color.GetRed(); float green = color.GetGreen(); float blue = color.GetBlue(); // set color of an individual pixel: we see that the memory // array contains the pixels line by line _canvas[3*_width*(y) + 3*(x) + RED] = (char)(red * 255); _canvas[3*_width*(y) + 3*(x) + GREEN] = (char)(green * 255); _canvas[3*_width*(y) + 3*(x) + BLUE] = (char)(blue * 255); } // draw the canvas on the screen using the OpenGL API void Canvas::flushCanvas() { glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glRasterPos3f(0.0,0.0,0.0); glDrawPixels(_width, _height, GL_RGB, GL_UNSIGNED_BYTE, _canvas); } // Light class member functions ********************************* // When this constructor is called with last parameter 0, it creates // a directional light, assuming that the first parameter Point3 is // the invariant light direction. When the last parameter is 1, it // creates a point light, assuming that the first parameter represents // an absolute light position. Light::Light(const Point3 &pos, RGBColor &col, const int &type) { _color = col; if(type == 0) { _type = DIRECTIONAL; _up.SetX(pos.GetX()); _up.SetY(pos.GetY()); _up.SetZ(pos.GetZ()); _up.Normalize(); } else { _type = POINT; _position = pos; } } // the "direction" function parameter is changed to a unit vector // pointing towards the light source after this is called. void Light::UnitVector(const Point3 &pos, Vector3 &direction) { // pointlight: light direction depends on where the observer is if(_type == POINT) { direction.SetX(_position.GetX() - pos.GetX()); direction.SetY(_position.GetY() - pos.GetY()); direction.SetZ(_position.GetZ() - pos.GetZ()); direction.Normalize(); } // directional light: light direction is invariant else { direction.SetX(_up.GetX()); direction.SetY(_up.GetY()); direction.SetZ(_up.GetZ()); } } ostream& operator << (ostream &os, const Light &T) { if (T._type == 0) { return os << "Directional light invariant direction: " << T._up << " , light color: " << T._color << "\n"; } else { return os << "Point light position: " << T._position << " , light color: " << T._color << "\n"; } } // This function is taken from the "OpenGL Bible" by Richard S. Wright Jr. // and Benjamin Lipschak, SAMS publishing, 2005. // Define targa header. #pragma pack(1) typedef struct { GLbyte identsize; // Size of ID field that follows header (0) GLbyte colorMapType; // 0 = None, 1 = paletted GLbyte imageType; // 0 = none, 1 = indexed, 2 = rgb, 3 = grey, +8=rle unsigned short colorMapStart; // First colour map entry unsigned short colorMapLength; // Number of colors unsigned char colorMapBits; // bits per palette entry unsigned short xstart; // image x origin unsigned short ystart; // image y origin unsigned short width; // width in pixels unsigned short height; // height in pixels GLbyte bits; // bits per pixel (8 16, 24, 32) GLbyte descriptor; // image descriptor } TGAHEADER; #pragma pack(8) //////////////////////////////////////////////////////////////////// // Capture the current viewport and save it as a targa file. // Be sure and call SwapBuffers for double buffered contexts or // glFinish for single buffered contexts before calling this function. // Returns 0 if an error occurs, or 1 on success. GLint gltWriteTGA(const char *szFileName) { FILE *pFile; // File pointer TGAHEADER tgaHeader; // TGA file header unsigned long lImageSize; // Size in bytes of image GLbyte *pBits = NULL; // Pointer to bits GLint iViewport[4]; // Viewport in pixels // GLenum lastBuffer; // Storage for the current read buffer setting GLint lastBuffer; // Storage for the current read buffer setting // Get the viewport dimensions glGetIntegerv(GL_VIEWPORT, iViewport); // How big is the image going to be (targas are tightly packed) lImageSize = iViewport[2] * 3 * iViewport[3]; // Allocate block. If this doesn't work, go home pBits = (GLbyte *)malloc(lImageSize); if(pBits == NULL) return 0; // Read bits from color buffer glPixelStorei(GL_PACK_ALIGNMENT, 1); glPixelStorei(GL_PACK_ROW_LENGTH, 0); glPixelStorei(GL_PACK_SKIP_ROWS, 0); glPixelStorei(GL_PACK_SKIP_PIXELS, 0); // Get the current read buffer setting and save it. Switch to // the front buffer and do the read operation. Finally, restore // the read buffer state glGetIntegerv(GL_READ_BUFFER, &lastBuffer); glReadBuffer(GL_FRONT); glReadPixels(0, 0, iViewport[2], iViewport[3], GL_BGR_EXT, GL_UNSIGNED_BYTE, pBits); glReadBuffer(lastBuffer); // Initialize the Targa header tgaHeader.identsize = 0; tgaHeader.colorMapType = 0; tgaHeader.imageType = 2; tgaHeader.colorMapStart = 0; tgaHeader.colorMapLength = 0; tgaHeader.colorMapBits = 0; tgaHeader.xstart = 0; tgaHeader.ystart = 0; tgaHeader.width = iViewport[2]; tgaHeader.height = iViewport[3]; tgaHeader.bits = 24; tgaHeader.descriptor = 0; // Do byte swap for big vs little endian #ifdef __APPLE__ BYTE_SWAP(tgaHeader.colorMapStart); BYTE_SWAP(tgaHeader.colorMapLength); BYTE_SWAP(tgaHeader.xstart); BYTE_SWAP(tgaHeader.ystart); BYTE_SWAP(tgaHeader.width); BYTE_SWAP(tgaHeader.height); #endif // Attempt to open the file pFile = fopen(szFileName, "wb"); if(pFile == NULL) { free(pBits); // Free buffer and return error return 0; } // Write the header fwrite(&tgaHeader, sizeof(TGAHEADER), 1, pFile); // Write the image data fwrite(pBits, lImageSize, 1, pFile); // Free temporary buffer and close the file free(pBits); fclose(pFile); // Success! return 1; }