#include "ai.h"
#include <cmath>

AI_SHADER_NODE_EXPORT_METHODS(apInteriorMapping)

enum apInteriorMappingParams
{
	p_ceiling_texture,
	p_floor_texture,
	p_wall_xy_texture,
	p_wall_zy_texture,
	p_wall_frequencies,
};

inline int step(int step, int value)
{                                                                         /*sc*/
	return static_cast<int>(value >= step);
}                                                                         /*ec*/

inline unsigned char step(unsigned char step, unsigned char value)
{                                                                         /*sc*/
	return static_cast<unsigned char>(value >= step);
}                                                                         /*ec*/

inline float step(float step, float value)
{                                                                         /*sc*/
	return static_cast<float>(value >= step);
}

inline AtVector step(AtVector step, AtVector value)
{
	return AtVector(static_cast<float>(value.x >= step.x),
					static_cast<float>(value.y >= step.y),
					static_cast<float>(value.z >= step.z));
}

inline AtVector floor(AtVector value)
{
	return AtVector(floor(value.x), floor(value.y), floor(value.z));
}

struct ShaderData
{
	AtVector wall_frequencies;
};

node_parameters
{
	AiParameterRGB("ceiling_texture", 0, 0, 0);
	AiParameterRGB("floor_texture", 0, 0, 0);
	AiParameterRGB("wall_xy_texture", 0, 0, 0);
	AiParameterRGB("wall_zy_texture", 0, 0, 0);
	AiParameterVec("wall_frequencies", 1, 1, 1);
}

node_initialize
{
	ShaderData *data = (ShaderData *)AiMalloc(sizeof(ShaderData));
	AiNodeSetLocalData(node, data);
}

node_update
{
	ShaderData *data = (ShaderData *)AiNodeGetLocalData(node);
	data->wall_frequencies = AiNodeGetVec(node, "wall_frequencies");
}

node_finish
{
	ShaderData *data = (ShaderData *)AiNodeGetLocalData(node);
	AiFree(data);
}

shader_evaluate
{
	ShaderData *data = (ShaderData *)AiNodeGetLocalData(node);

	AtVector camere_position = AiShaderGlobalsTransformVector(sg, sg->Ro, AI_WORLD_TO_OBJECT);
	AtVector position = AiShaderGlobalsTransformVector(sg, sg->P, AI_WORLD_TO_OBJECT);
	AtVector direction = position - camere_position;

	//calculate wall locations
	AtVector walls = (floor(position * (data->wall_frequencies - 0.1)) + step(AtVector(0, 0, 0), direction)) / (data->wall_frequencies - 0.1);

	//how much of the ray is needed to get from the cameraPosition to each of the walls
	AtVector ray_fractions = (walls - camere_position) / direction;

	//texture-coordinates of intersections
	AtVector crd = (camere_position + ray_fractions.z * direction);
	AtVector2 intersection_xy = 1 * AtVector2(crd.x, crd.y);
	AtVector2 intersection_xz = 1 * AtVector2(crd.x, crd.z);
	AtVector2 intersection_zy = 1 * AtVector2(crd.z, crd.y);

	//use the intersection as the texture coordinates for the ceiling
	sg->u = intersection_xz.x;
	sg->v = intersection_xz.y;
	AtRGB ceil_colour = AiShaderEvalParamRGB(p_ceiling_texture);
	AtRGB floor_colour = AiShaderEvalParamRGB(p_floor_texture);
	AtRGB vertical_colour = AiLerp(step(0.0, direction.y), floor_colour, ceil_colour);

	sg->u = intersection_xy.x;
	sg->v = intersection_xy.y;
	AtRGB wall_xy_colour = AiShaderEvalParamRGB(p_wall_xy_texture);

	sg->u = intersection_zy.x;
	sg->v = intersection_zy.y;
	AtRGB wall_zy_colour = AiShaderEvalParamRGB(p_wall_zy_texture);

	//choose the closest of these walls for the normal for the lighting
	float xVSz = step(ray_fractions.x, ray_fractions.z);
	AtRGB interior_colour = AiLerp(xVSz, wall_xy_colour, wall_zy_colour);
	float ray_fraction_xVSz = AiLerp(xVSz, ray_fractions.z, ray_fractions.x);
	float xzVSy = step(ray_fraction_xVSz, ray_fractions.y);
	interior_colour = AiLerp(xzVSy, vertical_colour, interior_colour);

	sg->out.RGB() = interior_colour;
}

node_loader
{
	if (i > 0)
	return false;

	node->methods = apInteriorMapping;
	node->output_type = AI_TYPE_RGB;
	node->name = "apInteriorMapping";
	node->node_type = AI_NODE_SHADER;
	strcpy(node->version, AI_VERSION);
	return true;
}
