/*
 * Copyright (c) 2007, Ahmad Kabani
 * Distribute under GPL
 */

#include <stdlib.h>
#include <stdio.h>
#include "math.h"
#include "plugin.h"

/* ******************** GLOBAL VARIABLES ***************** */

#define TRUE 1
#define FALSE 0

/* Texture name */

char name[24]= "Super Bricks";

/* Subtype names must be less than 15 characters */

#define NR_TYPES 3
char stnames[NR_TYPES][16]= { "Linear", "Quadratic", "Cubic" };

/* Structure for buttons,
 *  butcode      name           default  min  max  0
 */
VarStruct varstr[]= {
    { NUM|FLO,	"Scale",	1.0,	0.0,	  100.0,  "Scales texture size" },
    { NUM|INT,	"# Bricks",	3.0,	1.0,	   10.0,  "Number of bricks in pattern" },
    { NUM|FLO,	"Min Width",	0.3,	0.1,	    1.0,  "Minimum brick width" },
    { NUM|FLO,	"Max Width",	0.5,	0.1,	    1.0,  "Maximum brick width" },
    { NUM|FLO,	"Height",	0.1,	0.1,	    1.0,  "Height of brick" },
    { NUM|FLO,	"Min Offset",	0.1,	0.0,	    0.25, "Minimum offset for brick" },
    { NUM|FLO,	"Max Offset",	0.2,	0.0,	    0.25, "Maximum offset for brick" },
    { NUM|FLO,	"Mortar",	0.02,	0.0,	    0.5,  "Mortar thickness" },
    { NUM|FLO,	"Shift", 	0.1,	0.0,	    1.0,  "Amount to shift every row" },
    { NUM|FLO,	"Bevel", 	0.2,	0.0,	    0.5,  "Amount of bevel applied to brick" },
    { NUM|FLO,	"Nabla",	0.025,	0.001,	    0.1,  "Defines size of derivative offset used for calculating normal"},
    { NUM|FLO,	"Round",	0.25,	0.0,	    0.5,  "Roundness factor for corners"},
    { NUM|FLO,	"Rotation",	0.0,	0.0,	    360,  "Rotation in degrees" },
    { NUM|INT,	"Seed",		0.0,	0.0, 2147483647,  "Random number seed" },
};

/* The cast struct is for input in the main doit function
   Varstr and Cast must have the same variables in the same order, 
   INCLUDING dummy variables for label fields. */ 
typedef struct Cast {
    float        scale;
    int          numBricks;
    float        minWidth;
    float        maxWidth;
    float        height;
    float        minOffset;
    float        maxOffset;
    float        mortar;
    float        shift;
    float        bevel;
    float        nabla;
    float        round;
    float        rotation;
    unsigned int seed;
} Cast;

/* result:
   Intensity, R, G, B, Alpha, nor.x, nor.y, nor.z
   */

float result[8];

/* cfra: the current frame */

float cfra;

int plugin_tex_doit(int, Cast*, float*, float*, float*);

#define MAXBRICKS	10				/* Keep it reasonable */
#define CALCLEN(n)	(n + 1)				/* How many points along horizontal to track */
#define MIN(a, b)	((a < b) ? a : b);		/* Compute minimum of 2 numbers */
#define MAX(a, b)	((a > b) ? a : b);		/* Compute minimum of 2 numbers */
#define PI		3.1415926535898;		/* Mathemagical Pie */
#define PI_OVER_180	0.0174532925199;		/* Used to convert degrees to radian */

#define BRICK_MIN	0.00				/* Define minimum intensity for brick face */
#define BRICK_MAX	0.75				/* Define maximum intensity for brick face */
#define BRICK_RANGE	(BRICK_MAX - BRICK_MIN)
#define BRICK_MAP(v)	(BRICK_MIN + v * BRICK_RANGE)	/* Maps a value in [0,1] to [BRICK_MIN, BRICK_MAX) */

static const double ONE_OVER_ONE_PLUS_RAND_MAX = 1.0 / (double)(RAND_MAX + 1.0);
/* Used for computing random numbers between 0 and 1 */

float pat[CALCLEN(MAXBRICKS)];				/* Stores brick pattern */
float width[MAXBRICKS];					/* Stores width of bricks */
float offset[MAXBRICKS];				/* Stores height offset of bricks */
int   gRecalcRandomValues = 1;				/* Whether or not to recalculate random values */
int   gRecalcPattern = 1;				/* Whether or not to recalculate brick pattern */

