apophysis7x/Plugin/hexes.c

290 lines
8.1 KiB
C
Raw Permalink Normal View History

/*
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;
}