// this creates a procedural fireball model with density and temperature per
// voxel, in openvdb format. the output file will be fireball.vdb.
// this code has been converted from a different storage backend with least
// possible care taken. this means in particular that motion vectors got lost
// on the way, conversion is single threaded and overall quite slow indeed.
//
// as far as we are concerned, you can do whatever you want with this code. the
// perlin noise snipped is based on ken perlins original reference
// implementation from 2002.
//
// if you want to change the size of the output, change the voxel size:
//
#define VOXEL_SIZE (1./8.)

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <math.h>
#include <openvdb/openvdb.h>
#include <omp.h>

#define crossproduct(v1, v2, res) \
	(res)[0] = (v1)[1]*(v2)[2] - (v2)[1]*(v1)[2];\
	(res)[1] = (v1)[2]*(v2)[0] - (v2)[2]*(v1)[0];\
	(res)[2] = (v1)[0]*(v2)[1] - (v2)[0]*(v1)[1]

#define dotproduct(u, v) ((u)[0]*(v)[0] + (u)[1]*(v)[1] + (u)[2]*(v)[2])

#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define CLAMP(a, m, M) MIN(MAX(a, m), M)

static inline void normalise(float *f)
{
	const float len = 1.0f/sqrtf(dotproduct(f, f));
	for(int k=0;k<3;k++) f[k] *= len;
}

static inline void get_perpendicular(const float* n, float* res)
{
	if(fabsf(n[1]) < 0.5)
	{
		float up[3] = {0, 1, 0};
		crossproduct(n, up, res);
	}
	else
	{
		float rg[3] = {1, 0, 0};
		crossproduct(n, rg, res);
	}
}

static inline void get_onb(const float *n, float *u, float *v)
{
	get_perpendicular(n, u);
	normalise(u);
	crossproduct(n, u, v);
	// now v should be normalised already (n and u were perpendicular and normalised)
}

static inline float fade(float t) { return t * t * t * (t * (t * 6 - 15) + 10); }
static inline float lerp(float t, float a, float b) { return a + t * (b - a); }
static inline float grad(int hash, float x, float y, float z)
{
	int h = hash & 15;                      // CONVERT LO 4 BITS OF HASH CODE
	float u = h<8 ? x : y,                 // INTO 12 GRADIENT DIRECTIONS.
		  v = h<4 ? y : h==12||h==14 ? x : z;
	return ((h&1) == 0 ? u : -u) + ((h&2) == 0 ? v : -v);
}
static const int perlin_noise_p[512] = { 151,160,137,91,90,15,
	131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
	190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
	88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
	77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
	102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
	135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
	5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
	223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
	129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
	251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
	49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
	138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180,
	151,160,137,91,90,15,
	131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
	190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
	88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
	77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
	102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
	135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
	5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
	223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
	129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
	251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
	49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
	138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
};
static inline float cnoise_improved(float x, float y, float z)
{
	int X = ((int)x) & 255,                  // FIND UNIT CUBE THAT
		Y = ((int)y) & 255,                  // CONTAINS POINT.
		Z = ((int)z) & 255;
	x -= (int)x;                                // FIND RELATIVE X,Y,Z
	y -= (int)y;                                // OF POINT IN CUBE.
	z -= (int)z;
	float u = fade(x),                                // COMPUTE FADE CURVES
		  v = fade(y),                                // FOR EACH OF X,Y,Z.
		  w = fade(z);
	int A = perlin_noise_p[X  ]+Y, AA = perlin_noise_p[A]+Z, AB = perlin_noise_p[A+1]+Z,      // HASH COORDINATES OF
		B = perlin_noise_p[X+1]+Y, BA = perlin_noise_p[B]+Z, BB = perlin_noise_p[B+1]+Z;      // THE 8 CUBE CORNERS,

	return lerp(w, lerp(v, lerp(u, grad(perlin_noise_p[AA  ], x  , y  , z   ),  // AND ADD
					grad(perlin_noise_p[BA  ], x-1, y  , z   )), // BLENDED
				lerp(u, grad(perlin_noise_p[AB  ], x  , y-1, z   ),  // RESULTS
					grad(perlin_noise_p[BB  ], x-1, y-1, z   ))),// FROM  8
			lerp(v, lerp(u, grad(perlin_noise_p[AA+1], x  , y  , z-1 ),  // CORNERS
					grad(perlin_noise_p[BA+1], x-1, y  , z-1 )), // OF CUBE
				lerp(u, grad(perlin_noise_p[AB+1], x  , y-1, z-1 ),
					grad(perlin_noise_p[BB+1], x-1, y-1, z-1 ))));
}