/* ******************** Fixed Functions ***************** */

int plugin_tex_getversion(void) 
{
    return B_PLUGIN_VERSION;
}

/* Called when a button in the UI changes */
void plugin_but_changed(int but) 
{
    /* If a button affect recomputation of brick pattern array
     * we have to flag that here. We can't calculate the pattern
     * here because we need an object of type Cast with all the
     * parameters.
     */
    switch ( but )
    {
	case  0: /* Scale */
	case  4: /* Height */
	case  7: /* Mortar */
	case  8: /* Shift */
	case  9: /* Bevel */
	case 10: /* Nabla */
	case 11: /* Round */
	case 12: /* Rotation */
	    /* We can recalculate the pat[] from width[] array.
	     * For some of these we probably don't need to but it
	     * will only be done once anyway.
	     */
	    gRecalcPattern = 1;
	    break;
	case  1: /* # Bricks */
	case  2: /* Min Width */
	case  3: /* Max Width */
	case  5: /* Min Offset */
	case  6: /* Max Offset */
	case 13: /* Seed */
	    /* When the seed or # bricks changes, we'll store a new set of random values
	     * and also the brick pattern.
	     */
	    gRecalcRandomValues = 1;
	    gRecalcPattern = 1;
	    break;
	default:
	    /* Unhandled options??? */
	    break;
    }
}

/* Initialize the plugin */
void plugin_init(void)
{
}

/* This function should not be changed: */
void plugin_getinfo(PluginInfo *info)
{
    info->name = name;
    info->stypes = NR_TYPES;
    info->nvars = sizeof(varstr)/sizeof(VarStruct);

    info->snames = stnames[0];
    info->result = result;
    info->cfra = &cfra;
    info->varstr = varstr;

    info->init = plugin_init;
    info->tex_doit =  (TexDoit) plugin_tex_doit;
    info->callback = plugin_but_changed;
}

/* *********************   Helpers   ******************** */
void debug_log_inputs(float *texvec, float *dxt, float *dyt) 
{
    FILE *f;
    char *filename = "/Temp/wbricks.txt";
    char format[] = "%s = ( %6.4f %6.4f %6.4f) \n";

    /* only log anti-aliased stuff */
    if ((dxt==NULL)&&(dyt==NULL)) return;

    f=fopen(filename,"a");
    if (f==NULL) return;

    fprintf(f,format,"texvec",texvec[0],texvec[1],texvec[2]);

    if (dxt!=NULL) {
	fprintf(f,format,"dxt   ",dxt[0],dxt[1],dxt[2]);
    } else {
	fprintf (f,"dxt   = null\n");
    }

    if (dyt!=NULL) {
	fprintf(f,format,"dyt   ",dyt[0],dyt[1],dyt[2]);
    } else {
	fprintf (f,"dyt   = null\n");
    }

    fprintf(f,"\n");
    fflush(f);
    fclose(f);
}

/* Clamp values in given range */
float clamp(float x, float a, float b)
{
    if ( x < a ) return a;
    if ( x > b ) return b;
    return x;
}

/* Calculates a step function as follows:
 *    y = 0             for x < 0
 *    y = -2x^2 + 3x^3  for 0 <= x <= 1
 *    y = 1             for x > 1
 *
 * Since we're using an interval of [a, b] instead of [0, 1]
 * we have to transform x with (x - a) / (b - a).
 */
float smoothstep(float x, float a, float b)
{
    float e;

    if ( a == b ) 
    {
	return ( x < a ) ? 0 : 1;
    }

    if ( x < a ) return 0;
    if ( x > b ) return 1;

    e = ( x - a ) / ( b - a );
    return e * e * (3 - 2 * e);
}

/* Calculates a random value between 0 and 1 */
float rand01()
{
    return (float)(rand() * ONE_OVER_ONE_PLUS_RAND_MAX);
}

/* Calculate a mod b for floating point numbers.
 * NOTE: a can be negative however I'm not sure what behaviour this function
 * has if b is negative
 *
 * NOTE: Copied from brick.c by Unicorn. The original code was in Public 
 * Domain and is supplied with the Blender Texture Plugins repository.
 */
float mod(float a, float b)
{
    int n = (int)(a / b);
    a -= n * b;
    if ( a < 0 ) a += b;
    return a;
}

