// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include <math.h>
#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
#include <GL/gl.h>
#include "c3ga_util.h"
#include "light_source.h"
#include "ray.h"
#include "scene.h"
#include "surface_point.h"
#include "../shared.src/string_util.h"
#include "../shared.src/intersection_info.h"
using namespace c3ga;
light_source::light_source() :
m_spot_exponent(0.0f),
m_spot_cutoff(100.0f),
m_constant_attenuation(1.0f),
m_linear_attenuation(0.0f),
m_quadratic_attenuation(0.0f) {
}
light_source::light_source(const EXForm &xf,
const color &ambient_color,
const color &diffuse_color,
const color &specular_color,
mv::Float spot_exponent,
mv::Float spot_cutoff,
mv::Float constant_attenuation,
mv::Float linear_attenuation,
mv::Float quadratic_attenuation) : locator(xf),
m_ambient_color(ambient_color),
m_diffuse_color(diffuse_color),
m_specular_color(specular_color),
m_spot_exponent(spot_exponent),
m_spot_cutoff(spot_cutoff),
m_constant_attenuation(constant_attenuation),
m_linear_attenuation(linear_attenuation),
m_quadratic_attenuation(quadratic_attenuation) {
}
light_source::light_source(const light_source &l) : locator(l),
m_ambient_color(l.m_ambient_color),
m_diffuse_color(l.m_diffuse_color),
m_specular_color(l.m_specular_color),
m_spot_exponent(l.m_spot_exponent),
m_spot_cutoff(l.m_spot_cutoff),
m_constant_attenuation(l.m_constant_attenuation),
m_linear_attenuation(l.m_linear_attenuation),
m_quadratic_attenuation(l.m_quadratic_attenuation) {
}
light_source::~light_source() {
}
light_source &light_source::operator=(const light_source &l) {
if (this != & l) {
locator::operator=(l);
m_ambient_color = l.m_ambient_color;
m_diffuse_color = l.m_diffuse_color;
m_specular_color = l.m_specular_color;
m_spot_exponent = l.m_spot_exponent;
m_spot_cutoff = l.m_spot_cutoff;
m_constant_attenuation = l.m_constant_attenuation;
m_linear_attenuation = l.m_linear_attenuation;
m_quadratic_attenuation = l.m_quadratic_attenuation;
}
return *this;
}
void light_source::to_OpenGL(int OpenGL_light_idx) const {
// compute position of light, set GL_POSITION
// flatPoint light_pos = _flatPoint(get_xf() * noni *get_xfi());
flatPoint light_pos = _flatPoint(apply_om(get_M(), noni));
GLfloat gl_light_pos[4];
gl_light_pos[0] = _float(light_pos & e1ni);
gl_light_pos[1] = _float(light_pos & e2ni);
gl_light_pos[2] = _float(light_pos & e3ni);
gl_light_pos[3] = _float(light_pos & noni);
glLightfv(OpenGL_light_idx, GL_POSITION, gl_light_pos);
// set GL ambient, diffuse, specular light color:
get_ambient_color().glLight(OpenGL_light_idx, GL_AMBIENT);
get_diffuse_color().glLight(OpenGL_light_idx, GL_DIFFUSE);
get_specular_color().glLight(OpenGL_light_idx, GL_SPECULAR);
// set atternuation:
glLightf(OpenGL_light_idx, GL_CONSTANT_ATTENUATION, (float)get_constant_attenuation());
glLightf(OpenGL_light_idx, GL_LINEAR_ATTENUATION, (float)get_linear_attenuation());
glLightf(OpenGL_light_idx, GL_QUADRATIC_ATTENUATION, (float)get_quadratic_attenuation());
// get the direction of the light (it 'shines' along -e3)
// freeVector light_dir = _freeVector(get_xf() * nie3 *get_xfi());
freeVector light_dir = _freeVector(apply_om(get_M(), get_xfi()));
GLfloat gl_light_dir[4];
gl_light_dir[0] = _float(light_dir & e1ni);
gl_light_dir[1] = _float(light_dir & e2ni);
gl_light_dir[2] = _float(light_dir & e3ni);
gl_light_dir[3] = _float(light_dir & noni); // always 0
// set spot properties:
glLightf(OpenGL_light_idx, GL_SPOT_CUTOFF,
(GLfloat)((get_spot_cutoff() > 95.0f) ? 180.0f :
(GLfloat)((get_spot_cutoff() > 90.0f) ? 90.0f : get_spot_cutoff())));
glLightf(OpenGL_light_idx, GL_SPOT_EXPONENT, (GLfloat)get_spot_exponent());
glLightfv(OpenGL_light_idx, GL_SPOT_DIRECTION, gl_light_dir);
// done
}
void light_source::to_OpenGL_model() const {
int i;
// todo: clean up this dull mess a little if possible
// multiply current GL matrix with transform
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
apply_transform_OpenGL();
// set material
color(0.0f, 0.0f, 0.0f, 1.0f).glMaterial(GL_FRONT_AND_BACK, GL_AMBIENT);
color(0.0f, 0.0f, 0.0f, 1.0f).glMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE);
color(0.0f, 0.0f, 0.0f, 1.0f).glMaterial(GL_FRONT_AND_BACK, GL_SPECULAR);
color(0.6f, 0.6f, 0.6f, 1.0f).glMaterial(GL_FRONT_AND_BACK, GL_EMISSION);
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 0.0);
// coordinates for a little box (yawn):
const float scale = 0.1f;
GLfloat vertices[8][3] = {
{scale *-1.0f, scale *-1.0f, scale *0.0f},
{scale *1.0f, scale *-1.0f, scale *0.0f},
{scale *1.0f, scale *1.0f, scale *0.0f},
{scale *-1.0f, scale *1.0f, scale *0.0f},
{scale *-1.0f, scale *-1.0f, scale *2.0f},
{scale *1.0f, scale *-1.0f, scale *2.0f},
{scale *1.0f, scale *1.0f, scale *2.0f},
{scale *-1.0f, scale *1.0f, scale *2.0f}
};
int vtx_idx[6][4] = {
{0, 1, 2, 3},
{1, 5, 6, 2},
{4, 7, 6, 5},
{0, 3, 7, 4},
{0, 4, 5, 1},
{2, 6, 7, 3}
};
GLfloat normals[6][3] = {
{0.0f, 0.0f, -1.0f},
{1.0f, 0.0f, 0.0f},
{0.0f, 0.0f, 1.0f},
{-1.0f, 0.0f, 0.0f},
{0.0f, -1.0f, 0.0f},
{0.0f, 1.0f, 0.0f},
};
// draw a little box . . .
glBegin(GL_QUADS);
for ( i = 0; i < 6; i++) {
glNormal3fv(normals[i]);
for (int j = 3; j >= 0; j--) {
glVertex3fv(vertices[vtx_idx[i][j]]);
}
}
glEnd();
mv::Float cutoff = (mv::Float)M_PI * ((get_spot_cutoff() > 90.0f) ? 90.0f : get_spot_cutoff()) / 180.0f;
// draw flaps that indicate the width of the spotlight:
float _c = -(float)sin(cutoff);
float c[4][2] = {
{0.0f, _c},
{-_c, 0.0f},
{0.0f, -_c},
{_c, 0.0f}
};
float s = (float)cos(cutoff);
const float scale2 = 2.0f * scale;
glBegin(GL_QUADS);
for (i = 0; i < 4; i++) {
int j = (i+1)%4;
glVertex3fv(vertices[i]);
glVertex3fv(vertices[j]);
glVertex3f(vertices[j][0] + scale2 * c[i][0], vertices[j][1] + scale2 * c[i][1], vertices[j][2] - scale2 * s);
glVertex3f(vertices[i][0] + scale2 * c[i][0], vertices[i][1] + scale2 * c[i][1], vertices[i][2] - scale2 * s);
glVertex3f(vertices[i][0] + scale2 * c[i][0], vertices[i][1] + scale2 * c[i][1], vertices[i][2] - scale2 * s);
glVertex3f(vertices[j][0] + scale2 * c[i][0], vertices[j][1] + scale2 * c[i][1], vertices[j][2] - scale2 * s);
glVertex3fv(vertices[j]);
glVertex3fv(vertices[i]);
}
glEnd();
// undo the transform:
glPopMatrix();
}
bool light_source::can_scale() const {
return false;
}
void light_source::set_ambient_color(const color &c) {
m_ambient_color = c;
}
void light_source::set_diffuse_color(const color &c) {
m_diffuse_color = c;
}
void light_source::set_specular_color(const color &c) {
m_specular_color = c;
}
void light_source::set_color(const std::string colorname, const color &c) {
if ((colorname[0] == 'a') || (colorname[0] == 'A'))
set_ambient_color(c);
else if ((colorname[0] == 'd') || (colorname[0] == 'D'))
set_diffuse_color(c);
else if ((colorname[0] == 's') || (colorname[0] == 'S'))
set_specular_color(c);
}
std::string light_source::to_string() const {
std::string result;
result += "light_source_begin\n";
// xf
result += "light_source_xf " + locator::to_string() + "\n";
// spot
result += "spot_exponent " + ::to_string(m_spot_exponent) + "\n";
result += "spot_cutoff " + ::to_string(m_spot_cutoff) + "\n";
// attenuation
result += "constant_attenuation " + ::to_string(m_constant_attenuation) + "\n";
result += "linear_attenuation " + ::to_string(m_linear_attenuation) + "\n";
result += "quadratic_attenuation " + ::to_string(m_quadratic_attenuation) + "\n";
// color
result += "light_source_ambient_color "+ m_ambient_color.to_string() + "\n";
result += "light_source_diffuse_color "+ m_diffuse_color.to_string() + "\n";
result += "light_source_specular_color "+ m_specular_color.to_string() + "\n";
result += "light_source_end\n";
return result;
}
void light_source::set_spot_exponent(mv::Float f) {
m_spot_exponent = f;
}
void light_source::set_spot_cutoff(mv::Float c) {
if (c < 0.0f) m_spot_cutoff = 0.0f;
else if (c > 100.0f) m_spot_cutoff = 100.0f;
else m_spot_cutoff = c;
}
void light_source::set_constant_attenuation(mv::Float f) {
m_constant_attenuation = f;
}
void light_source::set_linear_attenuation(mv::Float f) {
m_linear_attenuation = f;
}
void light_source::set_quadratic_attenuation(mv::Float f) {
m_quadratic_attenuation = f;
}
color light_source::shade(const ray &R, const scene &S, const intersection_info *II, const surface_point &spt) const {
// perform basic 'OpenGL-style' lighting computations (+ a shadow check)...
// get 'viewing direction' towards the light as a unit free vector
const freeVector view_dir = _freeVector(-R.get_direction());
// get direction from surface point to light as unit free vector
freeVector light_dir = _freeVector(unit_e(get_position() - spt.get_pt()));
// evaluate the contribution of every light and add it to 'c'
// is the light visible?
bool light_visble;
if (II) light_visble = (II->get_light_visible(S.get_light_index(this)));
else {
surface_point tmp_spt;
// spawn a shadow ray:
light_visble = !S.find_intersection(ray(spt.get_pt(), light_dir), tmp_spt, false); // false means: don't search for closest point
}
color C; // this is where the shading computations are accumulated
if (light_visble) { // perform diffuse & specular lighting computations
// compute the diffuse term
float fd = _float(dual(spt.get_att()) & light_dir);
if (fd < 0.0f) fd = 0.0f;
const float terminator_hack_threshold = 0.1f; // hack to limit the 'terminator effect'
if (fd < terminator_hack_threshold) fd = (fd * fd * fd) / (terminator_hack_threshold * terminator_hack_threshold);
//printf("fd = %f,\n", fd);
// add diffuse term to 'C'
C += fd * get_diffuse_color() * spt.get_diffuse_color();
// compute the specular term
freeVector half_dir = _freeVector(unit_e(view_dir + light_dir));
float fs = _float(dual(spt.get_att()) & half_dir);
fs = (fs < 0.0f) ? 0.0f : (float)pow(fs, (float)spt.get_shininess());
// add specular term to 'C'
C += fs * get_specular_color() * spt.get_specular_color();
}
// add ambient term to 'C'
C += get_ambient_color() * spt.get_ambient_color();
// spotlight (matches OpenGL: it applies spotlight to ambient too).
if (get_spot_cutoff() < 95.0) {
// get cutoff in radians; get cosine of cutoff
float cutoff = (get_spot_cutoff() < 90.0f) ? (float)get_spot_cutoff() : 90.0f;
float cutoff_rad = (float)M_PI * cutoff / 180.0f;
// compute the direction the spot is shining in, as a free vector:
freeVector spot_dir = _freeVector(apply_om(get_M(), e3ni));
// compute the cosine of the angle between the light_dir and spot_dir
float cos_angle = _float(light_dir & spot_dir); // todo: simply use Euclidean version of inner product . . . (&)
// the point lies outside the cone
if (cos_angle < cos(cutoff_rad)) cos_angle = 0.0f;
// apply spotlight to color
C *= (float)pow(cos_angle, (float)get_spot_exponent());
}
/*
Attenuation:
-compute distance of light source to surface point
-scale 'C' by the attenuation factor
*/
float distance = (float)flat_point_distance(get_position(), spt.get_pt());
C *= 1.0f / ((float)get_constant_attenuation() +
(float)get_linear_attenuation() * distance +
(float)get_quadratic_attenuation() * distance * distance);
return C;
}