/*
 * ***** BEGIN GPL LICENSE BLOCK *****
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * The Original Code is Copyright (C) 2009 Blender Foundation.
 * All rights reserved.
 *
 * The Original Code is: all of this file.
 *
 * Contributor(s): André Pinto.
 *
 * ***** END GPL LICENSE BLOCK *****
 */

/** \file blender/render/intern/raytrace/rayobject_instance.cpp
 *  \ingroup render
 */


#include <assert.h>

#include "MEM_guardedalloc.h"

#include "BLI_math.h"
#include "BLI_utildefines.h"

#include "rayintersection.h"
#include "rayobject.h"

#define RE_COST_INSTANCE (1.0f)

static int  RE_rayobject_instance_intersect(RayObject *o, Isect *isec);
static void RE_rayobject_instance_free(RayObject *o);
static void RE_rayobject_instance_bb(RayObject *o, float *min, float *max);
static float RE_rayobject_instance_cost(RayObject *o);

static void RE_rayobject_instance_hint_bb(RayObject *UNUSED(o), RayHint *UNUSED(hint),
                                          float *UNUSED(min), float *UNUSED(max))
{}

static RayObjectAPI instance_api =
{
	RE_rayobject_instance_intersect,
	NULL, //static void RE_rayobject_instance_add(RayObject *o, RayObject *ob);
	NULL, //static void RE_rayobject_instance_done(RayObject *o);
	RE_rayobject_instance_free,
	RE_rayobject_instance_bb,
	RE_rayobject_instance_cost,
	RE_rayobject_instance_hint_bb
};

typedef struct InstanceRayObject {
	RayObject rayobj;
	RayObject *target;

	void *ob; //Object represented by this instance
	void *target_ob; //Object represented by the inner RayObject, needed to handle self-intersection
	
	float global2target[4][4];
	float target2global[4][4];
	
} InstanceRayObject;


RayObject *RE_rayobject_instance_create(RayObject *target, float transform[4][4], void *ob, void *target_ob)
{
	InstanceRayObject *obj = (InstanceRayObject *)MEM_callocN(sizeof(InstanceRayObject), "InstanceRayObject");
	assert(RE_rayobject_isAligned(obj) );  /* RayObject API assumes real data to be 4-byte aligned */

	obj->rayobj.api = &instance_api;
	obj->target = target;
	obj->ob = ob;
	obj->target_ob = target_ob;

	copy_m4_m4(obj->target2global, transform);
	invert_m4_m4(obj->global2target, obj->target2global);

	return RE_rayobject_unalignRayAPI((RayObject *) obj);
}

static int  RE_rayobject_instance_intersect(RayObject *o, Isect *isec)
{
	InstanceRayObject *obj = (InstanceRayObject *)o;
	float start[3], dir[3], idot_axis[3], dist;
	int changed = 0, i, res;

	// TODO - this is disabling self intersection on instances
	if (isec->orig.ob == obj->ob && obj->ob) {
		changed = 1;
		isec->orig.ob = obj->target_ob;
	}

	// backup old values
	copy_v3_v3(start, isec->start);
	copy_v3_v3(dir, isec->dir);
	copy_v3_v3(idot_axis, isec->idot_axis);
	dist = isec->dist;

	// transform to target coordinates system
	mul_m4_v3(obj->global2target, isec->start);
	mul_mat3_m4_v3(obj->global2target, isec->dir);
	isec->dist *= normalize_v3(isec->dir);

	// update idot_axis and bv_index
	for (i = 0; i < 3; i++) {
		isec->idot_axis[i]        = 1.0f / isec->dir[i];

		isec->bv_index[2 * i]     = isec->idot_axis[i] < 0.0f ? 1 : 0;
		isec->bv_index[2 * i + 1] = 1 - isec->bv_index[2 * i];

		isec->bv_index[2 * i]     = i + 3 * isec->bv_index[2 * i];
		isec->bv_index[2 * i + 1] = i + 3 * isec->bv_index[2 * i + 1];
	}

	// Pre-calculate orientation for watertight intersection checks.
	isect_ray_tri_watertight_v3_precalc(&isec->isect_precalc, isec->dir);

	// raycast
	res = RE_rayobject_intersect(obj->target, isec);

	// map dist into original coordinate space
	if (res == 0) {
		isec->dist = dist;
	}
	else {
		// note we don't just multiply dist, because of possible
		// non-uniform scaling in the transform matrix
		float vec[3];

		mul_v3_v3fl(vec, isec->dir, isec->dist);
		mul_mat3_m4_v3(obj->target2global, vec);

		isec->dist = len_v3(vec);
		isec->hit.ob = obj->ob;

#ifdef RT_USE_LAST_HIT
		// TODO support for last hit optimization in instances that can jump
		// directly to the last hit face.
		// For now it jumps directly to the last-hit instance root node.
		isec->last_hit = RE_rayobject_unalignRayAPI((RayObject *) obj);
#endif
	}

	// restore values
	copy_v3_v3(isec->start, start);
	copy_v3_v3(isec->dir, dir);
	copy_v3_v3(isec->idot_axis, idot_axis);

	if (changed)
		isec->orig.ob = obj->ob;

	// restore bv_index
	for (i = 0; i < 3; i++) {
		isec->bv_index[2 * i]     = isec->idot_axis[i] < 0.0f ? 1 : 0;
		isec->bv_index[2 * i + 1] = 1 - isec->bv_index[2 * i];

		isec->bv_index[2 * i]     = i + 3 * isec->bv_index[2 * i];
		isec->bv_index[2 * i + 1] = i + 3 * isec->bv_index[2 * i + 1];
	}

	// Pre-calculate orientation for watertight intersection checks.
	isect_ray_tri_watertight_v3_precalc(&isec->isect_precalc, isec->dir);

	return res;
}

static void RE_rayobject_instance_free(RayObject *o)
{
	InstanceRayObject *obj = (InstanceRayObject *)o;
	MEM_freeN(obj);
}

static float RE_rayobject_instance_cost(RayObject *o)
{
	InstanceRayObject *obj = (InstanceRayObject *)o;
	return RE_rayobject_cost(obj->target) + RE_COST_INSTANCE;
}

static void RE_rayobject_instance_bb(RayObject *o, float *min, float *max)
{
	//TODO:
	// *better bb.. calculated without rotations of bb
	// *maybe cache that better-fitted-BB at the InstanceRayObject
	InstanceRayObject *obj = (InstanceRayObject *)o;

	float m[3], M[3], t[3];
	int i, j;
	INIT_MINMAX(m, M);
	RE_rayobject_merge_bb(obj->target, m, M);

	//There must be a faster way than rotating all the 8 vertexs of the BB
	for (i = 0; i < 8; i++) {
		for (j = 0; j < 3; j++) t[j] = (i & (1 << j)) ? M[j] : m[j];
		mul_m4_v3(obj->target2global, t);
		DO_MINMAX(t, min, max);
	}
}

