crt/lights.cpp
#include "lights.hpp"
#include "scene.hpp"
GlobalLight::GlobalLight(unsigned brightness)
: Light(true), val(MIN((light_t)brightness,100.0)/100.0) {
assert(val > 0.0 && val <= 1.0);
}
bool GlobalLight::reachable(const Object*) const {
return true;
}
bool GlobalLight::occlude_box(const Object* o, vertex_t& bound_min, vertex_t& bound_max) const {
return false;
}
light_t GlobalLight::get(std::vector<const Object*>&, const vertex_t&, const vertex_t*, const vertex_t&) const {
return val;
}
RayLight::RayLight(unsigned brightness)
: Light(false), intensity(MIN((light_t)brightness,100.0)/100.0) {
assert(intensity > 0.0 && intensity <= 1.0);
}
bool RayLight::occlude_box(const Object* o, vertex_t& bound_min, vertex_t& bound_max) const {
return false;
}
bool RayLight::reachable(const Object*) const {
return true;
}
light_t RayLight::get(std::vector<const Object*>&, const vertex_t&, const vertex_t* nray, const vertex_t& nnrm) const {
light_t fac = nray->dot(nnrm); // A * B = |A| * |B| * cos(A,B) - TODO: is not linear?
if (fac < 0.0) {
fac *= -1.0; // assume an angle between 0° and 90° is meant
}
return fac * intensity; // TODO: len dropoff?
}
PointLight::PointLight(const vertex_t& p, Img::rgb_t c, unsigned brightness, unsigned len)
: Light(true, c), pos(p), intensity(MIN((light_t)brightness,100.0)/100.0), maxlen(len), maxsqlen(len*len) {
assert(intensity > 0.0 && intensity <= 1.0);
}
bool PointLight::occlude_box(const Object* o, vertex_t& bound_min, vertex_t& bound_max) const {
bound_min = vertex_t::minof(o->bound_min, pos);
bound_max = vertex_t::maxof(o->bound_max, pos);
return true;
}
bool PointLight::reachable(const Object* o) const {
return o->sqdist(pos, maxlen, maxsqlen) < maxsqlen;
}
light_t PointLight::get(std::vector<const Object*>& objects, const vertex_t& hit, const vertex_t*, const vertex_t& nnrm) const {
vertex_t lray = pos-hit;
ray_t ray(hit, lray);
coord_t lsqlen = lray.sqlen();
if (lsqlen >= maxsqlen) { // dropoff <= 0.0
return 0.0;
}
light_t dropoff;
#ifdef PLIGHT_SQ_DROPOFF
dropoff = 1.0 - (lsqlen / maxsqlen);
#else
dropoff = 1.0 - (sqrt(lsqlen) / (coord_t)maxlen);
#endif
lray.norm();
const coord_t angle = nnrm.dot(lray);
if (angle <= 0.0) { // TODO: provide max angle (and direction?) to get a spotlight
return 0.0;
} else {
dropoff *= angle; // make configurable?
}
if (Scene::intersect(ray, objects)) {
return 0.0; // occluded
}
return intensity * dropoff;
}
AreaLight::AreaLight(const vertex_t& aa, const vertex_t& bb, unsigned brightness, unsigned len):
Light(true),
intensity(MIN((light_t)brightness, 100.0)/100.0),
maxlen(len), maxsqlen(pow2(len)), maxmaxlen(len+bb.len()), maxmaxsqlen(pow2(len+bb.len())),
bound_min(vertex_t::minof(aa, aa+bb)), bound_max(vertex_t::maxof(aa, aa+bb)),
samples(MAX(1, config.alight_samples.i)) {
minp = bound_min;
vertex_t dirp = bound_max - minp;
assert(dirp.nulls() == 1);
int a_os, b_os;
if (dirp.v[0] == 0.0) {
a_os = 1;
b_os = 2;
} else if (dirp.v[1] == 0.0) {
a_os = 0;
b_os = 2;
} else {
a_os = 0;
b_os = 1;
}
stepa.v[a_os] = dirp.v[a_os]/(samples+1);
stepb.v[b_os] = dirp.v[b_os]/(samples+1);
starta.v[a_os] = 0.5*stepa.v[a_os];
startb.v[b_os] = 0.5*stepb.v[b_os];
}
bool AreaLight::occlude_box(const Object* o, vertex_t& bmin, vertex_t& bmax) const {
bmin = vertex_t::minof(o->bound_min, bound_min);
bmax = vertex_t::maxof(o->bound_max, bound_max);
return true;
}
bool AreaLight::reachable(const Object* o) const {
return o->sqdist(minp, maxmaxlen, maxmaxsqlen) < maxmaxsqlen;
}
light_t AreaLight::get(std::vector<const Object*>& objects, const vertex_t& hit, const vertex_t*, const vertex_t& nnrm) const {
if (!samples) {
return 0.0;
}
const vertex_t dir = minp - hit;
coord_t dirsqlen = dir.sqlen();
if (dirsqlen > maxmaxsqlen) {
return 0.0; // too far
}
coord_t angle = nnrm.dot(dir.normed());
if (angle <= 0.0) {
return 0.0;
}
coord_t dists = 0.0;
coord_t angles = 0.0;
unsigned hits = 0;
ray_t ray(hit, vertex_t(0, 0, 0));
vertex_t aos = starta;
for (unsigned a=0; a<samples; ++a) {
vertex_t bos = startb;
for (unsigned b=0; b<samples; ++b) {
ray.direction = dir + aos + bos;
coord_t dist = ray.direction.sqlen();
if (dist <= maxsqlen) {
if (!Scene::intersect(ray, objects)) {
++hits;
#if 1
dist = sqrt(dist); // have to norm anyhow for angle
ray.direction /= dist;
angle = fabs(nnrm.dot(ray.direction)); // 0: 90/270, 1: 0/360
dists += dist/maxlen;
#else
dists += dist/maxsqlen;
#endif
angles += angle;
}
}
bos += stepb;
}
aos += stepa;
}
if (!hits) {
return 0.0; // occluded
}
dists = 1.0 - (dists/((coord_t)hits));
angles /= (coord_t)hits;
return intensity *
((light_t)hits/(light_t)(samples*samples)) *
dists *
(dists + (angles*(1.0-dists))); // s.t. angles get more important when far away (are always bigger when near)
}