static inline float pnoise(const float* p, const int i, const float size)
{
	float f = size;
	float sum = 0.0f;
	float a = 1.0f*f;

	float p0 = fabsf(p[0])/f;
	float p1 = fabsf(p[1])/f;
	float p2 = fabsf(p[2])/f;

	for(int k=0;k<i;k++)
	{
		sum += cnoise_improved(p0, p1, p2)*a;
		a /= 2.0f;
		p0 *= 2;
		p1 *= 2;
		p2 *= 2;
	}
	return sum;
}


static inline float torus_density(
		const float *p,   // point to evaluate
		const float *c0,  // center of the torus
		const float r,    // small radius
		const float R,    // big radius
		const float *n0)  // normal/orientation
{
	float tx = p[0], ty = p[1], tz = p[2];

	const float xt0[3] = {tx-c0[0], ty-c0[1], tz-c0[2]};
	float a0[3], b0[3];
	get_onb(n0, a0, b0);
	const float x0[3] = {dotproduct(xt0, a0), dotproduct(xt0, b0), dotproduct(xt0, n0)};
	const float norm0 = sqrtf(x0[0]*x0[0] + x0[1]*x0[1]);
	const float dd0[3] = {x0[0]/norm0 * R - x0[0], x0[1]/norm0 * R - x0[1], 0.0f - x0[2]};
	return r - sqrtf(dotproduct(dd0, dd0));
}

static inline float fake_rand(uint32_t *seed)
{
	*seed *= 0x15A4E35u;
	return (float) (*seed >> 16) / (0xFFFF + 1.0f);
}

static inline float fireball_density(const float *p, float *temperature)
{
	if(p[0] < -16 || p[0] > 16 ||
			p[1] < -16 || p[1] > 16 ||
			p[2] < -16 || p[2] > 16) return 0.0f; // early out
	const float scale = 5;
	const int octaves = 3;
	const float q0[3] = {p[0] + 70000, p[1] + 10000, p[2] + 23000};
	const float q1[3] = {p[0] + 10000, p[1] + 70000, p[2] + 10000};
	const float q2[3] = {p[0] + 13000, p[1] + 10000, p[2] + 70000};
	const float ng[3] = { pnoise(q0, octaves, scale), pnoise(q1, octaves, scale), pnoise(q2, octaves, scale)};
	const float pp[3] = {p[0] - ng[0], p[1] - ng[1], p[2] - ng[2]};
	const float add = 1e-3f*(ng[0]*ng[0]+ng[1]*ng[1]+ng[2]*ng[2]);

	const float val = fmaxf(0.0, 4.0f-fabsf(10.0f - sqrtf(pp[0]*pp[0] + pp[1]*pp[1] + pp[2]*pp[2])));
	*temperature = 800.0*(add+val);
	assert(*temperature >= 0.f);
	if(val > 0.0f) return add+val;
	return 0.0f;
}

	template<class GridType> void
make_fireball(GridType &gdens, GridType &gtemp, const float voxel_size)
{
	float aabb[6] = {-32.0f, -32.0f, -32.0f, 32.0f, 32.0f, 32.0f};
	for(int k=0;k<6;k++) aabb[k] /= voxel_size;
#pragma omp parallel for
	for (int i = (int)(aabb[0]); i < (int)(aabb[3]); i++)
	{
		typename GridType::Accessor adens = gdens.getAccessor();
		typename GridType::Accessor atemp = gtemp.getAccessor();
		for (int j = aabb[1]; j < aabb[4]; j++)
		for (int k = aabb[2]; k < aabb[5]; k++)
		{
			const float p[3] = {i*voxel_size, j*voxel_size, k*voxel_size};
			float temperature = 0.0f;
			float density = fireball_density(p, &temperature);
			if(density > 0.0f)
			{
#pragma omp critical
				{
					adens.setValue(openvdb::Coord(i, j, k), density);
					atemp.setValue(openvdb::Coord(i, j, k), temperature);
				}
			}
		}
	}
}

int main (int argc, char *argv[])
{
	openvdb::initialize();
	openvdb::FloatGrid::Ptr gdens = openvdb::FloatGrid::create(0.0f);
	openvdb::FloatGrid::Ptr gtemp = openvdb::FloatGrid::create(0.0f);
	const float voxel_size = VOXEL_SIZE;
	make_fireball(*gdens, *gtemp, voxel_size);
	gdens->setTransform(openvdb::math::Transform::createLinearTransform(voxel_size));
	gtemp->setTransform(openvdb::math::Transform::createLinearTransform(voxel_size));
	gdens->setName("density");
	gtemp->setName("temperature");
	openvdb::io::File file("fireball.vdb");
	openvdb::GridPtrVec grids;
	grids.push_back(gdens);
	grids.push_back(gtemp);
	file.write(grids);
	file.close();
	return 0;
}
