276 lines
8.2 KiB
C++
276 lines
8.2 KiB
C++
/**
|
|
@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
|