/*
	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License version 2 
	as published by the Free Software Foundation.

	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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA


	Copyright (C) 2006  Thierry Berger-Perrin <tbptbp@gmail.com>
*/
#include "specifics.h"

#include "sys_mem.h"
#include "sys_log.h"

#include "rt.h"

#include <memory>

//
// renderer
//
bool_t rt::renderer_t::open(const point_t &res) {
	bool_t rc = true;
	
	rc &= framebuffer.open(res);
	framebuffer.clear();

	rc &= rt.open();
	rt.camera.update(framebuffer);

	return rc;
}

void rt::renderer_t::close() {
	framebuffer.close();
}

//
// framebuffer
//
void rt::framebuffer_t::clear() {
	std::memset(fb, 0, resolution.area() * sizeof(rt::pixel_t)); 
}


//
// raytracer
//

bool_t rt::raytracer_t::open() {
	camera.open();
	//camera.sampler.set(camera, framebuffer.get_resolution());

	return true; // ehe.
}

void rt::raytracer_t::close() {
	camera.close();
	scene.close();
}


//
// framebuffer
//
bool_t rt::framebuffer_t::open(const point_t &res) {
	fb = 0;
	owned = false;
	resolution = res;
	allocate();

	return true; // ehe.
}

void rt::framebuffer_t::close() {
	sys::mem::liberate(fb);
	resolution = point_t(0,0);
}

void rt::framebuffer_t::allocate() {
	if (owned) {
		sys::log("rt::framebuffer_t::allocate: already owned\n");	// happens if we change more than once per frame. bah.
	}
	else {
		fb = (pixel_t * __restrict) sys::mem::allocate(resolution.area() * sizeof(rt::pixel_t));
		owned = true;
	}
}
		
void rt::framebuffer_t::set(void *p) {
	if (owned) sys::mem::liberate(fb);
	owned = false;

	const uint64_t align = uint64_t(p) & 7u;
	if (align != 0)
		fatal("rt::framebuffer_t::set: mapped buffer without sufficient alignment");

	fb = (pixel_t * __restrict) p; 
}


//
// camera / sampler
//
// (0 = (1,0,0), 1 = (0,1,0), 2 = (0,0,1))
static const vec_t world_up[3] = { vec_t(0,1,0), vec_t(0,0,1), vec_t(1,0,0) };
//static const vec_t world_up[3] = { vec_t(1,0,0), vec_t(0,1,0), vec_t(0,0,1) };

bool_t rt::camera_t::open() {
	set_fovx(90.f);
	set_eye(vec_t(0,0,1));
	look_at(vec_t(0,0,0), 0);

	return true;
}

void rt::camera_t::look_at(const vec_t &target, const int world_up_idx){
	if (world_up_idx >= 0) world_up_index = world_up_idx;
	dir		= (target - eye).normalize();
	right	= -world_up[world_up_index].cross(dir).normalize();
	up		= -dir.cross(right).normalize();
}

/*
void rt::camera_t::sampler_t::set(const camera_t &cam, const point_t &res) {
	const float
		res_x	= float(res.x), res_y = float(res.y),
		width	= tanf(cam.fovx),	// that's really half the fov
		height	= width * (res_y / res_x);

	const vec_t &dir(cam.dir), &up(cam.up), &right(cam.right);

	top = dir + up*height - right*width;
	dx = right*(2*width/res_x);
	dy = up*(-2*width/res_x);
}
*/
void rt::camera_t::update(const framebuffer_t &fb) {
	const point_t &res(fb.get_resolution());

	const float
		res_x	= float(res.x), res_y = float(res.y),
		width	= tanf(fovx),	// that's really half the fov
		height	= width * (res_y / res_x);

	const vec_t &d(dir), &u(up), &r(right);

	sampler.top = d + u*height - r*width;
	sampler.dx = r*(2*width/res_x);
	sampler.dy = u*(-2*width/res_x);
}

void rt::camera_t::update(const point_t &res) {

	const float
		res_x	= float(res.x), res_y = float(res.y),
		width	= tanf(fovx),
		height	= width * (res_y / res_x);

	const vec_t &d(dir), &u(up), &r(right);

	sampler.top = d + u*height - r*width;
	sampler.dx = r*(2*width/res_x);
	sampler.dy = u*(-2*width/res_x);
}
