/*
 * ***** BEGIN GPL LICENSE BLOCK *****
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * ***** END GPL LICENSE BLOCK *****
 */

/** \file blender/freestyle/intern/stroke/AdvancedStrokeShaders.cpp
 *  \ingroup freestyle
 *  \brief Fredo's stroke shaders
 *  \author Fredo Durand
 *  \date 17/12/2002
 */

#include "AdvancedStrokeShaders.h"
#include "StrokeIterators.h"

#include "../system/PseudoNoise.h"
#include "../system/RandGen.h"

namespace Freestyle {

/////////////////////////////////////////
//
//  CALLIGRAPHICS SHADER
//
/////////////////////////////////////////

CalligraphicShader::CalligraphicShader(real iMinThickness, real iMaxThickness, const Vec2f &iOrientation, bool clamp)
: StrokeShader()
{
	_minThickness = iMinThickness;
	_maxThickness = iMaxThickness;
	_orientation = iOrientation;
	_orientation.normalize();
	_clamp = clamp;
}

int CalligraphicShader::shade(Stroke &ioStroke) const
{
	Interface0DIterator v;
	Functions0D::VertexOrientation2DF0D fun;
	StrokeVertex *sv;
	for (v = ioStroke.verticesBegin(); !v.isEnd(); ++v) {
		real thickness;
		if (fun(v) < 0)
			return -1;

		Vec2f vertexOri(fun.result);
		Vec2r ori2d(-vertexOri[1], vertexOri[0]);
		ori2d.normalizeSafe();
		real scal = ori2d * _orientation;
		sv = dynamic_cast<StrokeVertex*>(&(*v));
		if (_clamp && (scal<0)) {
			scal = 0.0;
			sv->attribute().setColor(1, 1, 1);
		}
		else {
			scal = fabs(scal);
			sv->attribute().setColor(0, 0, 0);
		}
		thickness = _minThickness + scal * (_maxThickness - _minThickness);
		if (thickness < 0.0)
			thickness = 0.0;
		sv->attribute().setThickness(thickness / 2.0, thickness / 2.0);
	}

	return 0;
}

/////////////////////////////////////////
//
//  SPATIAL NOISE SHADER
//
/////////////////////////////////////////

static const unsigned NB_VALUE_NOISE = 512;

SpatialNoiseShader::SpatialNoiseShader(float ioamount, float ixScale, int nbOctave, bool smooth, bool pureRandom)
: StrokeShader()
{
	_amount = ioamount;
	if (ixScale == 0)
		_xScale = 0;
	else
		_xScale = 1.0 / ixScale / real(NB_VALUE_NOISE);
	_nbOctave = nbOctave;
	_smooth = smooth;
	_pureRandom = pureRandom;
}

int SpatialNoiseShader::shade(Stroke &ioStroke) const
{
	Interface0DIterator v, v2;
	v = ioStroke.verticesBegin();
	Vec2r p(v->getProjectedX(), v->getProjectedY());
	v2 = v;
	++v2;
	Vec2r p0(v2->getProjectedX(), v2->getProjectedY());
	p0 = p + 2 * (p - p0);
	StrokeVertex *sv;
	sv = dynamic_cast<StrokeVertex*>(&(*v));
	real initU = sv->strokeLength() * real(NB_VALUE_NOISE);
	if (_pureRandom)
		initU += RandGen::drand48() * real(NB_VALUE_NOISE);

	Functions0D::VertexOrientation2DF0D fun;
	while (!v.isEnd()) {
		sv = dynamic_cast<StrokeVertex*>(&(*v));
		Vec2r p(sv->getPoint());
		if (fun(v) < 0)
			return -1;
		Vec2r vertexOri(fun.result);
		Vec2r ori2d(vertexOri[0], vertexOri[1]);
		ori2d = Vec2r(p - p0);
		ori2d.normalizeSafe();

		PseudoNoise mynoise;
		real bruit;

		if (_smooth)
			bruit = mynoise.turbulenceSmooth(_xScale * sv->curvilinearAbscissa() + initU, _nbOctave);
		else
			bruit = mynoise.turbulenceLinear(_xScale * sv->curvilinearAbscissa() + initU, _nbOctave);

		Vec2r noise(-ori2d[1] * _amount * bruit, ori2d[0] * _amount * bruit);

		sv->setPoint(p[0] + noise[0], p[1] + noise[1]);
		p0 = p;

		++v;
	}

	ioStroke.UpdateLength();

	return 0;
}

/////////////////////////////////////////
//
//  SMOOTHING SHADER
//
/////////////////////////////////////////

SmoothingShader::SmoothingShader(int ionbIteration, real iFactorPoint, real ifactorCurvature,
                                 real iFactorCurvatureDifference, real iAnisoPoint, real iAnisoNormal,
                                 real iAnisoCurvature, real iCarricatureFactor)
: StrokeShader()
{
	_nbIterations = ionbIteration;
	_factorCurvature = ifactorCurvature;
	_factorCurvatureDifference = iFactorCurvatureDifference;
	_anisoNormal = iAnisoNormal;
	_anisoCurvature = iAnisoCurvature;
	_carricatureFactor = iCarricatureFactor;
	_factorPoint = iFactorPoint;
	_anisoPoint = iAnisoPoint;
}

int SmoothingShader::shade(Stroke &ioStroke) const
{
	// cerr << " Smoothing a stroke  " << endl;

	Smoother smoother(ioStroke);
	smoother.smooth(_nbIterations, _factorPoint, _factorCurvature, _factorCurvatureDifference, _anisoPoint,
	                _anisoNormal, _anisoCurvature, _carricatureFactor);
	return 0;
}

// SMOOTHER
////////////////////////////

Smoother::Smoother(Stroke &ioStroke)
{
	_stroke = &ioStroke;

	_nbVertices = ioStroke.vertices_size();
	_vertex = new Vec2r[_nbVertices];
	_curvature = new real[_nbVertices];
	_normal = new Vec2r[_nbVertices];
	StrokeInternal::StrokeVertexIterator v, vend;
	int i = 0;
	for (v = ioStroke.strokeVerticesBegin(), vend = ioStroke.strokeVerticesEnd(); v != vend; ++v, ++i) {
		_vertex[i] = (v)->getPoint();
	}
	Vec2r vec_tmp(_vertex[0] - _vertex[_nbVertices - 1]);
	_isClosedCurve = (vec_tmp.norm() < M_EPSILON);

	_safeTest = (_nbVertices > 4);
}

Smoother::~Smoother()
{
	delete[] _vertex;
	delete[] _curvature;
	delete[] _normal;
}

void Smoother::smooth(int nbIteration, real iFactorPoint, real ifactorCurvature, real iFactorCurvatureDifference,
                      real iAnisoPoint, real iAnisoNormal, real iAnisoCurvature, real iCarricatureFactor)
{
	_factorCurvature = ifactorCurvature;
	_factorCurvatureDifference = iFactorCurvatureDifference;
	_anisoNormal = iAnisoNormal;
	_anisoCurvature = iAnisoCurvature;
	_carricatureFactor = iCarricatureFactor;
	_factorPoint = iFactorPoint;
	_anisoPoint = iAnisoPoint;

	for (int i = 0; i < nbIteration; ++i)
		iteration ();
	copyVertices();
}

static real edgeStopping(real x, real sigma)
{
	if (sigma == 0.0)
		return 1.0;
	return exp(-x * x / (sigma * sigma));
}

void Smoother::iteration()
{
	computeCurvature();
	for (int i = 1; i < (_nbVertices - 1); ++i) {
		real motionNormal = _factorCurvature * _curvature[i] * edgeStopping(_curvature[i], _anisoNormal);

		real diffC1 = _curvature[i] - _curvature[i - 1];
		real diffC2 = _curvature[i] - _curvature[i + 1];
		real motionCurvature = edgeStopping(diffC1, _anisoCurvature) * diffC1 +
		                       edgeStopping(diffC2, _anisoCurvature) * diffC2; //_factorCurvatureDifference;
		motionCurvature *= _factorCurvatureDifference;
		//motionCurvature = _factorCurvatureDifference * (diffC1 + diffC2);
		if (_safeTest)
			_vertex[i] = Vec2r(_vertex[i] + (motionNormal + motionCurvature) * _normal[i]);
		Vec2r v1(_vertex[i - 1] - _vertex[i]);
		Vec2r v2(_vertex[i + 1] - _vertex[i]);
		real d1 = v1.norm();
		real d2 = v2.norm();
		_vertex[i] = Vec2r(_vertex[i] +
		                   _factorPoint * edgeStopping(d2, _anisoPoint) * (_vertex[i - 1] - _vertex[i]) +
		                   _factorPoint * edgeStopping(d1, _anisoPoint) * (_vertex[i + 1] - _vertex[i]));
	}

	if (_isClosedCurve) {
		real motionNormal = _factorCurvature * _curvature[0] * edgeStopping(_curvature[0], _anisoNormal);

		real diffC1 = _curvature[0] - _curvature[_nbVertices - 2];
		real diffC2 = _curvature[0] - _curvature[1];
		real motionCurvature = edgeStopping(diffC1, _anisoCurvature) * diffC1 +
		                       edgeStopping(diffC2, _anisoCurvature) * diffC2; //_factorCurvatureDifference;
		motionCurvature *= _factorCurvatureDifference;
		//motionCurvature = _factorCurvatureDifference * (diffC1 + diffC2);
		_vertex[0] = Vec2r(_vertex[0] + (motionNormal + motionCurvature) * _normal[0]);
		_vertex[_nbVertices - 1] = _vertex[0];
	}
}


void Smoother::computeCurvature()
{
	int i;
	Vec2r BA, BC, normalCurvature;
	for (i = 1; i < (_nbVertices - 1); ++i) {
		BA = _vertex[i - 1] - _vertex[i];
		BC = _vertex[i + 1] - _vertex[i];
		real lba = BA.norm(), lbc = BC.norm();
		BA.normalizeSafe();
		BC.normalizeSafe();
		normalCurvature = BA + BC;

		_normal[i] = Vec2r(-(BC - BA)[1], (BC - BA)[0]);
		_normal[i].normalizeSafe();

		_curvature[i] = normalCurvature * _normal[i];
		if (lba + lbc > M_EPSILON)
			_curvature[i] /= (0.5 * lba + lbc);
	}
	_curvature[0] = _curvature[1];
	_curvature[_nbVertices - 1] = _curvature[_nbVertices - 2];
	Vec2r di(_vertex[1] - _vertex[0]);
	_normal[0] = Vec2r(-di[1], di[0]);
	_normal[0].normalizeSafe();
	di = _vertex[_nbVertices - 1] - _vertex[_nbVertices - 2];
	_normal[_nbVertices - 1] = Vec2r(-di[1], di[0]);
	_normal[_nbVertices - 1].normalizeSafe();

	if (_isClosedCurve) {
		BA = _vertex[_nbVertices - 2] - _vertex[0];
		BC = _vertex[1] - _vertex[0];
		real lba = BA.norm(), lbc = BC.norm();
		BA.normalizeSafe();
		BC.normalizeSafe();
		normalCurvature = BA + BC;

		_normal[i] = Vec2r(-(BC - BA)[1], (BC - BA)[0]);
		_normal[i].normalizeSafe();

		_curvature[i] = normalCurvature * _normal[i];
		if (lba + lbc > M_EPSILON)
			_curvature[i] /= (0.5 * lba + lbc);

		_normal[_nbVertices - 1] = _normal[0];
		_curvature[_nbVertices - 1] = _curvature[0];
	}
}

void Smoother::copyVertices()
{
	int i = 0;
	StrokeInternal::StrokeVertexIterator v, vend;
	for (v = _stroke->strokeVerticesBegin(), vend = _stroke->strokeVerticesEnd(); v != vend; ++v) {
		const Vec2r p0((v)->getPoint());
		const Vec2r p1(_vertex[i]);
		Vec2r p(p0 + _carricatureFactor * (p1 - p0));

		(v)->setPoint(p[0], p[1]);
		++i;
	}
	_stroke->UpdateLength();
}

} /* namespace Freestyle */
