/** @file GLight.cpp @maintainer Morgan McGuire, http://graphics.cs.williams.edu @created 2003-11-12 @edited 2009-11-16 */ #include "G3D/GLight.h" #include "G3D/Sphere.h" #include "G3D/CoordinateFrame.h" #include "G3D/Any.h" #include "G3D/stringutils.h" namespace G3D { GLight::GLight(const Any& any) { any.verifyName("GLight"); if (any.type() == Any::TABLE) { *this = GLight(); Vector3 spotTarget; bool hasSpotTarget = false; for (Any::AnyTable::Iterator it = any.table().begin(); it.hasMore(); ++it) { const std::string& key = toLower(it->key); if (key == "position") { position = it->value; } else if (key == "rightdirection") { rightDirection = it->value; } else if (key == "spotdirection") { spotDirection = Vector3(it->value).directionOrZero(); } else if (key == "spottarget") { spotTarget = it->value; hasSpotTarget = true; } else if (key == "spotcutoff") { spotCutoff = it->value.number(); } else if (key == "spotsquare") { spotSquare = it->value.boolean(); } else if (key == "attenuation") { attenuation[0] = it->value[0].number(); attenuation[1] = it->value[1].number(); attenuation[2] = it->value[2].number(); } else if (key == "color") { color = it->value; } else if (key == "enabled") { enabled = it->value.boolean(); } else if (key == "specular") { specular = it->value.boolean(); } else if (key == "diffuse") { diffuse = it->value.boolean(); } else { any.verify(false, "Illegal key: " + it->key); } } if (hasSpotTarget) { spotDirection = (spotTarget - position.xyz()).direction(); } } else if (toLower(any.name()) == "glight::directional") { *this = directional(any[0], any[1], (any.size() > 2) ? any[2] : Any(true), (any.size() > 3) ? any[3] : Any(true)); } else if (toLower(any.name()) == "glight::point") { *this = point(any[0], any[1], (any.size() > 2) ? any[2] : Any(1), (any.size() > 3) ? any[3] : Any(0), (any.size() > 4) ? any[4] : Any(0.5f), (any.size() > 5) ? any[5] : Any(true), (any.size() > 6) ? any[6] : Any(true)); } else if (toLower(any.name()) == "glight::spot") { *this = spot(any[0], any[1], any[2], any[3], (any.size() > 4) ? any[4] : Any(1), (any.size() > 5) ? any[5] : Any(0), (any.size() > 6) ? any[6] : Any(0), (any.size() > 7) ? any[7] : Any(true), (any.size() > 8) ? any[8] : Any(true)); } else { any.verify(false, "Unrecognized name"); } } GLight::operator Any() const { Any a(Any::TABLE, "GLight"); a.set("position", position.operator Any()); a.set("rightDirection", rightDirection.operator Any()); a.set("spotDirection", spotDirection.operator Any()); a.set("spotCutoff", spotCutoff); a.set("spotSquare", spotSquare); Any att(Any::ARRAY); att.append(attenuation[0], attenuation[1], attenuation[2]); a.set("attenuation", att); a.set("color", color.operator Any()); a.set("enabled", enabled); a.set("specular", specular); a.set("diffuse", diffuse); return a; } GLight::GLight() : position(0, 0, 0, 0), rightDirection(0,0,0), spotDirection(0, 0, -1), spotCutoff(180), spotSquare(false), color(Color3::white()), enabled(false), specular(true), diffuse(true) { attenuation[0] = 1.0; attenuation[1] = 0.0; attenuation[2] = 0.0; } GLight GLight::directional(const Vector3& toLight, const Color3& color, bool s, bool d) { GLight L; L.position = Vector4(toLight.direction(), 0); L.color = color; L.specular = s; L.diffuse = d; return L; } GLight GLight::point(const Vector3& pos, const Color3& color, float constAtt, float linAtt, float quadAtt, bool s, bool d) { GLight L; L.position = Vector4(pos, 1); L.color = color; L.attenuation[0] = constAtt; L.attenuation[1] = linAtt; L.attenuation[2] = quadAtt; L.specular = s; L.diffuse = d; return L; } GLight GLight::spot(const Vector3& pos, const Vector3& pointDirection, float cutOffAngleDegrees, const Color3& color, float constAtt, float linAtt, float quadAtt, bool s, bool d) { GLight L; L.position = Vector4(pos, 1.0f); L.spotDirection = pointDirection.direction(); debugAssert(cutOffAngleDegrees <= 90); L.spotCutoff = cutOffAngleDegrees; L.color = color; L.attenuation[0] = constAtt; L.attenuation[1] = linAtt; L.attenuation[2] = quadAtt; L.specular = s; L.diffuse = d; return L; } bool GLight::operator==(const GLight& other) const { return (position == other.position) && (rightDirection == other.rightDirection) && (spotDirection == other.spotDirection) && (spotCutoff == other.spotCutoff) && (spotSquare == other.spotSquare) && (attenuation[0] == other.attenuation[0]) && (attenuation[1] == other.attenuation[1]) && (attenuation[2] == other.attenuation[2]) && (color == other.color) && (enabled == other.enabled) && (specular == other.specular) && (diffuse == other.diffuse); } bool GLight::operator!=(const GLight& other) const { return !(*this == other); } Sphere GLight::effectSphere(float cutoff) const { if (position.w == 0) { // Directional light return Sphere(Vector3::zero(), finf()); } else { // Avoid divide by zero cutoff = max(cutoff, 0.00001f); float maxIntensity = max(color.r, max(color.g, color.b)); float radius = finf(); if (attenuation[2] != 0) { // Solve I / attenuation.dot(1, r, r^2) < cutoff for r // // a[0] + a[1] r + a[2] r^2 > I/cutoff // float a = attenuation[2]; float b = attenuation[1]; float c = attenuation[0] - maxIntensity / cutoff; float discrim = square(b) - 4 * a * c; if (discrim >= 0) { discrim = sqrt(discrim); float r1 = (-b + discrim) / (2 * a); float r2 = (-b - discrim) / (2 * a); if (r1 < 0) { if (r2 > 0) { radius = r2; } } else if (r2 > 0) { radius = min(r1, r2); } else { radius = r1; } } } else if (attenuation[1] != 0) { // Solve I / attenuation.dot(1, r) < cutoff for r // // r * a[1] + a[0] = I / cutoff // r = (I / cutoff - a[0]) / a[1] float radius = (maxIntensity / cutoff - attenuation[0]) / attenuation[1]; radius = max(radius, 0.0f); } return Sphere(position.xyz(), radius); } } CoordinateFrame GLight::frame() const { CoordinateFrame f; if (rightDirection == Vector3::zero()) { // No specified right direction; choose one automatically if (position.w == 0) { // Directional light f.lookAt(-position.xyz()); } else { // Spot light f.lookAt(spotDirection); } } else { const Vector3& Z = -spotDirection.direction(); Vector3 X = rightDirection.direction(); // Ensure the vectors are not too close together while (abs(X.dot(Z)) > 0.9f) { X = Vector3::random(); } // Ensure perpendicular X -= Z * Z.dot(X); const Vector3& Y = Z.cross(X); f.rotation.setColumn(Vector3::X_AXIS, X); f.rotation.setColumn(Vector3::Y_AXIS, Y); f.rotation.setColumn(Vector3::Z_AXIS, Z); } f.translation = position.xyz(); return f; } } // G3D