stokes with 2D length longer than 50. Problem report by Forrest Gimp, thanks!
1169 lines
31 KiB
C++
Executable File
1169 lines
31 KiB
C++
Executable File
|
|
//
|
|
// Copyright (C) : Please refer to the COPYRIGHT file distributed
|
|
// with this source distribution.
|
|
//
|
|
// 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "StrokeRenderer.h"
|
|
#include <fstream>
|
|
#include "BasicStrokeShaders.h"
|
|
#include "../system/PseudoNoise.h"
|
|
#include "../system/RandGen.h"
|
|
#include "../view_map/Functions0D.h"
|
|
#include "../view_map/Functions1D.h"
|
|
#include "AdvancedFunctions0D.h"
|
|
#include "AdvancedFunctions1D.h"
|
|
#include "StrokeIterators.h"
|
|
#include "../system/StringUtils.h"
|
|
#include "StrokeIO.h"
|
|
|
|
//soc #include <qimage.h>
|
|
//soc #include <QString>
|
|
extern "C" {
|
|
#include "IMB_imbuf.h"
|
|
#include "IMB_imbuf_types.h"
|
|
}
|
|
|
|
// Internal function
|
|
|
|
// soc
|
|
// void convert(const QImage& iImage, float **oArray, unsigned &oSize) {
|
|
// oSize = iImage.width();
|
|
// *oArray = new float[oSize];
|
|
// for(unsigned i=0; i<oSize; ++i) {
|
|
// QRgb rgb = iImage.pixel(i,0);
|
|
// (*oArray)[i] = ((float)qBlue(rgb))/255.f;
|
|
// }
|
|
// }
|
|
void convert(ImBuf *imBuf, float **oArray, unsigned &oSize) {
|
|
oSize = imBuf->x;
|
|
*oArray = new float[oSize];
|
|
|
|
char *pix;
|
|
for(unsigned i=0; i < oSize; ++i) {
|
|
pix = (char*) imBuf->rect + i*4;
|
|
(*oArray)[i] = ((float) pix[2] )/255.f;
|
|
}
|
|
}
|
|
|
|
namespace StrokeShaders {
|
|
|
|
//
|
|
// Thickness modifiers
|
|
//
|
|
//////////////////////////////////////////////////////////
|
|
|
|
int ConstantThicknessShader::shade(Stroke& stroke) const
|
|
{
|
|
StrokeInternal::StrokeVertexIterator v, vend;
|
|
int i=0;
|
|
int size = stroke.strokeVerticesSize();
|
|
for(v=stroke.strokeVerticesBegin(), vend=stroke.strokeVerticesEnd();
|
|
v!=vend;
|
|
++v)
|
|
{
|
|
if((1 == i) || (size-2 == i))
|
|
v->attribute().setThickness(_thickness/4.0,_thickness/4.0);
|
|
if((0 == i) || (size-1 == i))
|
|
v->attribute().setThickness(0,0);
|
|
|
|
v->attribute().setThickness(_thickness/2.0, _thickness/2.0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int ConstantExternThicknessShader::shade(Stroke& stroke) const
|
|
{
|
|
StrokeInternal::StrokeVertexIterator v, vend;
|
|
int i=0;
|
|
int size = stroke.strokeVerticesSize();
|
|
for(v=stroke.strokeVerticesBegin(), vend=stroke.strokeVerticesEnd();
|
|
v!=vend;
|
|
++v)
|
|
{
|
|
if((1 == i) || (size-2 == i))
|
|
v->attribute().setThickness(_thickness/2.0,0);
|
|
if((0 == i) || (size-1 == i))
|
|
v->attribute().setThickness(0,0);
|
|
|
|
v->attribute().setThickness(_thickness, 0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int IncreasingThicknessShader::shade(Stroke& stroke) const
|
|
{
|
|
|
|
int n=stroke.strokeVerticesSize()-1;
|
|
StrokeInternal::StrokeVertexIterator v, vend;
|
|
int i=0;
|
|
for(v=stroke.strokeVerticesBegin(), vend=stroke.strokeVerticesEnd();
|
|
v!=vend;
|
|
|
|
++v)
|
|
{
|
|
float t;
|
|
if(i < (float)n/2.f)
|
|
t = (1.0-(float)i/(float)n)*_ThicknessMin + (float)i/(float)n*_ThicknessMax;
|
|
else
|
|
t = (1.0-(float)i/(float)n)*_ThicknessMax + (float)i/(float)n*_ThicknessMin;
|
|
v->attribute().setThickness(t/2.0, t/2.0);
|
|
++i;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int ConstrainedIncreasingThicknessShader::shade(Stroke& stroke) const
|
|
{
|
|
float slength = stroke.getLength2D();
|
|
float maxT = min(_ratio*slength,_ThicknessMax);
|
|
int n=stroke.strokeVerticesSize()-1;
|
|
StrokeInternal::StrokeVertexIterator v, vend;
|
|
int i=0;
|
|
for(v=stroke.strokeVerticesBegin(), vend=stroke.strokeVerticesEnd();
|
|
v!=vend;
|
|
|
|
++v)
|
|
{
|
|
float t;
|
|
if(i < (float)n/2.f)
|
|
t = (1.0-(float)i/(float)n)*_ThicknessMin + (float)i/(float)n*maxT;
|
|
else
|
|
t = (1.0-(float)i/(float)n)*maxT + (float)i/(float)n*_ThicknessMin;
|
|
v->attribute().setThickness(t/2.0, t/2.0);
|
|
if(i == n-1)
|
|
v->attribute().setThickness(_ThicknessMin/2.0, _ThicknessMin/2.0);
|
|
++i;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int LengthDependingThicknessShader::shade(Stroke& stroke) const
|
|
{
|
|
float step = (_maxThickness-_minThickness)/3.f;
|
|
float l = stroke.getLength2D();
|
|
float thickness = 0.0;
|
|
if(l>300.f)
|
|
thickness = _minThickness+3.f*step;
|
|
else if((l< 300.f) && (l>100.f))
|
|
thickness = _minThickness+2.f*step;
|
|
else if((l<100.f) && (l>50.f))
|
|
thickness = _minThickness+1.f*step;
|
|
else if(l< 50.f)
|
|
thickness = _minThickness;
|
|
|
|
StrokeInternal::StrokeVertexIterator v, vend;
|
|
int i=0;
|
|
int size = stroke.strokeVerticesSize();
|
|
for(v=stroke.strokeVerticesBegin(), vend=stroke.strokeVerticesEnd();
|
|
v!=vend;
|
|
++v)
|
|
{
|
|
if((1 == i) || (size-2 == i))
|
|
v->attribute().setThickness(thickness/4.0,thickness/4.0);
|
|
if((0 == i) || (size-1 == i))
|
|
v->attribute().setThickness(0,0);
|
|
|
|
v->attribute().setThickness(thickness/2.0, thickness/2.0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
ThicknessVariationPatternShader::ThicknessVariationPatternShader(const string pattern_name,
|
|
float iMinThickness,
|
|
float iMaxThickness,
|
|
bool stretch)
|
|
: StrokeShader() {
|
|
_stretch = stretch;
|
|
_minThickness = iMinThickness;
|
|
_maxThickness = iMaxThickness;
|
|
ImBuf *image = 0; //soc
|
|
vector<string> pathnames;
|
|
StringUtils::getPathName(TextureManager::Options::getPatternsPath(),
|
|
pattern_name,
|
|
pathnames);
|
|
for (vector<string>::const_iterator j = pathnames.begin(); j != pathnames.end(); j++) {
|
|
ifstream ifs(j->c_str());
|
|
if (ifs.is_open()) {
|
|
//soc image.load(j->c_str());
|
|
image = IMB_loadiffname(j->c_str(), 0);
|
|
break;
|
|
}
|
|
}
|
|
if (image == 0) //soc
|
|
cerr << "Error: cannot find pattern \"" << pattern_name
|
|
<< "\" - check the path in the Options" << endl;
|
|
else
|
|
convert(image, &_aThickness, _size);
|
|
}
|
|
|
|
int ThicknessVariationPatternShader::shade(Stroke& stroke) const
|
|
{
|
|
StrokeInternal::StrokeVertexIterator v, vend;
|
|
float *array = 0;
|
|
int size;
|
|
array = _aThickness;
|
|
size = _size;
|
|
// }
|
|
int vert_size = stroke.strokeVerticesSize();
|
|
int sig = 0;
|
|
unsigned index;
|
|
const float* originalThickness;
|
|
for(v=stroke.strokeVerticesBegin(), vend=stroke.strokeVerticesEnd();
|
|
v!=vend;
|
|
++v)
|
|
{
|
|
originalThickness = v->attribute().getThickness();
|
|
if (_stretch) {
|
|
float tmp = v->u()*(_size-1);
|
|
index = (unsigned)floor(tmp);
|
|
if((tmp-index) > (index+1-tmp))
|
|
++index;
|
|
}
|
|
else
|
|
index = (unsigned)floor(v->curvilinearAbscissa());
|
|
index %= _size;
|
|
float thicknessR = array[index]*originalThickness[0];
|
|
float thicknessL = array[index]*originalThickness[1];
|
|
if(thicknessR+thicknessL < _minThickness)
|
|
{
|
|
thicknessL = _minThickness/2.f;
|
|
thicknessR = _minThickness/2.f;
|
|
}
|
|
if(thicknessR+thicknessL > _maxThickness)
|
|
{
|
|
thicknessL = _maxThickness/2.f;
|
|
thicknessR = _maxThickness/2.f;
|
|
}
|
|
if((sig==0) || (sig==vert_size-1))
|
|
v->attribute().setThickness(1, 1);
|
|
else
|
|
v->attribute().setThickness(thicknessR, thicknessL);
|
|
++sig;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static const unsigned NB_VALUE_NOISE = 512;
|
|
ThicknessNoiseShader::ThicknessNoiseShader()
|
|
:StrokeShader()
|
|
{_amplitude=1.f;_scale=1.f/2.f/(float)NB_VALUE_NOISE;}
|
|
|
|
ThicknessNoiseShader::ThicknessNoiseShader(float iAmplitude, float iPeriod)
|
|
:StrokeShader()
|
|
{_amplitude=iAmplitude;_scale=1.f/iPeriod/(float)NB_VALUE_NOISE;}
|
|
|
|
|
|
int ThicknessNoiseShader::shade(Stroke& stroke) const
|
|
{
|
|
StrokeInternal::StrokeVertexIterator v=stroke.strokeVerticesBegin(), vend=stroke.strokeVerticesEnd();
|
|
real initU1=v->strokeLength()*real(NB_VALUE_NOISE)+RandGen::drand48()*real(NB_VALUE_NOISE);
|
|
real initU2=v->strokeLength()*real(NB_VALUE_NOISE)+RandGen::drand48()*real(NB_VALUE_NOISE);
|
|
|
|
real bruit, bruit2;
|
|
PseudoNoise mynoise, mynoise2;
|
|
for(;
|
|
v!=vend;
|
|
++v)
|
|
{
|
|
|
|
bruit=mynoise.turbulenceSmooth(_scale*v->curvilinearAbscissa()+initU1,
|
|
2); //2 : nbOctaves
|
|
bruit2=mynoise2.turbulenceSmooth(_scale*v->curvilinearAbscissa()+initU2,
|
|
2); //2 : nbOctaves
|
|
const float *originalThickness = v->attribute().getThickness();
|
|
float r = bruit*_amplitude+originalThickness[0];
|
|
float l = bruit2*_amplitude+originalThickness[1];
|
|
v->attribute().setThickness(r,l);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// Color shaders
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
int ConstantColorShader::shade(Stroke& stroke) const
|
|
{
|
|
StrokeInternal::StrokeVertexIterator v, vend;
|
|
for(v=stroke.strokeVerticesBegin(), vend=stroke.strokeVerticesEnd();
|
|
v!=vend;
|
|
++v)
|
|
{
|
|
v->attribute().setColor(_color[0], _color[1], _color[2]);
|
|
v->attribute().setAlpha(_color[3]);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int IncreasingColorShader::shade(Stroke& stroke) const
|
|
{
|
|
StrokeInternal::StrokeVertexIterator v, vend;
|
|
int n=stroke.strokeVerticesSize()-1;
|
|
int yo=0;
|
|
float newcolor[4];
|
|
for(v=stroke.strokeVerticesBegin(), vend=stroke.strokeVerticesEnd();
|
|
v!=vend;
|
|
|
|
++v)
|
|
{
|
|
for(int i=0; i<4; ++i)
|
|
{
|
|
newcolor[i] = (1.0-(float)yo/(float)n)*_colorMin[i] + (float)yo/(float)n*_colorMax[i];
|
|
}
|
|
v->attribute().setColor(newcolor[0], newcolor[1], newcolor[2]);
|
|
v->attribute().setAlpha(newcolor[3]);
|
|
++yo;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
ColorVariationPatternShader::ColorVariationPatternShader(const string pattern_name,
|
|
bool stretch)
|
|
: StrokeShader() {
|
|
_stretch = stretch;
|
|
ImBuf *image = 0; //soc
|
|
vector<string> pathnames;
|
|
StringUtils::getPathName(TextureManager::Options::getPatternsPath(),
|
|
pattern_name,
|
|
pathnames);
|
|
for (vector<string>::const_iterator j = pathnames.begin(); j != pathnames.end(); j++) {
|
|
ifstream ifs(j->c_str());
|
|
if (ifs.is_open()) {
|
|
image = IMB_loadiffname(j->c_str(), 0); //soc
|
|
break;
|
|
}
|
|
}
|
|
if (image == 0) //soc
|
|
cerr << "Error: cannot find pattern \"" << pattern_name
|
|
<< "\" - check the path in the Options" << endl;
|
|
else
|
|
convert(image, &_aVariation, _size);
|
|
}
|
|
|
|
int ColorVariationPatternShader::shade(Stroke& stroke) const
|
|
{
|
|
StrokeInternal::StrokeVertexIterator v, vend;
|
|
unsigned index;
|
|
for(v=stroke.strokeVerticesBegin(), vend=stroke.strokeVerticesEnd();
|
|
v!=vend;
|
|
++v)
|
|
{
|
|
const float *originalColor = v->attribute().getColor();
|
|
if (_stretch) {
|
|
float tmp = v->u()*(_size-1);
|
|
index = (unsigned)floor(tmp);
|
|
if((tmp-index) > (index+1-tmp))
|
|
++index;
|
|
}
|
|
else
|
|
index = (unsigned)floor(v->curvilinearAbscissa());
|
|
index %= _size;
|
|
float r = _aVariation[index]*originalColor[0];
|
|
float g = _aVariation[index]*originalColor[1];
|
|
float b = _aVariation[index]*originalColor[2];
|
|
v->attribute().setColor(r,g,b);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int MaterialColorShader::shade(Stroke& stroke) const
|
|
{
|
|
Interface0DIterator v, vend;
|
|
Functions0D::MaterialF0D fun;
|
|
StrokeVertex *sv;
|
|
for(v=stroke.verticesBegin(), vend=stroke.verticesEnd();
|
|
v!=vend;
|
|
++v)
|
|
{
|
|
if (fun(v) < 0)
|
|
return -1;
|
|
const float *diffuse = fun.result.diffuse();
|
|
sv = dynamic_cast<StrokeVertex*>(&(*v));
|
|
sv->attribute().setColor(diffuse[0]*_coefficient, diffuse[1]*_coefficient, diffuse[2]*_coefficient);
|
|
sv->attribute().setAlpha(diffuse[3]);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int CalligraphicColorShader::shade(Stroke& stroke) const
|
|
{
|
|
Interface0DIterator v;
|
|
Functions0D::VertexOrientation2DF0D fun;
|
|
StrokeVertex* sv;
|
|
for(v=stroke.verticesBegin();
|
|
!v.isEnd();
|
|
++v)
|
|
{
|
|
if (fun(v) < 0)
|
|
return -1;
|
|
Vec2f vertexOri(fun.result);
|
|
Vec2d ori2d(-vertexOri[1], vertexOri[0]);
|
|
ori2d.normalizeSafe();
|
|
real scal = ori2d * _orientation;
|
|
sv = dynamic_cast<StrokeVertex*>(&(*v));
|
|
if ((scal<0))
|
|
sv->attribute().setColor(0,0,0);
|
|
else
|
|
sv->attribute().setColor(1,1,1);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
ColorNoiseShader::ColorNoiseShader()
|
|
:StrokeShader()
|
|
{_amplitude=1.f;_scale=1.f/2.f/(float)NB_VALUE_NOISE;}
|
|
|
|
ColorNoiseShader::ColorNoiseShader(float iAmplitude, float iPeriod)
|
|
:StrokeShader()
|
|
{_amplitude=iAmplitude;_scale=1.f/iPeriod/(float)NB_VALUE_NOISE;}
|
|
|
|
|
|
int ColorNoiseShader::shade(Stroke& stroke) const
|
|
{
|
|
StrokeInternal::StrokeVertexIterator v=stroke.strokeVerticesBegin(), vend=stroke.strokeVerticesEnd();
|
|
real initU=v->strokeLength()*real(NB_VALUE_NOISE)+RandGen::drand48()*real(NB_VALUE_NOISE);
|
|
|
|
real bruit;
|
|
PseudoNoise mynoise;
|
|
for(;
|
|
v!=vend;
|
|
++v)
|
|
{
|
|
|
|
bruit=mynoise.turbulenceSmooth(_scale*v->curvilinearAbscissa()+initU,
|
|
2); //2 : nbOctaves
|
|
const float *originalColor = v->attribute().getColor();
|
|
float r = bruit*_amplitude+originalColor[0];
|
|
float g = bruit*_amplitude+originalColor[1];
|
|
float b = bruit*_amplitude+originalColor[2];
|
|
v->attribute().setColor(r,g,b);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
//
|
|
// Texture Shaders
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
int TextureAssignerShader::shade(Stroke& stroke) const
|
|
{
|
|
// getBrushTextureIndex(TEXTURES_DIR "/brushes/charcoalAlpha.bmp", Stroke::HUMID_MEDIUM);
|
|
// getBrushTextureIndex(TEXTURES_DIR "/brushes/washbrushAlpha.bmp", Stroke::HUMID_MEDIUM);
|
|
// getBrushTextureIndex(TEXTURES_DIR "/brushes/oil.bmp", Stroke::HUMID_MEDIUM);
|
|
// getBrushTextureIndex(TEXTURES_DIR "/brushes/oilnoblend.bmp", Stroke::HUMID_MEDIUM);
|
|
// getBrushTextureIndex(TEXTURES_DIR "/brushes/charcoalAlpha.bmp", Stroke::DRY_MEDIUM);
|
|
// getBrushTextureIndex(TEXTURES_DIR "/brushes/washbrushAlpha.bmp", Stroke::DRY_MEDIUM);
|
|
// getBrushTextureIndex(TEXTURES_DIR "/brushes/opaqueDryBrushAlpha.bmp", Stroke::OPAQUE_MEDIUM);
|
|
// getBrushTextureIndex(TEXTURES_DIR "/brushes/opaqueBrushAlpha.bmp", Stroke::OPAQUE_MEDIUM);
|
|
|
|
TextureManager * instance = TextureManager::getInstance();
|
|
if(!instance)
|
|
return 0;
|
|
string pathname;
|
|
Stroke::MediumType mediumType;
|
|
bool hasTips = false;
|
|
switch(_textureId)
|
|
{
|
|
case 0:
|
|
//pathname = TextureManager::Options::getBrushesPath() + "/charcoalAlpha.bmp";
|
|
pathname = "/charcoalAlpha.bmp";
|
|
mediumType = Stroke::HUMID_MEDIUM;
|
|
hasTips = false;
|
|
break;
|
|
case 1:
|
|
pathname = "/washbrushAlpha.bmp";
|
|
mediumType = Stroke::HUMID_MEDIUM;
|
|
hasTips = true;
|
|
break;
|
|
case 2:
|
|
pathname = "/oil.bmp";
|
|
mediumType = Stroke::HUMID_MEDIUM;
|
|
hasTips = true;
|
|
break;
|
|
case 3:
|
|
pathname = "/oilnoblend.bmp";
|
|
mediumType = Stroke::HUMID_MEDIUM;
|
|
hasTips = true;
|
|
break;
|
|
case 4:
|
|
pathname = "/charcoalAlpha.bmp";
|
|
mediumType = Stroke::DRY_MEDIUM;
|
|
hasTips = false;
|
|
break;
|
|
case 5:
|
|
mediumType = Stroke::DRY_MEDIUM;
|
|
hasTips = true;
|
|
break;
|
|
case 6:
|
|
pathname = "/opaqueDryBrushAlpha.bmp";
|
|
mediumType = Stroke::OPAQUE_MEDIUM;
|
|
hasTips = true;
|
|
break;
|
|
case 7:
|
|
pathname = "/opaqueBrushAlpha.bmp";
|
|
mediumType = Stroke::OPAQUE_MEDIUM;
|
|
hasTips = true;
|
|
break;
|
|
default:
|
|
pathname = "/smoothAlpha.bmp";
|
|
mediumType = Stroke::OPAQUE_MEDIUM;
|
|
hasTips = false;
|
|
break;
|
|
}
|
|
unsigned int texId = instance->getBrushTextureIndex(pathname, mediumType);
|
|
stroke.setMediumType(mediumType);
|
|
stroke.setTips(hasTips);
|
|
stroke.setTextureId(texId);
|
|
return 0;
|
|
}
|
|
|
|
// FIXME
|
|
int StrokeTextureShader::shade(Stroke& stroke) const
|
|
{
|
|
TextureManager * instance = TextureManager::getInstance();
|
|
if(!instance)
|
|
return 0;
|
|
string pathname = TextureManager::Options::getBrushesPath() + "/" + _texturePath;
|
|
unsigned int texId = instance->getBrushTextureIndex(pathname, _mediumType);
|
|
stroke.setMediumType(_mediumType);
|
|
stroke.setTips(_tips);
|
|
stroke.setTextureId(texId);
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// Geometry Shaders
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
int BackboneStretcherShader::shade(Stroke& stroke) const
|
|
{
|
|
float l=stroke.getLength2D();
|
|
if(l <= 1e-6)
|
|
return 0;
|
|
|
|
StrokeInternal::StrokeVertexIterator v0=stroke.strokeVerticesBegin();
|
|
StrokeInternal::StrokeVertexIterator v1=v0;++v1;
|
|
StrokeInternal::StrokeVertexIterator vn=stroke.strokeVerticesEnd();--vn;
|
|
StrokeInternal::StrokeVertexIterator vn_1=vn;--vn_1;
|
|
|
|
|
|
Vec2d first((v0)->x(), (v0)->y());
|
|
Vec2d last((vn)->x(), (vn)->y());
|
|
|
|
Vec2d d1(first-Vec2d((v1)->x(), (v1)->y()));
|
|
d1.normalize();
|
|
Vec2d dn(last-Vec2d((vn_1)->x(), (vn_1)->y()));
|
|
dn.normalize();
|
|
|
|
Vec2d newFirst(first+_amount*d1);
|
|
(v0)->setPoint(newFirst[0], newFirst[1]);
|
|
Vec2d newLast(last+_amount*dn);
|
|
(vn)->setPoint(newLast[0], newLast[1]);
|
|
|
|
stroke.UpdateLength();
|
|
return 0;
|
|
}
|
|
|
|
int SamplingShader::shade(Stroke& stroke) const
|
|
{
|
|
stroke.Resample(_sampling);
|
|
return 0;
|
|
}
|
|
|
|
int ExternalContourStretcherShader::shade(Stroke& stroke) const
|
|
{
|
|
//float l=stroke.getLength2D();
|
|
Interface0DIterator it=stroke.verticesBegin();
|
|
Functions0D::Normal2DF0D fun;
|
|
StrokeVertex* sv;
|
|
while (!it.isEnd())
|
|
{
|
|
if (fun(it) < 0)
|
|
return -1;
|
|
Vec2f n(fun.result);
|
|
sv = dynamic_cast<StrokeVertex*>(&(*it));
|
|
Vec2d newPoint(sv->x()+_amount*n.x(), sv->y()+_amount*n.y());
|
|
sv->setPoint(newPoint[0], newPoint[1]);
|
|
++it;
|
|
}
|
|
stroke.UpdateLength();
|
|
return 0;
|
|
}
|
|
|
|
int BSplineShader::shade(Stroke& stroke) const
|
|
{
|
|
if(stroke.strokeVerticesSize() < 4)
|
|
return 0;
|
|
|
|
// Find the new vertices
|
|
vector<Vec2d> newVertices;
|
|
double t=0.f;
|
|
float _sampling = 5.f;
|
|
|
|
StrokeInternal::StrokeVertexIterator p0,p1,p2,p3, end;
|
|
p0 = stroke.strokeVerticesBegin();
|
|
p1 = p0;
|
|
p2 = p1;
|
|
p3 = p2;
|
|
end = stroke.strokeVerticesEnd();
|
|
double a[4],b[4];
|
|
int n=0;
|
|
while(p1 != end)
|
|
{
|
|
// if(p1 == end)
|
|
// p1 = p0;
|
|
if(p2 == end)
|
|
p2 = p1;
|
|
if(p3 == end)
|
|
p3 = p2;
|
|
// compute new matrix
|
|
a[0] = (-(p0)->x()+3*(p1)->x()-3*(p2)->x()+(p3)->x())/6.0;
|
|
a[1] = (3*(p0)->x()-6*(p1)->x()+3*(p2)->x())/6.0;
|
|
a[2] = (-3*(p0)->x()+3*(p2)->x())/6.0;
|
|
a[3] = ((p0)->x()+4*(p1)->x()+(p2)->x())/6.0;
|
|
|
|
b[0] = (-(p0)->y()+3*(p1)->y()-3*(p2)->y()+(p3)->y())/6.0;
|
|
b[1] = (3*(p0)->y()-6*(p1)->y()+3*(p2)->y())/6.0;
|
|
b[2] = (-3*(p0)->y()+3*(p2)->y())/6.0;
|
|
b[3] = ((p0)->y()+4*(p1)->y()+(p2)->y())/6.0;
|
|
|
|
|
|
// draw the spline depending on resolution:
|
|
Vec2d p1p2((p2)->x()-(p1)->x(), (p2)->y()-(p1)->y());
|
|
double norm = p1p2.norm();
|
|
//t = _sampling/norm;
|
|
t=0;
|
|
while(t<1)
|
|
{
|
|
newVertices.push_back(Vec2d((a[3] + t*(a[2] + t*(a[1] + t*a[0]))),
|
|
(b[3] + t*(b[2] + t*(b[1] + t*b[0])))));
|
|
t = t + _sampling/norm;
|
|
}
|
|
if(n > 2)
|
|
{
|
|
++p0;
|
|
++p1;
|
|
++p2;
|
|
++p3;
|
|
}
|
|
else
|
|
{
|
|
if(n==0)
|
|
++p3;
|
|
if(n==1)
|
|
{++p2;++p3;}
|
|
if(n==2)
|
|
{++p1;++p2;++p3;}
|
|
++n;
|
|
}
|
|
}
|
|
//last point:
|
|
newVertices.push_back(Vec2d((p0)->x(), (p0)->y()));
|
|
|
|
int originalSize = newVertices.size();
|
|
_sampling = stroke.ComputeSampling(originalSize);
|
|
|
|
// Resample and set x,y coordinates
|
|
stroke.Resample(_sampling);
|
|
int newsize = stroke.strokeVerticesSize();
|
|
|
|
int nExtraVertex=0;
|
|
if(newsize < originalSize)
|
|
cerr << "Warning: unsufficient resampling" << endl;
|
|
else
|
|
{
|
|
nExtraVertex = newsize - originalSize;
|
|
}
|
|
|
|
// assigns the new coordinates:
|
|
vector<Vec2d>::iterator p,pend;
|
|
p=newVertices.begin();pend=newVertices.end();
|
|
vector<Vec2d>::iterator last = p;
|
|
n=0;
|
|
StrokeInternal::StrokeVertexIterator it=stroke.strokeVerticesBegin(), itend=stroke.strokeVerticesEnd();
|
|
it=stroke.strokeVerticesBegin();
|
|
for(;
|
|
((it!=itend) && (p!=pend));
|
|
++it)
|
|
{
|
|
it->setX(p->x());
|
|
it->setY(p->y());
|
|
last = p;
|
|
++p;
|
|
++n;
|
|
}
|
|
// nExtraVertex should stay unassigned
|
|
for(int i=0; i< nExtraVertex; ++i)
|
|
{
|
|
it->setX(last->x());
|
|
it->setY(last->y());
|
|
if(it.isEnd())
|
|
cerr << "Warning: Problem encountered while creating B-spline" << endl;
|
|
++it;
|
|
++n;
|
|
}
|
|
stroke.UpdateLength();
|
|
return 0;
|
|
}
|
|
|
|
//!! Bezier curve stroke shader
|
|
int BezierCurveShader::shade(Stroke& stroke) const
|
|
{
|
|
if(stroke.strokeVerticesSize() < 4)
|
|
return 0;
|
|
|
|
// Build the Bezier curve from this set of data points:
|
|
vector<Vec2d> data;
|
|
StrokeInternal::StrokeVertexIterator v=stroke.strokeVerticesBegin(), vend=stroke.strokeVerticesEnd();
|
|
data.push_back(Vec2d(v->x(), v->y()));//first one
|
|
StrokeInternal::StrokeVertexIterator previous = v;++v;
|
|
for(;
|
|
v!=vend;
|
|
++v)
|
|
{
|
|
if(!((fabs(v->x() -(previous)->x())<M_EPSILON) && ((fabs(v->y() - (previous)->y())<M_EPSILON))))
|
|
data.push_back(Vec2d(v->x(), v->y()));
|
|
previous = v;
|
|
}
|
|
|
|
// Vec2d tmp;bool equal=false;
|
|
// if(data.front() == data.back())
|
|
// {
|
|
// tmp = data.back();
|
|
// data.pop_back();
|
|
// equal=true;
|
|
// }
|
|
// here we build the bezier curve
|
|
BezierCurve bcurve(data, _error);
|
|
|
|
// bad performances are here !!! // FIXME
|
|
vector<Vec2d> CurveVertices;
|
|
vector<BezierCurveSegment*>& bsegments = bcurve.segments();
|
|
vector<BezierCurveSegment*>::iterator s=bsegments.begin(),send=bsegments.end();
|
|
vector<Vec2d>& segmentsVertices = (*s)->vertices();
|
|
vector<Vec2d>::iterator p,pend;
|
|
// first point
|
|
CurveVertices.push_back(segmentsVertices[0]);
|
|
for(;
|
|
s!=send;
|
|
++s)
|
|
{
|
|
segmentsVertices = (*s)->vertices();
|
|
p=segmentsVertices.begin(); pend=segmentsVertices.end();
|
|
++p;
|
|
for(;
|
|
p!=pend;
|
|
++p)
|
|
{
|
|
CurveVertices.push_back((*p));
|
|
}
|
|
}
|
|
|
|
//if(equal)
|
|
// if(data.back() == data.front())
|
|
// {
|
|
// vector<Vec2d>::iterator d=data.begin(), dend=data.end();
|
|
// cout << "ending point = starting point" << endl;
|
|
// cout << "---------------DATA----------" << endl;
|
|
// for(;
|
|
// d!=dend;
|
|
// ++d)
|
|
// {
|
|
// cout << d->x() << "-" << d->y() << endl;
|
|
// }
|
|
// cout << "--------------BEZIER RESULT----------" << endl;
|
|
// d=CurveVertices.begin(), dend=CurveVertices.end();
|
|
// for(;d!=dend;++d)
|
|
// {
|
|
// cout << d->x() << "-" << d->y() << endl;
|
|
// }
|
|
// }
|
|
|
|
// Resample the Stroke depending on the number of
|
|
// vertices of the bezier curve:
|
|
int originalSize = CurveVertices.size();
|
|
//float sampling = stroke.ComputeSampling(originalSize);
|
|
//stroke.Resample(sampling);
|
|
stroke.Resample(originalSize);
|
|
int newsize = stroke.strokeVerticesSize();
|
|
int nExtraVertex=0;
|
|
if(newsize < originalSize)
|
|
cerr << "Warning: unsufficient resampling" << endl;
|
|
else
|
|
{
|
|
//cout << "Oversampling" << endl;
|
|
nExtraVertex = newsize - originalSize;
|
|
if(nExtraVertex != 0)
|
|
cout << "Bezier Shader : Stroke " << stroke.getId() << " have not been resampled" << endl;
|
|
}
|
|
|
|
// assigns the new coordinates:
|
|
p=CurveVertices.begin();pend=CurveVertices.end();
|
|
vector<Vec2d>::iterator last = p;
|
|
int n=0;
|
|
StrokeInternal::StrokeVertexIterator it=stroke.strokeVerticesBegin(), itend=stroke.strokeVerticesEnd();
|
|
// while(p!=pend)
|
|
// {
|
|
// ++n;
|
|
// ++p;
|
|
// }
|
|
it=stroke.strokeVerticesBegin();
|
|
for(;
|
|
((it!=itend) && (p!=pend));
|
|
++it)
|
|
{
|
|
it->setX(p->x());
|
|
it->setY(p->y());
|
|
// double x = p->x();
|
|
// double y = p->y();
|
|
// cout << "x = " << x << "-" << "y = " << y << endl;
|
|
last = p;
|
|
++p;
|
|
++n;
|
|
}
|
|
stroke.UpdateLength();
|
|
|
|
// Deal with extra vertices:
|
|
if(nExtraVertex == 0)
|
|
return 0;
|
|
|
|
// nExtraVertex should stay unassigned
|
|
vector<StrokeAttribute> attributes;
|
|
vector<StrokeVertex*> verticesToRemove;
|
|
for(int i=0; i< nExtraVertex; ++i)
|
|
{
|
|
verticesToRemove.push_back(&(*it));
|
|
if(it.isEnd())
|
|
cout << "fucked up" << endl;
|
|
++it;
|
|
++n;
|
|
}
|
|
it=stroke.strokeVerticesBegin();
|
|
for(;
|
|
it!=itend;
|
|
++it)
|
|
{
|
|
attributes.push_back(it->attribute());
|
|
}
|
|
|
|
for(vector<StrokeVertex*>::iterator vr=verticesToRemove.begin(), vrend=verticesToRemove.end();
|
|
vr!=vrend;
|
|
++vr)
|
|
{
|
|
stroke.RemoveVertex(*vr);
|
|
}
|
|
it=stroke.strokeVerticesBegin();
|
|
itend=stroke.strokeVerticesEnd();
|
|
vector<StrokeAttribute>::iterator a=attributes.begin(), aend=attributes.end();
|
|
int index = 0;
|
|
int index1 = (int)floor((float)originalSize/2.0);
|
|
int index2 = index1+nExtraVertex;
|
|
for(;
|
|
(it!=itend) && (a!=aend);
|
|
++it)
|
|
{
|
|
(it)->setAttribute(*a);
|
|
if((index <= index1)||(index>index2))
|
|
++a;
|
|
++index;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int InflateShader::shade(Stroke& stroke) const
|
|
{
|
|
// we're computing the curvature variance of the stroke.(Combo 5)
|
|
// If it's too high, forget about it
|
|
Functions1D::Curvature2DAngleF1D fun;
|
|
if (fun(stroke) < 0)
|
|
return -1;
|
|
if (fun.result > _curvatureThreshold)
|
|
return 0;
|
|
|
|
Functions0D::VertexOrientation2DF0D ori_fun;
|
|
Functions0D::Curvature2DAngleF0D curv_fun;
|
|
Functions1D::Normal2DF1D norm_fun;
|
|
Interface0DIterator it=stroke.verticesBegin();
|
|
StrokeVertex* sv;
|
|
while (!it.isEnd())
|
|
{
|
|
if (ori_fun(it) < 0)
|
|
return -1;
|
|
Vec2f ntmp(ori_fun.result);
|
|
Vec2f n(ntmp.y(), -ntmp.x());
|
|
if (norm_fun(stroke) < 0)
|
|
return -1;
|
|
Vec2f strokeN(norm_fun.result);
|
|
if(n*strokeN < 0)
|
|
{
|
|
n[0] = -n[0];
|
|
n[1] = -n[1];
|
|
}
|
|
sv = dynamic_cast<StrokeVertex*>(&(*it));
|
|
float u=sv->u();
|
|
float t = 4.f*(0.25f - (u-0.5)*(u-0.5));
|
|
if (curv_fun(it) < 0)
|
|
return -1;
|
|
float curvature_coeff = (M_PI-curv_fun.result)/M_PI;
|
|
Vec2d newPoint(sv->x()+curvature_coeff*t*_amount*n.x(), sv->y()+curvature_coeff*t*_amount*n.y());
|
|
sv->setPoint(newPoint[0], newPoint[1]);
|
|
++it;
|
|
}
|
|
stroke.UpdateLength();
|
|
return 0;
|
|
}
|
|
|
|
class CurvePiece
|
|
{
|
|
public:
|
|
StrokeInternal::StrokeVertexIterator _begin;
|
|
StrokeInternal::StrokeVertexIterator _last;
|
|
Vec2d A;
|
|
Vec2d B;
|
|
int size;
|
|
float _error;
|
|
|
|
CurvePiece(StrokeInternal::StrokeVertexIterator b, StrokeInternal::StrokeVertexIterator l, int iSize)
|
|
{
|
|
_begin = b;
|
|
_last = l;
|
|
A = Vec2d((_begin)->x(),(_begin)->y());
|
|
B = Vec2d((_last)->x(),(_last)->y());
|
|
size = iSize;
|
|
}
|
|
|
|
float error()
|
|
{
|
|
float maxE = 0.f;
|
|
for(StrokeInternal::StrokeVertexIterator it=_begin;
|
|
it!=_last;
|
|
++it)
|
|
{
|
|
Vec2d P(it->x(), it->y());
|
|
float d = GeomUtils::distPointSegment(P,A,B);
|
|
if(d > maxE)
|
|
maxE = d;
|
|
}
|
|
_error = maxE;
|
|
return maxE;
|
|
}
|
|
//! Subdivides the curve into two pieces.
|
|
// The first piece is this same object (modified)
|
|
// the second piece is returned by the method
|
|
CurvePiece * subdivide()
|
|
{
|
|
StrokeInternal::StrokeVertexIterator it=_begin;
|
|
int actualSize = 1;
|
|
for(int i=0; i<size/2; ++i)
|
|
{
|
|
++it;
|
|
++actualSize;
|
|
}
|
|
CurvePiece * second = new CurvePiece(it, _last, size-actualSize+1);
|
|
size = actualSize;
|
|
_last = it;
|
|
B = Vec2d((_last)->x(), (_last)->y());
|
|
return second;
|
|
}
|
|
};
|
|
|
|
int PolygonalizationShader::shade(Stroke& stroke) const
|
|
{
|
|
vector<CurvePiece*> _pieces;
|
|
vector<CurvePiece*> _results;
|
|
vector<CurvePiece*>::iterator cp,cpend;
|
|
|
|
// Compute first approx:
|
|
StrokeInternal::StrokeVertexIterator a=stroke.strokeVerticesBegin();
|
|
StrokeInternal::StrokeVertexIterator b=stroke.strokeVerticesEnd();--b;
|
|
int size = stroke.strokeVerticesSize();
|
|
|
|
CurvePiece * piece = new CurvePiece(a,b,size);
|
|
_pieces.push_back(piece);
|
|
|
|
while(!_pieces.empty())
|
|
{
|
|
piece = _pieces.back();_pieces.pop_back();
|
|
if(piece->error() > _error)
|
|
{
|
|
CurvePiece * second = piece->subdivide();
|
|
_pieces.push_back(second);
|
|
_pieces.push_back(piece);
|
|
}
|
|
else
|
|
{
|
|
_results.push_back(piece);
|
|
}
|
|
}
|
|
|
|
// actually modify the geometry for each piece:
|
|
for(cp=_results.begin(), cpend=_results.end();
|
|
cp!=cpend;
|
|
++cp)
|
|
{
|
|
a = (*cp)->_begin;
|
|
b = (*cp)->_last;
|
|
Vec2d u = (*cp)->B-(*cp)->A;
|
|
Vec2d n(u[1], -u[0]);n.normalize();
|
|
//Vec2d n(0,0);
|
|
float offset = ((*cp)->_error);
|
|
StrokeInternal::StrokeVertexIterator v,vlast;
|
|
for(v=a;
|
|
v!=b;
|
|
++v)
|
|
{
|
|
v->setPoint((*cp)->A.x()+v->u()*u.x()+n.x()*offset, (*cp)->A.y()+v->u()*u.y()+n.y()*offset);
|
|
}
|
|
// u.normalize();
|
|
// (*a)->setPoint((*a)->x()-u.x()*10, (*a)->y()-u.y()*10);
|
|
}
|
|
stroke.UpdateLength();
|
|
|
|
// delete stuff
|
|
for(cp=_results.begin(), cpend=_results.end();
|
|
cp!=cpend;
|
|
++cp)
|
|
{
|
|
delete (*cp);
|
|
}
|
|
_results.clear();
|
|
return 0;
|
|
}
|
|
|
|
int GuidingLinesShader::shade(Stroke& stroke) const
|
|
{
|
|
Functions1D::Normal2DF1D norm_fun;
|
|
StrokeInternal::StrokeVertexIterator a=stroke.strokeVerticesBegin();
|
|
StrokeInternal::StrokeVertexIterator b=stroke.strokeVerticesEnd();--b;
|
|
int size = stroke.strokeVerticesSize();
|
|
CurvePiece piece(a,b,size);
|
|
|
|
Vec2d u = piece.B-piece.A;
|
|
Vec2f n(u[1], -u[0]);n.normalize();
|
|
if (norm_fun(stroke) < 0)
|
|
return -1;
|
|
Vec2f strokeN(norm_fun.result);
|
|
if(n*strokeN < 0)
|
|
{
|
|
n[0] = -n[0];
|
|
n[1] = -n[1];
|
|
}
|
|
float offset = (piece.error())/2.f*_offset;
|
|
StrokeInternal::StrokeVertexIterator v=a,vend=stroke.strokeVerticesEnd();
|
|
for(;
|
|
v!=vend;
|
|
++v)
|
|
{
|
|
v->setPoint(piece.A.x()+v->u()*u.x()+n.x()*offset, piece.A.y()+v->u()*u.y()+n.y()*offset);
|
|
}
|
|
stroke.UpdateLength();
|
|
return 0;
|
|
}
|
|
|
|
/////////////////////////////////////////
|
|
//
|
|
// Tip Remover
|
|
//
|
|
/////////////////////////////////////////
|
|
|
|
|
|
TipRemoverShader::TipRemoverShader(real tipLength)
|
|
: StrokeShader()
|
|
{
|
|
_tipLength = tipLength;
|
|
}
|
|
|
|
int
|
|
TipRemoverShader::shade(Stroke& stroke) const
|
|
{
|
|
int originalSize = stroke.strokeVerticesSize();
|
|
|
|
if(originalSize<4)
|
|
return 0;
|
|
|
|
StrokeInternal::StrokeVertexIterator v, vend;
|
|
vector<StrokeVertex*> verticesToRemove;
|
|
vector<StrokeAttribute> oldAttributes;
|
|
v=stroke.strokeVerticesBegin(); vend=stroke.strokeVerticesEnd();
|
|
for(;
|
|
v!=vend;
|
|
++v)
|
|
{
|
|
if ((v->curvilinearAbscissa()<_tipLength) ||
|
|
(v->strokeLength()-v->curvilinearAbscissa()<_tipLength))
|
|
{
|
|
verticesToRemove.push_back(&(*v));
|
|
}
|
|
oldAttributes.push_back(v->attribute());
|
|
}
|
|
|
|
if(originalSize-verticesToRemove.size() < 2)
|
|
return 0;
|
|
|
|
vector<StrokeVertex*>::iterator sv=verticesToRemove.begin(), svend=verticesToRemove.end();
|
|
for(;
|
|
sv!=svend;
|
|
++sv)
|
|
{
|
|
stroke.RemoveVertex((*sv));
|
|
}
|
|
|
|
// Resample so that our new stroke have the same
|
|
// number of vertices than before
|
|
stroke.Resample(originalSize);
|
|
|
|
if((int)stroke.strokeVerticesSize() != originalSize) //soc
|
|
cerr << "Warning: resampling problem" << endl;
|
|
|
|
// assign old attributes to new stroke vertices:
|
|
v=stroke.strokeVerticesBegin(); vend=stroke.strokeVerticesEnd();
|
|
vector<StrokeAttribute>::iterator a=oldAttributes.begin(), aend=oldAttributes.end();
|
|
//cout << "-----------------------------------------------" << endl;
|
|
for(;(v!=vend)&&(a!=aend);++v,++a)
|
|
{
|
|
v->setAttribute(*a);
|
|
//cout << "thickness = " << (*a).getThickness()[0] << "-" << (*a).getThickness()[1] << endl;
|
|
}
|
|
// we're done!
|
|
return 0;
|
|
}
|
|
|
|
int streamShader::shade(Stroke& stroke) const{
|
|
cout << stroke << endl;
|
|
return 0;
|
|
}
|
|
int fstreamShader::shade(Stroke& stroke) const{
|
|
_stream << stroke << endl;
|
|
return 0;
|
|
}
|
|
|
|
} // end of namespace StrokeShaders
|
|
|