/* Generate a set of random widths.
 * Add all initialization code that relies on random numbers in this function.
 */
void genRandomValues(Cast *cast)
{
    float bw = cast->maxWidth - cast->minWidth;
    float bo = cast->maxOffset - cast->minOffset;
    int i = 0;

    /* Initialize the random number generator */
    srand(cast->seed);

    /* Calculate a random value between cast->minWidth and cast->maxWidth */
    for (; i < cast->numBricks; i++)
    {
	width[i] = (rand01() * bw + cast->minWidth);
	offset[i] = (rand01() * bo + cast->minOffset);
    }
}

/* Generate a pattern of bricks.
 * pat[] will store a sequence of points calculated along the horizontal
 * using random widths. The starting and ending points include the size 
 * of the mortar.
 *
 * So for the i^th brick (0 <= i < cast->numBricks):
 *     pat[i]                        = start of mortar
 *     pat[i]     + cast->mortar / 2 = start of brick
 *     pat[i + 1] - cast->mortar / 2 = end of brick
 *     pat[i + 1]                    = end of mortar.
 */
void genPattern(Cast *cast)
{
    int len = CALCLEN(cast->numBricks);
    int i;

    pat[0] = 0;	/* Start at 0 */

    for (i=0; i < len; i++)
    {
	/* Add width of brick and mortar to the last known point */
	pat[i] = pat[i - 1] + cast->mortar + width[i - 1];
    }
}

/* Given points (x, y) determines where in the brick pattern, pat[], the point
 * belongs. It returns the index of the block in the argument i and
 * x and y will contain the relative offset for the brick.
 *
 * Also, the function will handle shifting alternate rows of brick by
 * amount specified in cast->shift.
 */
void calc(Cast *cast, float *x, float *y, int *i)
{
    int len = CALCLEN(cast->numBricks);
    int i1 = 0;

    float wtot = pat[len - 1] - pat[0];		/* Width of brick pattern */
    float htot = cast->height + cast->mortar;	/* Height of brick pattern -- 1 row */

    /* Shift alternate rows */
    int q = (int)floor(*y / htot);
    if ( ( q % 2 ) == 0 ) *x += cast->shift;

    /* Compute relative offset for current brick */
    *x = mod(*x, wtot);
    *y = mod(*y, htot);

    for (; i1 < len; i1++)
    {
	if ( *x <= pat[i1 + 1] ) break;
    }

    *i = i1;
}

/* Computes the intensity function for the brick pattern.
 * NOTE: 0 <= x <= w, 0 <= y <= h.
 *
 * The intensity starts at 1 and falls to 0 linearly based
 * on bevel b.
 */
float brickfunc(float x, float y, float w, float h, float b)
{
    float v = 1.0;

    if ( x < b )
    {
	if ( y < b )
	{
	    v = MIN(x, y);
	}
	else if ( y > h - b ) 
	{
	    v = MIN(x, h - y);
	}
	else
	{
	    v = x;
	}
	v /= b;
    }
    else if ( x > w - b )
    {
	if ( y < b )
	{
	    v = MIN(w - x, y);
	}
	else if ( y > h - b )
	{
	    v = MIN(w - x, h - y);
	}
	else
	{
	    v = w - x;
	}
	v /= b;
    }
    else
    {
	if ( y < b )
	{
	    v = y / b;
	}
	else if ( y > h - b )
	{
	    v = (h - y) / b;
	}
    }

    return v;
}

/* Determines if a point (x, y) lies inside circle with center (cx, cy)
 * and radius r.
 */
int point_in_circle(float x, float y, float cx, float cy, float r)
{
    float dx = (x - cx);
    float dy = (y - cy);
    return ( dx * dx + dy * dy <= r * r ) ? 1 : 0;
}

/* For a point (x, y) and circle with center (cx, cy) and radius r,
 * and a bevel distance b from outer edge of circle:
 *    If the distance from the center to the point is less than r - b
 *    returns 1.
 *    Otherwise, returns a linear fall-off from 1 to 0 from r - b to r.
 *
 * Used for beveling a circle (or quarter circle in case of a rounded
 * brick.
 *
 * Works when r >= b.
 * Works when r < b and (x, y) lie within the circle.
 */
