/*
    Apophysis Plugin

    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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

// "Hexes" variation breaks plane into hexagonal cells and applies same
//    power, scaling, rotation.

// voronoi is additional, not required in all Apophysis plugins
#define VORONOI_MAXPOINTS 25

double vratio( double P[2], double Q[2], double U[2] );
int closest( double P[VORONOI_MAXPOINTS][2], int n, double U[2] );
double voronoi( double P[VORONOI_MAXPOINTS][2], int n, int q, double U[2] );

// Cheap and cheerful vector definitions for 3D :-)
// They just make reading vector code based on arrays
// slightly nicer for me! E.g. use V[_x_] instead of V[0].

#define _x_ 0
#define _y_ 1
#define _z_ 2

// Must define this structure before we include apoplugin.h
typedef struct
{
	double hexes_cellsize;
	double hexes_power;
	double hexes_rotate;
	double hexes_scale;
	// P is a working list of points
	double P[VORONOI_MAXPOINTS][2];

	double rotSin;
	double rotCos;
} Variables;

#define _USE_MATH_DEFINES
#include "apoplugin.h"

// Set the name of this plugin
APO_PLUGIN("hexes");

// Define the Variables
APO_VARIABLES(
    VAR_REAL(hexes_cellsize, 1.0),
    VAR_REAL(hexes_power, 1.0),
    VAR_REAL(hexes_rotate, 0.166),
    VAR_REAL(hexes_scale, 1.0)
);

// Following are pre-calculated fixed multipliers for converting
// between "Hex" co-ordinates and "Original" co-ordinates.

// Xh = (Xo + sqrt(3) * Yo) / (3 * l)
static const double AXhXo = 1.0/3.0;
static const double AXhYo = 1.7320508075688772935/3.0;
// Now:  Xh = ( AXhXo * Xo + AXhYo * Yo ) / l;

// Yh = (-Xo + sqrt(3) * Yo) / (3 * l)
static const double AYhXo = -1.0/3.0;
static const double AYhYo = 1.7320508075688772935/3.0;
// Now:  Yh = ( AYhXo * Xo + AYhYo * Yo ) / l;

// Xo = 3/2 * l * (Xh - Yh)
static const double AXoXh = 1.5;
static const double AXoYh = -1.5;
// Now:  Xo = ( AXoXh * Xh + AXoYh * Yh ) * l;

// Yo = sqrt(3)/2 * l * (Xh + Yh)
static const double AYoXh = 1.7320508075688772935/2.0;
static const double AYoYh = 1.7320508075688772935/2.0;
// Now:  Yo = ( AYoXh * Xh + AYoYh * Yh ) * l;

double vratio( double P[2], double Q[2], double U[2] ) {
	double PmQx, PmQy;

	PmQx = P[_x_] - Q[_x_];
	PmQy = P[_y_] - Q[_y_];

	if ( 0.0 == PmQx && 0.0 == PmQy ) {
		return 1.0;
	}

	return 2.0 * ( ( U[_x_] - Q[_x_] ) * PmQx + ( U[_y_] - Q[_y_] ) * PmQy ) / ( PmQx * PmQx + PmQy * PmQy );
}

// Closest point to U from array P.
//  P is an array of points
//  n is number of points to check
//  U is location to find closest
int closest( double P[VORONOI_MAXPOINTS][2], int n, double U[2] ) {
	double d2;
	double d2min = 1.0e100;
	int i, j;

	for( i = 0; i < n; i++ ) {
		d2 = (P[i][_x_] - U[_x_]) * (P[i][_x_] - U[_x_]) + (P[i][_y_] - U[_y_]) * (P[i][_y_] - U[_y_]);
		if ( d2 < d2min ) {
			d2min = d2;
			j = i;
		}
	}

	return j;
}

// Voronoi "value" is 0.0 (centre) to 1.0 (edge) if inside cell . . . higher values
// mean that point is not in the cell defined by chosen centre.
//  P is an array of points defining cell centres
//  n is number of points in array
//  q is chosen centre to measure distance from
//  U is point to test
double voronoi( double P[VORONOI_MAXPOINTS][2], int n, int q, double U[2] ) {
	double ratio;
	double ratiomax = -1.0e100;
	int i;

	for( i = 0; i < n; i++ ) {
		if ( i != q ) {
			ratio = vratio( P[i], P[q], U );
			if ( ratio > ratiomax ) {
				ratiomax = ratio;
			}
		}
	}

	return ratiomax;
}


// You must call the argument "vp".
int PluginVarPrepare(Variation* vp)
{
	// Pre-calculate rotation matrix, to save time later . . .
    VAR(rotSin) = sin( VAR(hexes_rotate) * 2 * M_PI );
    VAR(rotCos) = cos( VAR(hexes_rotate) * 2 * M_PI );

    return TRUE; // Always return TRUE.
}

// You must call the argument "vp".
int PluginVarCalc(Variation* vp)
{
	double XCh, YCh, XCo, YCo, DXo, DYo, L, L1, L2, R, R1, R2, s, trgL, Vx, Vy;
	double U[2];

	// For speed/convenience
	s = VAR(hexes_cellsize);

	// Infinite number of small cells? No effect . . .
    if ( 0.0 == s ) {
		return TRUE;
	}

	// Get co-ordinates, and convert to hex co-ordinates
    U[_x_] = FTx;
    U[_y_] = FTy;

    XCh = floor( ( AXhXo * U[_x_] + AXhYo * U[_y_] ) / s );
    YCh = floor( ( AYhXo * U[_x_] + AYhYo * U[_y_] ) / s );

	// Get a set of 4 hex centre points, based around the one above
	int i = 0;
	double di, dj;
	for (di = XCh; di < XCh + 1.1; di += 1.0) {
		for (dj = YCh; dj < YCh + 1.1; dj += 1.0) {
			VAR(P)[i][_x_] = (AXoXh * di + AXoYh * dj ) * s;
			VAR(P)[i][_y_] = (AYoXh * di + AYoYh * dj ) * s;
			i++;
		}
	}

	int q = closest( VAR(P), 4, U );

	double offset[4][2] = { {  0.0, 0.0}, {  0.0, 1.0},
							{  1.0, 0.0}, {  1.0, 1.0}
						 };

	// Remake list starting from chosen hex, ensure it is completely surrounded (total 7 points)

	// First adjust centres according to which one was found to be closest
	XCh += offset[q][_x_];
	YCh += offset[q][_y_];

	// First point is central/closest

	XCo = (AXoXh * XCh + AXoYh * YCh ) * s;
	YCo = (AYoXh * XCh + AYoYh * YCh ) * s;
	VAR(P)[0][_x_] = XCo;
	VAR(P)[0][_y_] = YCo;

	// Next six points are based on hex graph (6 hexes around centre). As long as
	// centre points are not too distorted from simple hex, this defines all possible edges

	// In hex co-ords, offsets are: (0,1) (1,1) (1,0) (0,-1) (-1,-1) (-1, 0)
	VAR(P)[1][_x_] = XCo + ( AXoYh ) * s;
	VAR(P)[1][_y_] = YCo + ( AYoYh ) * s;

	VAR(P)[2][_x_] = XCo + ( AXoXh + AXoYh ) * s;
	VAR(P)[2][_y_] = YCo + ( AYoXh + AYoYh ) * s;

	VAR(P)[3][_x_] = XCo + ( AXoXh ) * s;
	VAR(P)[3][_y_] = YCo + ( AYoXh ) * s;

	VAR(P)[4][_x_] = XCo - AXoYh * s;
	VAR(P)[4][_y_] = YCo - AYoYh * s;

	VAR(P)[5][_x_] = XCo - ( AXoXh + AXoYh ) * s;
	VAR(P)[5][_y_] = YCo - ( AYoXh + AYoYh ) * s;

	VAR(P)[6][_x_] = XCo - AXoXh * s;
	VAR(P)[6][_y_] = YCo - AYoXh * s;

	L1 = voronoi( VAR(P), 7, 0, U );

	// Delta vector from centre of hex
	DXo = U[_x_] - VAR(P)[0][_x_];
	DYo = U[_y_] - VAR(P)[0][_y_];

	/////////////////////////////////////////////////////////////////
	// Apply "interesting bit" to cell's DXo and DYo co-ordinates

	// trgL is the defined value of l, independent of any rotation
	trgL = pow( L1 + 1e-100, VAR(hexes_power) ) * VAR(hexes_scale);

	// Rotate
	Vx = DXo * VAR(rotCos) + DYo * VAR(rotSin);
    Vy = -DXo * VAR(rotSin) + DYo * VAR(rotCos);

	//////////////////////////////////////////////////////////////////
	// Measure voronoi distance again

	U[_x_] = Vx + VAR(P)[0][_x_];
	U[_y_] = Vy + VAR(P)[0][_y_];
	L2 = voronoi( VAR(P), 7, 0, U );

	//////////////////////////////////////////////////////////////////
	// Scale to meet target size . . . adjust according to how close
	// we are to the edge

	// Code here attempts to remove the "rosette" effect caused by
	// scaling between

	// L is maximum of L1 or L2 . . .
	// When L = 0.8 or higher . . . match trgL/L2 exactly
	// When L = 0.5 or less . . . match trgL/L1 exactly

    R1 = trgL / ( L1 + 1e-100 );
    R2 = trgL / ( L2 + 1e-100 );

	L = ( L1 > L2 ) ? L1 : L2;

	if ( L < 0.5 ) {
		R = trgL / L1;
	} else {
		if ( L > 0.8 ) {
			R = trgL / L2;
		} else {
			R = ( ( trgL / L1 ) * ( 0.8 - L )  + ( trgL / L2 ) * ( L - 0.5 ) ) / 0.3;
		}
	}

	Vx *= R;
	Vy *= R;

	// Add cell centre co-ordinates back in
    Vx += VAR(P)[0][_x_];
    Vy += VAR(P)[0][_y_];

	// Finally add values in
	FPx += VVAR * Vx;
	FPy += VVAR * Vy;

	return TRUE;
}
