dollar/dollar/tools.cpp

278 lines
7.8 KiB
C++

/** @file
* @author Edouard DUPIN
* @copyright 2016, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#include <dollar/tools.h>
#include <dollar/debug.h>
#include <math.h>
#include <algorithm>
float dollar::pathLength(std::vector<vec2> _points) {
float distance = 0;
for (size_t iii = 1; iii < _points.size(); ++iii) {
distance += (_points[iii] - _points[iii-1]).length();
}
return distance;
}
vec2 dollar::getBaryCenter(const std::vector<vec2>& _points) {
vec2 center(0,0);
for (auto &it : _points) {
center += it;
}
center /= float(_points.size());
return center;
}
// TODO: Change this with the use of a generic matrix 2D...
std::vector<vec2> dollar::rotateBy(const std::vector<vec2>& _points, float _rotation) {
std::vector<vec2> out;
vec2 center = getBaryCenter(_points);
float cosine = std::cos(_rotation);
float sine = std::sin(_rotation);
for (auto &it : _points) {
float qx = (it.x() - center.x()) * cosine - (it.y() - center.y()) * sine + center.x();
float qy = (it.x() - center.x()) * sine + (it.y() - center.y()) * cosine + center.y();
out.push_back(vec2(qx, qy));
}
return out;
}
std::vector<vec2> dollar::rotateToZero(const std::vector<vec2>& _points) {
vec2 center = getBaryCenter(_points);
float rotation = std::atan2(center.y() - _points[0].y(), center.x() - _points[0].x());
return rotateBy(_points, -rotation);
}
float maxKeepAspectRatio = 5.5f;
// TODO : Rework this to have a correct scale with keeping aspect ration ... or not ...
std::vector<vec2> dollar::scaleToOne(const std::vector<vec2>& _points, bool _keepAspectRation) {
dollar::Rectangle box(_points);
std::vector<vec2> out;
vec2 scale(1.0f/box.getSize().x(), 1.0f/box.getSize().y());
vec2 offset(0,0);
/*
float aspectRatio = box.getSize().x() / box.getSize().y();
if (aspectRatio < 1.0f) {
aspectRatio = 1.0f / aspectRatio;
}
if (aspectRatio > maxKeepAspectRatio) {
_keepAspectRation = true;
}
*/
if (_keepAspectRation == true) {
float val = 1;
offset = box.getSize() * val;
if (box.getSize().x() > box.getSize().y()) {
val = 1.0f/box.getSize().x();
offset = vec2(0.0f, (1.0f-offset.y())*0.5f);
} else {
val = 1.0f/box.getSize().y();
offset = vec2((1.0f-offset.x())*0.5f, 0.0f);
}
scale = vec2(val,val);
}
for (auto &it : _points) {
vec2 tmp = it - box.getPos();
tmp *= scale;
//tmp += offset;
out.push_back(tmp);
}
return out;
}
std::vector<std::vector<vec2>> dollar::scaleToOne(const std::vector<std::vector<vec2>>& _points, bool _keepAspectRation) {
dollar::Rectangle box(_points);
std::vector<std::vector<vec2>> out;
vec2 scale(1.0f/box.getSize().x(), 1.0f/box.getSize().y());
vec2 offset(0,0);
/*
float aspectRatio = box.getSize().x() / box.getSize().y();
if (aspectRatio < 1.0f) {
aspectRatio = 1.0f / aspectRatio;
}
if (aspectRatio > maxKeepAspectRatio) {
_keepAspectRation = true;
}
*/
if (_keepAspectRation == true) {
float val = 1;
offset = box.getSize() * val;
if (box.getSize().x() > box.getSize().y()) {
val = 1.0f/box.getSize().x();
offset = vec2(0.0f, (1.0f-offset.y())*0.5f);
} else {
val = 1.0f/box.getSize().y();
offset = vec2((1.0f-offset.x())*0.5f, 0.0f);
}
scale = vec2(val,val);
}
for (auto &it : _points) {
std::vector<vec2> stroke;
for (auto &itPoint : it) {
vec2 tmp = itPoint - box.getPos();
tmp *= scale;
//tmp += offset;
stroke.push_back(tmp);
}
out.push_back(stroke);
}
return out;
}
std::vector<vec2> dollar::translateBariCenterToZero(std::vector<vec2> _points) {
std::vector<vec2> out;
vec2 center = getBaryCenter(_points);
for (auto &it :_points) {
out.push_back(it - center);
}
return out;
}
static std::vector<vec2> discretize(std::vector<vec2> _points, float _interval) {
std::vector<vec2> out;
if (_points.size() == 0) {
return out;
}
// same first point ==> no change
out.push_back(_points.front());
float distance = 0.0f;
// For all other point we have to resample elements
for (size_t iii=1; iii<_points.size(); ++iii) {
vec2 currentPoint = _points[iii];
vec2 previousPoint = _points[iii-1];
float tmpDist = (currentPoint-previousPoint).length();
if ((distance + tmpDist) >= _interval) {
vec2 point = previousPoint + (currentPoint - previousPoint) * ((_interval - distance) / tmpDist);
out.push_back(point);
_points.insert(_points.begin() + iii, point);
distance = 0.0;
} else {
distance += tmpDist;
}
}
return out;
}
std::vector<vec2> dollar::resample(std::vector<vec2> _points, int32_t _nbPoints) {
std::vector<vec2> out;
if (_points.size() == 0) {
return out;
}
// calculate the interval between every points ...
float interval = pathLength(_points) / (_nbPoints - 1);
out = discretize(_points, interval);
// somtimes we fall a rounding-error short of adding the last point, so add it if so
if (int64_t(out.size()) == (_nbPoints - 1)) {
out.push_back(_points.back());
}
return out;
}
std::vector<std::vector<vec2>> dollar::makeReferenceStrokes(const std::vector<std::vector<vec2>>& _strokes) {
std::vector<std::vector<vec2>> out;
// create the ordr of all possibilities of writing the strokes ... (ABC, ACB, BAC, BCA ...)
std::vector<size_t> order;
for(size_t iii=0; iii<_strokes.size(); ++iii) {
order.push_back(iii);
}
// For all orders (every permutation of the path):
do {
// now we have an other problem: the user can write in multiple way the path
size_t nbPermutation = std::pow(2, order.size());
// we use the bit like a flag to know the order of the draw
for (size_t permut=0; permut<nbPermutation; ++permut) {
std::vector<vec2> stroke;
for (size_t iii=0; iii<order.size(); ++iii) {
std::vector<vec2> pts = _strokes[order[iii]];
// check to permut the value order
if (((permut>>iii) & 0x01) == 1) {
reverse(pts.begin(),pts.end());
}
// Add point in next of the path...
for (auto &it : pts) {
stroke.push_back(it);
}
}
// Add new generated stroke
out.push_back(stroke);
}
} while (next_permutation(order.begin(), order.end()));
return out;
}
std::vector<vec2> dollar::combineStrokes(const std::vector<std::vector<vec2>>& _strokes) {
std::vector<vec2> out;
for (auto &it : _strokes) {
for (auto &pointIt : it) {
out.push_back(pointIt);
}
}
return out;
}
vec2 dollar::getStartVector(const std::vector<vec2>& _points, float _index) {
DOLLAR_ASSERT(_index > 0, "index must be != of 0");
if (_points.size() <= _index) {
return vec2(1.0, 0.0);
}
vec2 vect = _points[_index] - _points[0];
float len = vect.length();
return vect / len;
}
std::vector<vec2> dollar::normalyse(const std::vector<vec2>& _points) {
float sum = 0.0;
std::vector<vec2> out = _points;
for (auto &it : _points) {
sum += it.length2();
}
float magnitude = sqrt(sum);
if (magnitude == 0.0f) {
DOLLAR_ERROR("Magnetude is == 0");
return out;
}
for (auto &it : out) {
it /= magnitude;
}
return out;
}
std::vector<vec2> dollar::normalizePath(std::vector<vec2> _points, size_t _nbSample, bool _ignoreRotation, bool _keepAspectRatio) {
_points = dollar::resample(_points, _nbSample);
if (_ignoreRotation == true) {
_points = rotateToZero(_points);
}
_points = scaleToOne(_points, _keepAspectRatio);
return translateBariCenterToZero(_points);
}
std::vector<vec2> dollar::normalizePathToPoints(std::vector<std::vector<vec2>> _points, float _distance, bool _keepAspectRatio) {
// Scale point to (0.0,0.0) position and (1.0,1.0) size
_points = dollar::scaleToOne(_points, _keepAspectRatio);
std::vector<vec2> out;
for (auto &it : _points) {
if (it.size() == 0) {
continue;
}
if (it.size() == 1) {
out.push_back(it[0]);
continue;
}
std::vector<vec2> tmp = discretize(it, _distance);
for (auto &pointIt : tmp) {
out.push_back(pointIt);
}
if (tmp[tmp.size()-1] != it[it.size()-1]) {
out.push_back(it[it.size()-1]);
}
}
return out;
// NOTE: this is a bad idea ... return translateBariCenterToZero(out);
}