float dist_circle(float x, float y, float cx, float cy, float b, float r)
{
    /* Compute offset from center */
    float dx = (x - cx);
    float dy = (y - cy);

    /* Compute distance from center */
    float d = fsqrt(dx * dx + dy * dy);

    /* Compute intensity */
    float intens = 1;
    if ( r >= b )
    {
	if ( d >= r - b )
	    intens = (r - d) / b;
    }
    else
    {
	intens = (r - d) / b;
    }
    return intens;
}

/* Computes the intensity function for the brick pattern which
 * contains bricks with rounded corners.
 *
 * The intensity starts at 1 and falls to 0 linearly based
 * on bevel b.
 */
float brickfunc2(float x, float y, float w, float h, float b, float r)
{
    float v = 1;

    if ( ( x < 0 ) || ( y < 0 ) || ( x > w ) || ( y > h ) ) return 0;

    if ( r <= b )
    {
	/* Special cases */
	if ( ( ( x > r ) && ( x <= b ) ) || 
		( ( x >= w - b ) && ( x < w - r ) ) ||
		( ( y > r ) && ( y <= b ) ) ||
		( ( y >= h - b ) && ( y < h - r ) ) )
	{
	    return brickfunc(x, y, w, h, b);
	}
	else
	{
	    if ( x <= r )
	    {
		if ( y <= r )
		{
		    return dist_circle(x, y, r, r, b, r);
		}
		else if ( y >= h - r )
		{
		    return dist_circle(x, y, r, h - r, b, r);
		}
	    }
	    else if ( x >= w - r )
	    {
		if ( y <= r )
		{
		    return dist_circle(x, y, w - r, r, b, r);
		}
		else if ( y >= h - r )
		{
		    return dist_circle(x, y, w - r, h - r, b, r);
		}
	    }
	}
    }

    if ( x <= r )
    {
	if ( y <= r )
	{
	    v = dist_circle(x, y, r, r, b, r);
	}
	else if ( y < h - r )
	{
	    if ( x <= b )
	    {
		v = x / b;
	    }
	}
	else
	{
	    v = dist_circle(x, y, r, h - r, b, r);
	}
    }
    else if ( x < w - r )
    {
	if ( y <= b )
	{
	    v = y / b;
	}
	else if ( y >= h - b )
	{
	    v = (h - y) / b;
	}
    }
    else
    {
	if ( y <= r )
	{
	    v = dist_circle(x, y, w - r, r, b, r);
	}
	else if ( y < h - r )
	{
	    if ( x >= w - b )
	    {
		v = ( w - x ) / b;
	    }
	}
	else
	{
	    v = dist_circle(x, y, w - r, h - r, b, r);
	}
    }

    return v;
}

/* Computes intensity for given (x, y) coordinates and brick starting position i */
float intensity(int stype, Cast *cast, float x, float y, int i)
{
    float w = width[i];	/* Size of brick */
    float h = cast->height;				/* Height of brick */
    float hm = cast->mortar * 0.5;			/* Mortar thickness on either side */
    float b, r, rval;

    /* Compute the bevel for the brick using the minimum of width/height */
    b = MIN(w, h);
    b *= cast->bevel;

    /* Compute roundness radius */
    r = MIN(w, h);
    r *= cast->round;

    /* Make sure 0 <= x <= w and 0 <= y <= h */
    x -= (pat[i] + hm);
    y -= hm;

    /* Compute brick intensity function */
    rval = brickfunc2(x, y, w, h, b, r);
    rval = clamp(rval, 0, 1);

    switch ( stype )
    {
	case 0:
	    /* Brick function handles linear case */
	    break;
	case 1:
	    /* Since 0 <= rval <= 1, 0 <= rval^2 <= 1 
	     * This provides quadratic fall-off
	     */
	    rval *= rval;
	    break;
	case 2:
	    /* Since 0 <= rval <= 1, we can use the smooth step
	     * function which is cubic
	     */
	    rval = smoothstep(rval, 0, 1);
	    break;
	default:
	    /* Just use linear */
	    break;
    }

    if ( rval >= BRICK_MIN )
    {
	rval = offset[i] + BRICK_MAP(rval);
    }
    else
    {
	rval = BRICK_MAP(rval);
    }


    return rval;
}

/* Helper function */
float intensity_help(int stype, Cast *cast, float x, float y)
{
    float x1 = 0;	/* Stores offset            */
    float y1 = 0;	/* texture coordinates      */
    int i = 0;		/* Index into pattern array */

    x1 = x; y1 = y;
    calc(cast, &x1, &y1, &i);
    return intensity(stype, cast, x1, y1, i);
}

