// 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>
#include <time.h>
#include "../shared.src/string_util.h"
#include "../shared.src/mt19937-2.h"
#include "../shared.src/intersection_info.h"
#include "scene.h"
#include "ui.h"
#include "ray.h"
#include "c3ga_util.h"
#include "surface_point.h"
using namespace c3ga;
scene::scene() : m_current_camera(0), m_random_seed(0), m_display_lights(true) {
// set render camera
m_camera.push_back(camera(_EXForm(1.0), 1.0)); // first 1.0 is identify transform, second 1.0 is fov_width
// set model camera
m_camera.push_back(camera(_EXForm(1.0), 1.0)); // first 1.0 is identify transform, second 1.0 is fov_width
// add a default light source:
add_light_source(
light_source(
_EXForm(1.0), // xf
color(0.8f, 0.8f, 0.8f), // ambient_color
color(0.7f, 0.7f, 0.7f), // diffuse_color
color(0.1f, 0.1f, 0.1f), // specular_color,
20.0, // spot_exponent
180.0, // spot_cutoff,
1.0, // constant_attenuation,
0.0, // linear_attenuation,
0.0 // quadratic_attenuation
));
//load_rsn("../files/my_third_scene.rsn");
}
scene::scene(const scene &s) :
m_light_source(s.m_light_source),
// m_mesh(s.m_mesh),
m_camera(s.m_camera),
m_model(s.m_model),
m_rtm_model_cache(s.m_rtm_model_cache),
m_rtm_mesh_cache(s.m_rtm_mesh_cache),
m_current_camera(s.m_current_camera),
m_random_seed(s.m_random_seed),
m_display_lights(s.m_display_lights) {
transfer_mesh_pointers();
}
scene::scene(const std::string &filename) : m_random_seed(0) {
load_rsn(filename);
}
scene::~scene() {
}
scene &scene::operator=(const scene &s) {
if (this != &s) {
m_light_source = s.m_light_source;
//m_mesh = s.m_mesh;
m_camera = s.m_camera;
m_model = s.m_model;
m_rtm_model_cache = s.m_rtm_model_cache;
m_rtm_mesh_cache = s.m_rtm_mesh_cache;
m_current_camera = s.m_current_camera;
m_random_seed = s.m_random_seed;
m_display_lights = s.m_display_lights;
transfer_mesh_pointers();
}
return *this;
}
const model &scene::get_rtm(const std::string &filename) {
if (m_rtm_model_cache.find(filename) == m_rtm_model_cache.end())
load_rtm(filename);
return m_rtm_model_cache[filename];
}
void scene::add_model(const std::string &filename) {
// load model
model m(get_rtm(filename));
// get camera
const camera &c(get_current_camera());
// set transform of the model (so that it is in front of the current camera)
// todo: 3.0 -> bounding sphere size . . .
m.set_xf(_EXForm(c.get_xf() * exp(-3.0 * nie3)));
// printf("%s\n", m.get_xf().string());
// add model
m_model.push_back(m);
}
void scene::add_model(const model &M) {
m_model.push_back(M);
}
/// duplicates a model \param M
void scene::duplicate_model(const model &_M) {
// copy model
model M(_M);
/**
We want to keep the orientation of the model, but position it
in front of the camera.
So first we compute where we want the model to be (pos).
Then we compute that position in the frame of the model,
and create a translator to that position, also in the model frame.
*/
EXForm in_front_of_camera_xf = _EXForm(scene::get_current_camera().get_xf() * exp(-3.0 * nie3));
// todo: 3.0 -> bounding sphere size . . .
flatPoint pos = _flatPoint(in_front_of_camera_xf * noni * inverse(in_front_of_camera_xf));
flatPoint pos_in_model_frame = _flatPoint(M.apply_xfi_to(pos));
translator T = _translator(exp(-0.5 * (pos_in_model_frame - (noni))));
// apply transform to model
M.post_mul_xf(T);
// turn of selection because that might be on (if the model _M passed to this function was selected!)
M.set_selected(false);
// add model to scene
add_model(M);
}
const camera &scene::get_camera(int idx) const {
try {
return m_camera.at(idx);
}
catch (...) {
throw std::string("scene::get_camera(): index out of range");
}
}
camera &scene::get_camera(int idx) {
try {
return m_camera.at(idx);
}
catch (...) {
throw std::string("scene::get_camera(): index out of range");
}
}
void scene::set_camera(int idx, const camera &C) {
if ((idx < 0) || (idx >= (int)m_camera.size()))
throw std::string("scene::set_camera(): index out of range");
m_camera[idx] = C;
}
const camera &scene::get_current_camera() const {
return get_camera(m_current_camera);
}
camera &scene::get_current_camera() {
return get_camera(m_current_camera);
}
int scene::get_current_camera_idx() const {
return m_current_camera;
}
/// Sets the currently active camera ('model' or 'render'); throws exception if index out of range
void scene::set_current_camera(int idx) {
if ((idx < 0) || (idx >= (int)m_camera.size()))
throw std::string("scene::set_current_camera(): index out of range");
if (m_current_camera == idx) return;
m_current_camera = idx;
// update the user interface (menu widgets)
if (g_ui_state)
g_ui_state->update_current_camera();
}
void scene::add_light_source(const light_source &L) {
m_light_source.push_back(L);
}
void scene::reset_selected_object() {
unsigned int i;
// set all models & light source to 'not selected'
for (i = 0; i < m_model.size(); i++)
m_model[i].set_selected(false);
for (i = 0; i < m_light_source.size(); i++)
m_light_source[i].set_selected(false);
if (g_ui_state) g_ui_state->update_selected_none();
}
void scene::set_selected_object(int idx) {
// Goal: set the specified object to selected
// check if already selected; if so: simply return
if (idx < 0) {
}
else if (idx < (int)m_model.size()) {
if (m_model[idx].get_selected()) {
return;
}
}
else if (idx < (int)(m_model.size() + m_light_source.size())) {
if (m_light_source[idx - m_model.size()].get_selected()) {
return;
}
}
// set all models & light source to 'not selected'
reset_selected_object();
// set the specified object to selected
if (idx < 0) {
if (g_ui_state) g_ui_state->update_selected_none();
}
else if (idx < (int)m_model.size()) {
m_model[idx].set_selected(true);
// tell it to the UI state so it can update the properties window:
if (g_ui_state)
g_ui_state->update_selected_model();
}
else {
idx -= (int)m_model.size();
if (idx < (int)m_light_source.size()) {
m_light_source[idx].set_selected(true);
// tell it to the UI state so it can update the properties window:
if (g_ui_state)
g_ui_state->update_selected_light_source();
}
}
}
locator &scene::get_selected_object() {
unsigned int i;
for (i = 0; i < m_model.size(); i++)
if (m_model[i].get_selected())
return m_model[i];
for (i = 0; i < m_light_source.size(); i++)
if (m_light_source[i].get_selected())
return m_light_source[i];
throw std::string("scene::get_selected_object(): no object selected");
}
light_source &scene::get_selected_light_source() {
for (unsigned int i = 0; i < m_light_source.size(); i++)
if (m_light_source[i].get_selected())
return m_light_source[i];
throw std::string("scene::get_selected_light_source(): no light source selected");
}
model &scene::get_selected_model() {
for (unsigned int i = 0; i < m_model.size(); i++)
if (m_model[i].get_selected())
return m_model[i];
throw std::string("scene::get_selected_model(): no model selected");
}
/// Turns this scene into a string (e.g., for storing a scene to file)
std::string scene::to_string(const std::string &basepath) const {
std::string result;
unsigned int i;
// models
for (i = 0; i < m_model.size(); i++)
result += m_model[i].to_string(basepath) + "\n";
// light sources
for (i = 0; i < m_light_source.size(); i++)
result += m_light_source[i].to_string() + "\n";
result += "display_lights " + ::to_string((int)display_lights()) + "\n";
// cameras
for (i = 0; i < m_camera.size(); i++)
result += m_camera[i].to_string() + "\n";
result += "current_camera " + ::to_string(m_current_camera) + "\n";
return result;
}
void scene::transfer_mesh_pointers() {
/*
After a copy, all pointers to meshes have to be
'transfered' from the old object to the new object
*/
for (unsigned int i = 0; i < m_model.size(); i++)
m_model[i].set_mesh(&(m_rtm_mesh_cache[m_model[i].get_mesh()->get_filename()]));
for (std::map<std::string, model>::iterator I = m_rtm_model_cache.begin();
I != m_rtm_model_cache.end(); I++)
I->second.set_mesh(&(m_rtm_mesh_cache[I->second.get_mesh()->get_filename()]));
}
void scene::remove_selected_object() {
unsigned int i;
for (i = 0; i < m_model.size(); i++)
if (m_model[i].get_selected())
m_model.erase(m_model.begin() + i);
// light sources
for (i = 0; i < m_light_source.size(); i++)
if (m_light_source[i].get_selected())
m_light_source.erase(m_light_source.begin() + i);
// tell it to the UI state so it can update the properties window:
if (g_ui_state)
g_ui_state->update_selected_none();
}
void scene::set_random_seed(unsigned long seed) {
sgenrand(m_random_seed = seed);
}
void scene::reset_random_seed() const {
sgenrand(m_random_seed);
}
void scene::render_init() {
int i;
for (i = 0; i < get_nb_models(); i++)
get_model(i).compute_position();
for (i = 0; i < get_nb_light_sources(); i++)
get_light_source(i).compute_position();
get_current_camera().compute_position();
}
void scene::render(int camera_idx, unsigned char *image_buffer,
int image_width, int image_height, int multisample,
int verbosity, const intersection_info *II, int max_recursion, int scanline) {
/*
If precomputed intersection info is provided, reset the random seed for the
mersenne twister to the value it had before intersection info
was precomputed. This is required in order to reproduce sequence
of events as they happened during precomputation.
If no precomputed intersection info is provided,
just set the seed to the current time.
*/
if (scanline <= 0) {
if (II) reset_random_seed();
else set_random_seed(time(NULL));
}
if (multisample < 0) multisample = 1;
// get camera
const camera &C(get_camera(camera_idx));
// initialize per pixel/line adds
mv::Float pixel_width = C.get_fov_width() / image_width;
mv::Float pixel_height = -pixel_width;
/*
We 'recycle' the initial ray for all samples/pixels
Here we set the position of the ray, later we set
its direction.
*/
ray R;
R.set_position(C.get_position());
int startline = 0;
int endline = image_height;
if ((scanline >= 0) && (scanline < image_height))
startline = scanline, endline = startline + 1;
int idx = startline * image_width; // index in buffer where the final color values go
for (int y = startline; y < endline; y++) {
if (verbosity > 0) printf("scanline %d\n", y);
for (int x = 0; x < image_width; x++) {
/*
Compute the 'base direction' of the ray. It has to
go through sensor pixel [x, y]. Later we add small pertubations
for multisampling.
*/
freeVector base_direction = _freeVector(
((x * pixel_width - 0.5 * image_width * pixel_width) * e1 +
(y * pixel_height - 0.5 * image_height * pixel_height) * e2 -
e3) ^ ni);
// sample multiple times, accumulate the result in pix_color:
color pixel_color;
for (int s = 0; s < multisample; s++) {
/*
Add a small perturbation within the pixel.
To generate random numbers, we call genrand(),
the mersenne twister random number generator.
*/
freeVector sample_direction;
if (multisample > 1)
sample_direction = _freeVector(
unit_e(base_direction +
(((genrand() - 0.5) * e1 * pixel_width +
(genrand() - 0.5) * e2 * pixel_height) ^ ni)));
else sample_direction = _freeVector(unit_e(base_direction));
/*
Sample direction is in the camera frame, so transform it to the
global frame, and set the ray direction
*/
R.set_direction(_freeVector(apply_om(C.get_M(), sample_direction)));
// trace the ray
pixel_color += trace(R, max_recursion, (II) ? II + idx : NULL);
}
pixel_color /= (float)multisample;
// clamp color to range of image
pixel_color.clamp(0.0, 1.0);
// set color
image_buffer[idx * 3 + 0] = (unsigned char)(pixel_color.r() * 255.0f + 0.5f);
image_buffer[idx * 3 + 1] = (unsigned char)(pixel_color.g() * 255.0f + 0.5f);
image_buffer[idx * 3 + 2] = (unsigned char)(pixel_color.b() * 255.0f + 0.5f);
// update index into image_buffer
idx++;
}
}
}
color scene::trace(const ray &R, int max_recursion, const intersection_info *II) const {
surface_point spt;
// find the closest intersection point
if (II) {
/* if (info->m_model == NULL) return 0;
if (info->m_model->lineFaceIntersect(ln, startPt, spt, info->m_fIdx) == 0) {
cr.set(0.0, 0.0, 0.0);
return 0;
}*/
}
else {
if (!find_intersection(R, spt, true)) return color(); // no intersection found
}
// get all info for the surface point in 'spt':
spt.compute_details();
// this is where the resulting color will go:
color C;
// do local shading computations
if (spt.get_direct_lighting_factor() > 0.0)
C = shade(R, II, spt);
// reflection required? This will recursively call trace() again
if ((max_recursion > 0) && (spt.get_reflection_factor() > 0.0))
C += reflect(R, max_recursion, II, spt);
// refraction required? This will also recursively call trace() again
if ((max_recursion > 0) && (spt.get_refraction_factor() > 0.0))
C += refract(R, max_recursion, II, spt);
return C;
}
color scene::shade(const ray &R, const intersection_info *II, const surface_point &spt) const {
// perform basic 'OpenGL-style' lighting computations (+ a shadow check)...
// this is where the lighting computations are accumulated:
color C;
// evaluate the contribution of every light and add it to 'c'
for (int i = 0; i < get_nb_light_sources(); i++) {
C += get_light_source(i).shade(R, *this, II, spt);
}
// global ambient in scene (TODO TODO TODO: look this up in OpenGL manual)
//C.mul(m_ambientColor, spt.m_ambientColor);
// apply direct lighting factor & return
return (float)spt.get_direct_lighting_factor() * C;
}
color scene::reflect(const ray &R, int max_recursion, const intersection_info *II, const surface_point &spt) const {
/*
Reflect the ray direction in the surface attitude,
and spawn a new ray with that direction at the surface point.
*/
ray reflected_ray(
spt.get_pt(),
_freeVector(-((spt.get_att() ^ no) * R.get_direction() * reverse(spt.get_att() ^ no)))
);
return trace(reflected_ray, max_recursion - 1, II);
}
color scene::refract(const ray &R, int max_recursion, const intersection_info *II, const surface_point &spt) const {
vectorE3GA surface_normal = _vectorE3GA(no << dual(spt.get_att()));
vectorE3GA ray_direction = _vectorE3GA(no << R.get_direction());
//printf("surface_normal = %s,\n", surface_normal.string());
//printf("ray_direction = %s,\n", ray_direction.string());
mv::Float nu2 = _Float(surface_normal % ray_direction);
mv::Float r, r2, f, sd;
if (nu2 > 0.0) {
sd = 1.0;
r = spt.get_refractive_index() / get_refractive_index();
}
else if (nu2 < 0.0) {
sd = -1.0;
r = get_refractive_index() / spt.get_refractive_index();
}
else return color();
r2 = r * r;
f = (1 - r2) + nu2 * nu2 * r2;
if (f < 0) return color(); // total internal reflection
ray refracted_ray(
spt.get_pt(),
_freeVector(((sd * (mv::Float)sqrt(f) - nu2 * r) * surface_normal + r * ray_direction) ^ ni)
);
//printf("ray_direction = %s,\n\n", refracted_ray.get_direction().string());
// printf("Rp = %s,\n", c3ga::string(refracted_ray.get_position(), "%f"));
// printf("Rd = %s,\n", c3ga::string(refracted_ray.get_direction(), "%f"));
return trace(refracted_ray, max_recursion - 1, II);
}
bool scene::find_intersection(const ray &R, surface_point &spt, bool find_closest) const {
/*
Find the (closest) intersection point with any model
*/
spt.set_valid(false);
// surface_point tmp_spt; // temporary surface point
for (unsigned int i = 0; i < m_model.size(); i++) {
// for each model, seach for the intersection
if (m_model[i].find_intersection(R, spt, find_closest) &&
(!find_closest)) {
// an intersection was found and the caller doesn't care about the closest
return true;
}
}
// set distance to true value (required?):
if (spt.get_valid()) {
spt.set_distance(sqrt(spt.get_distance()));
return true;
}
else return false;
}
bool scene::display_lights() const {
return m_display_lights;
}
void scene::set_display_lights(bool l) {
m_display_lights = l;
}