/* This function handles intensity & bump mapping */
void doit(int stype, Cast *cast, float x, float y, float *pixsize, float *intens, float *du, float *dv)
{
    /* Apply texture scaling */
    x *= cast->scale;
    y *= cast->scale;

    /* Handle Intensity */
    *intens = intensity_help(stype, cast, x, y);
    if ( pixsize )
    {
	/* Probably an overkill to do antialiasing like this.
	 * For now I'm using an 3x3 filter like so:
	 * +------+------+------+
	 * | 1/16 | 1/8  | 1/16 |
	 * +------+------+------+
	 * | 1/8  | 1/4  | 1/8  |
	 * +------+------+------+
	 * | 1/16 | 1/8  | 1/16 |
	 * +------+------+------+
	 */
	*intens = 0.25 * (*intens) +
	    0.125  * intensity_help(stype, cast, x + pixsize[0], y             ) +
	    0.125  * intensity_help(stype, cast, x,              y + pixsize[1]) + 
	    0.125  * intensity_help(stype, cast, x - pixsize[0], y             ) + 
	    0.125  * intensity_help(stype, cast, x,              y - pixsize[1]) + 
	    0.0625 * intensity_help(stype, cast, x + pixsize[0], y + pixsize[1]) +
	    0.0625 * intensity_help(stype, cast, x - pixsize[0], y + pixsize[1]) + 
	    0.0625 * intensity_help(stype, cast, x + pixsize[0], y - pixsize[1]) + 
	    0.0625 * intensity_help(stype, cast, x - pixsize[0], y - pixsize[1]);
    }
    /* Handle Bump Mapping */
    *du = intensity_help(stype, cast, x + cast->nabla, y) - (*intens);
    *dv = intensity_help(stype, cast, x, y + cast->nabla) - (*intens);
}

/* ********************* The Texture ******************** */

/*
 *  Calculate and return a texture pixel for Blender.
 *
 *  stype: index of which subtype, from the list specified earlier
 *  cast:  plugin data, including UI controls
 *  texvec: pointer to three floats, texture coordinates of the pixel
 *          whose value is to be returned.
 *  dxt,dyt: if non-null, these point to two vectors (two arrays of three floats)
 *           that define the size of the requested texture value in pixel space.
 *           They are only non-NULL when OSA is on, and are used to calculate 
 *           proper anti aliasing.
 *
 *  This function should fill in the values in the result[] array,
 *  and then return one of the following:
 *
 *    0: only filled in intensity                (TEX_INT)
 *    1: filled in a color value, and intensity  (TEX_RGB|TEX_INT)
 *    2: filled in a normal value, and intensity (TEX_NOR|TEX_INT)
 *    3: filled in everything                    (TEX_NOR|TEX_RGB|TEX_INT)
 */
int plugin_tex_doit(int stype, Cast *cast, float *texvec, float *dxt, float *dyt)
{
	float angle, sinval, cosval, x, y;

    /* Recalculate random numbers */
    if ( gRecalcRandomValues == 1 )
    {
	genRandomValues(cast);
	gRecalcRandomValues = 0;
    }

    /* Recalculate brick pattern once */
    if ( gRecalcPattern == 1 )
    {
	genPattern(cast);
	gRecalcPattern = 0;
    }

    /* Rotate texture coordinates about origin */
    angle = cast->rotation * PI_OVER_180;
    sinval = fsin(angle);
    cosval = fcos(angle);
    x = texvec[0] * cosval - texvec[1] * sinval;
    y = texvec[1] * cosval + texvec[0] * sinval;

    if ( dxt && dyt )
    {
	float pixsize[3];
	pixsize[0] = fabs(dxt[0] - dxt[0]) * 0.5;
	pixsize[1] = fabs(dyt[1] - dyt[1]) * 0.5;
	pixsize[2] = fabs(dyt[2] - dyt[2]) * 0.5; /* Just in case you want to use this info */

	doit(stype, cast, x, y, pixsize, &result[0], &result[5], &result[6]);
    }
    else
    {
	doit(stype, cast, x, y, 0, &result[0], &result[5], &result[6]);
    }

    result[7] = 0; /* This is a 2D texture */

    return 2;
}
