Split the shapes in different files and made them all QObjects with QProperties.

This commit is contained in:
Tats
2016-01-21 16:18:19 -05:00
parent ba1af4b724
commit c656f8246a
17 changed files with 1232 additions and 962 deletions
+152
View File
@@ -0,0 +1,152 @@
/*
* Ellipse.cpp
*
* (c) 2016 Sofian Audry -- info(@)sofianaudry(.)com
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include "Ellipse.h"
void Ellipse::sanitize()
{
// Get horizontal axis rotated 90 degrees CW
QVector2D hAxis = getHorizontalAxis();
const QVector2D center(getCenter());
QVector2D hAxisRotated(hAxis.y(), -hAxis.x());
// Project vertex 1 onto it.
QVector2D vAxisNormalized = hAxisRotated.normalized();
QVector2D vFromCenter = QVector2D(getVertex(1)) - center;
const QVector2D& projection = QVector2D::dotProduct( vFromCenter, vAxisNormalized ) * vAxisNormalized;
MShape::setVertex(1, (center + projection).toPointF());
MShape::setVertex(3, (center - projection).toPointF());
if (hasCenterControl())
{
// Clip control point.
MShape::setVertex(4, clipInside(getVertex(4)));
}
}
QPointF Ellipse::clipInside(const QPointF& v) const
{
// Map point as vector on a unit circle.
QVector2D vector(toUnitCircle().map(v));
// Clip control point.
return (vector.length() <= 1 ?
v :
fromUnitCircle().map(vector.normalized().toPointF()));
}
QTransform Ellipse::toUnitCircle() const
{
const QPointF& center = getCenter();
return QTransform().scale(1.0/getHorizontalRadius(), 1.0/getVerticalRadius())
.rotateRadians(-getRotationRadians())
.translate(-center.x(), -center.y());
}
QTransform Ellipse::fromUnitCircle() const
{
return toUnitCircle().inverted();
}
bool Ellipse::includesPoint(qreal x, qreal y)
{
return (QVector2D(toUnitCircle().map(QPointF(x, y))).length() <= 1);
}
void Ellipse::setVertex(int i, const QPointF& v)
{
// Save vertical axis vector.
const QVector2D& vAxis = getVerticalAxis();
// If changed one of the two rotation-controlling points, adjust the other two points.
if (i == 0 || i == 2)
{
// Transformation ellipse_t --> circle.
QTransform transform = toUnitCircle();
// Change the vertex.
_rawSetVertex(i, v);
// Combine with transformation circle -> ellipse_{t+1}.
transform *= fromUnitCircle();
// Set vertices.
MShape::setVertex(1, transform.map( getVertex(1) ));
MShape::setVertex(3, transform.map( getVertex(3) ));
if (hasCenterControl())
MShape::setVertex(4, transform.map( getVertex(4) ));
}
// If changed one of the two other points, just change the vertical axis.
else if (i == 1 || i == 3)
{
// Retrieve the new horizontal axis vector and center.
const QVector2D center(getCenter());
QVector2D vFromCenter = QVector2D(v) - center;
// Find projection of v onto vAxis / 2.
QVector2D vAxisNormalized = vAxis.normalized();
const QVector2D& projection = QVector2D::dotProduct( vFromCenter, vAxisNormalized ) * vAxisNormalized;
// Assign vertical control points.
QPointF v1;
QPointF v3;
if (i == 1)
{
v1 = (center + projection).toPointF();
v3 = (center - projection).toPointF();
}
else
{
v1 = (center - projection).toPointF();
v3 = (center + projection).toPointF();
}
// Transformation ellipse_t --> circle.
QTransform transform = toUnitCircle();
// Change vertical points.
_rawSetVertex(1, v1);
_rawSetVertex(3, v3);
// Combine with transformation circle -> ellipse_{t+1}.
transform *= fromUnitCircle();
// Set vertices.
if (hasCenterControl())
_rawSetVertex(4, transform.map( getVertex(4) ));
}
// Center control point (make sure it stays inside!).
else if (hasCenterControl())
{
// Clip control point.
_rawSetVertex(4, clipInside(v));
}
// Just to be sure.
sanitize();
}
+142
View File
@@ -0,0 +1,142 @@
/*
* Ellipse.h
*
* (c) 2016 Sofian Audry -- info(@)sofianaudry(.)com
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef ELLIPSE_H_
#define ELLIPSE_H_
#include "Shape.h"
class Ellipse : public MShape
{
Q_OBJECT
public:
Ellipse() {}
Ellipse(QPointF p1, QPointF p2, QPointF p3, QPointF p4, QPointF p5)
{
_addVertex(p1);
_addVertex(p2);
_addVertex(p3);
_addVertex(p4);
_addVertex(p5);
sanitize();
}
Ellipse(QPointF p1, QPointF p2, QPointF p3, QPointF p4, bool hasCenterControl=true)
{
_addVertex(p1);
_addVertex(p2);
_addVertex(p3);
_addVertex(p4);
if (hasCenterControl)
_addVertex(getCenter());
sanitize();
}
virtual ~Ellipse() {}
/// Remaps points so as to make sure this is a correct ellipse, keeping vertices 0 and 2 as
/// reference for the horizzontal axis.
void sanitize();
virtual QString getType() const { return "ellipse"; }
qreal getRotationRadians() const
{
QVector2D hAxis = getHorizontalAxis();
return atan2( hAxis.y(), hAxis.x() );
}
qreal getRotation() const
{
return radiansToDegrees( getRotationRadians() );
}
bool hasCenterControl() const
{
return (nVertices() == 5);
}
/// If v is outside boundaries, remap it to the border.
QPointF clipInside(const QPointF& v) const;
// QRect getBoundingRect() const {
// return QRect(0, getVerticalAxis().manhattanLength(),
// getHorizontalAxis().manhattanLength(), getVerticalAxis().manhattanLength());
// }
//
QPointF getCenter() const
{
return (QVector2D(getVertex(0)) - (getHorizontalAxis() / 2)).toPointF();
}
QVector2D getHorizontalAxis() const
{
return QVector2D(getVertex(0)) - QVector2D(getVertex(2));
}
QVector2D getVerticalAxis() const
{
return QVector2D(getVertex(1)) - QVector2D(getVertex(3));
}
qreal getHorizontalRadius() const
{
return getHorizontalAxis().length() / 2;
}
qreal getVerticalRadius() const
{
return getVerticalAxis().length() / 2;
}
/// Remaps point from ellipse to a circle with radius 1 set at origin (0,0).
QTransform toUnitCircle() const;
/// Remaps point from circle with radius 1 set at origin (0,0) to ellipse coordinates.
QTransform fromUnitCircle() const;
/** Return true if Shape includes point (x,y), false otherwise
* Algorithm should work for all polygons, including non-convex
* Found at http://www.cs.tufts.edu/comp/163/notes05/point_inclusion_handout.pdf
*/
virtual bool includesPoint(qreal x, qreal y);
virtual bool includesPoint(const QPointF& p)
{
return includesPoint(p.x(), p.y());
}
// Override the parent, checking to make sure the vertices are displaced correctly.
virtual void setVertex(int i, const QPointF& v);
protected:
/// Returns a new MShape (using default constructor).
virtual MShape* _create() const { return new Ellipse(); }
//protected:
// virtual void _vertexChanged(int i, Point* p=NULL) {
// // Get horizontal and vertical axis length.
// qreal hAxisLength = Point::dist(getVertex(0)->toPoint(), getVertex(2)->toPoint());
// qreal vAxisLength = Point::dist(getVertex(1)->toPoint(), getVertex(3)->toPoint());
// }
};
#endif /* ELLIPSE_H_ */
+460
View File
@@ -0,0 +1,460 @@
/*
* Mesh.cpp
*
* (c) 2016 Sofian Audry -- info(@)sofianaudry(.)com
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include "Mesh.h"
Mesh::Mesh() : Quad(), _nColumns(0), _nRows(0) {}
Mesh::Mesh(QPointF p1, QPointF p2, QPointF p3, QPointF p4) : Quad()
{
// Add points in standard order.
QVector<QPointF> points;
points.push_back(p1);
points.push_back(p2);
points.push_back(p4);
points.push_back(p3);
// Init.
init(points, 2, 2);
}
Mesh::Mesh(const QVector<QPointF>& points, int nColumns, int nRows) : Quad()
{
init(points, nColumns, nRows);
}
void Mesh::init(const QVector<QPointF>& points, int nColumns, int nRows)
{
Q_ASSERT(nColumns >= 2 && nRows >= 2);
Q_ASSERT(points.size() == nColumns * nRows);
_nColumns = nColumns;
_nRows = nRows;
// Resize the vertices2d vector to appropriate dimensions.
resizeVertices2d(_vertices2d, _nColumns, _nRows);
// Just build vertices2d in the standard order.
int k = 0;
for (int y=0; y<_nRows; y++)
for (int x=0; x<_nColumns; x++)
{
vertices.push_back( points[k] );
_vertices2d[x][y] = k;
k++;
}
}
QPolygonF Mesh::toPolygon() const
{
QPolygonF polygon;
for (int i=0; i<nColumns(); i++)
polygon.append(getVertex2d(i, 0));
for (int i=0; i<nRows(); i++)
polygon.append(getVertex2d(nColumns()-1, i));
for (int i=nColumns()-1; i>=0; i--)
polygon.append(getVertex2d(i, nRows()-1));
for (int i=nRows()-1; i>=1; i--)
polygon.append(getVertex2d(0, i));
return polygon;
}
void Mesh::setVertex(int i, const QPointF& v)
{
// Extract column and row of vertex.
int col = i % nColumns();
int row = i / nColumns();
// Make a copy.
QPointF realV = v;
// Constrain vertex to stay within the internal quads it is part of.
if (col < nColumns()-1)
{
if (row < nRows() - 1)
{
Quad quad(getVertex2d(col, row), getVertex2d(col+1, row), getVertex2d(col+1, row+1), getVertex2d(col, row+1));
_constrainVertex(quad.toPolygon(), 0, realV);
}
if (row > 0)
{
Quad quad(getVertex2d(col, row), getVertex2d(col+1, row), getVertex2d(col+1, row-1), getVertex2d(col, row-1));
_constrainVertex(quad.toPolygon(), 0, realV);
}
}
if (col > 0)
{
if (row < nRows() - 1)
{
Quad quad(getVertex2d(col, row), getVertex2d(col-1, row), getVertex2d(col-1, row+1), getVertex2d(col, row+1));
_constrainVertex(quad.toPolygon(), 0, realV);
}
if (row > 0)
{
Quad quad(getVertex2d(col, row), getVertex2d(col-1, row), getVertex2d(col-1, row-1), getVertex2d(col, row-1));
_constrainVertex(quad.toPolygon(), 0, realV);
}
}
// Do set vertex.
_rawSetVertex(i, realV);
}
void Mesh::resizeVertices2d(IndexVector2d& vertices2d, int nColumns, int nRows)
{
vertices2d.resize(nColumns);
for (int i=0; i<nColumns; i++)
vertices2d[i].resize(nRows);
}
//void Mesh::init(int nColumns, int nRows)
//{
// // Create vertices correspondence of bouding quad.
// resizeVertices2d(_vertices2d, 2, 2);
// _vertices2d[0][0] = 0;
// _vertices2d[1][0] = 1;
// _vertices2d[1][1] = 2;
// _vertices2d[0][1] = 3;
//
// // Init number of columns and rows.
// _nColumns = _nRows = 2;
//
// // Add extra columns and rows.
// for (int i=0; i<nColumns-2; i++)
// addColumn();
// for (int i=0; i<nRows-2; i++)
// addRow();
//}
// vertices 0..3 = 4 corners
//
void Mesh::addColumn()
{
// Create new vertices 2d (temporary).
IndexVector2d newVertices2d;
resizeVertices2d(newVertices2d, nColumns()+1, nRows());
// Left displacement of points already there.
qreal leftMoveProp = 1.0f/(nColumns()-1) - 1.0f/nColumns();
// Add a point at each row.
int k = nVertices();
for (int y=0; y<nRows(); y++)
{
// Get left and right vertices.
QPointF left = getVertex2d( 0, y );
QPointF right = getVertex2d( nColumns()-1, y );
QPointF diff = right - left;
// First pass: move middle points.
for (int x=1; x<nColumns()-1; x++)
{
QPointF p = getVertex2d(x, y);
p -= diff * x * leftMoveProp;
_rawSetVertex( _vertices2d[x][y], p );
}
// Create and add new point.
QPointF newPoint = right - diff * 1.0f/nColumns();
_addVertex(newPoint);
// Assign new vertices 2d.
for (int x=0; x<nColumns()-1; x++)
newVertices2d[x][y] = _vertices2d[x][y];
// The new point.
newVertices2d[nColumns()-1][y] = k;
// The rightmost point.
newVertices2d[nColumns()][y] = _vertices2d[nColumns()-1][y];
k++;
}
// Copy new mapping.
_vertices2d = newVertices2d;
// Increment number of columns.
_nColumns++;
// Reorder.
_reorderVertices();
}
void Mesh::addRow()
{
// Create new vertices 2d (temporary).
IndexVector2d newVertices2d;
resizeVertices2d(newVertices2d, nColumns(), nRows()+1);
// Top displacement of points already there.
qreal topMoveProp = 1.0f/(nRows()-1) - 1.0f/nRows();
// Add a point at each row.
int k = nVertices();
for (int x=0; x<nColumns(); x++)
{
// Get left and right vertices.
QPointF top = getVertex2d(x, 0);
QPointF bottom = getVertex2d(x, nRows()-1);
QPointF diff = bottom - top;
// First pass: move middle points.
for (int y=1; y<nRows()-1; y++)
{
QPointF p = getVertex2d(x, y);
p -= diff * y * topMoveProp;
_rawSetVertex( _vertices2d[x][y], p );
}
// Create and add new point.
QPointF newPoint = bottom - diff * 1.0f/nRows();
_addVertex(newPoint);
// Assign new vertices 2d.
for (int y=0; y<nRows()-1; y++)
newVertices2d[x][y] = _vertices2d[x][y];
// The new point.
newVertices2d[x][nRows()-1] = k;
// The rightmost point.
newVertices2d[x][nRows()] = _vertices2d[x][nRows()-1];
k++;
}
// Copy new mapping.
_vertices2d = newVertices2d;
// Increment number of columns.
_nRows++;
// Reorder.
_reorderVertices();
}
void Mesh::removeColumn(int columnId)
{
// Cannot remove first and last columns
Q_ASSERT(columnId >= 1 && columnId < nColumns()-1);
// Temporary containers that will be used to rebuild new vertex space.
IndexVector2d newVertices2d;
resizeVertices2d(newVertices2d, nColumns()-1, nRows());
QVector<QPointF> newVertices(vertices.size()-nRows());
// Right displacement of points already there.
qreal rightMoveProp = 1.0f/(nColumns()-2) - 1.0f/(nColumns()-1);
// Process all rows.
int k = 0;
for (int y=0; y<nRows(); y++)
{
// Get left and right vertices.
QPointF left = getVertex2d( 0, y );
QPointF right = getVertex2d( nColumns()-1, y );
QPointF diff = right - left;
// Move all columns.
for (int x=0; x<nColumns(); x++)
{
// Ignore points from target column.
if (x == columnId)
continue;
// Get current vertex.
QPointF p = getVertex2d( x, y );
// The x value of this point in the new space.
int newX = x < columnId ? x : x-1;
// Move middle points.
if (x > 0 && x < nColumns()-1)
{
p += (x < columnId ? +1 : -1) * diff * newX * rightMoveProp;
}
// Assign new containers.
newVertices[k] = p;
newVertices2d[newX][y] = k;
k++;
}
}
// Copy new mapping.
vertices = newVertices;
_vertices2d = newVertices2d;
// Decrement number of columns.
_nColumns--;
// Reorder.
_reorderVertices();
}
void Mesh::removeRow(int rowId)
{
// Cannot remove first and last columns
Q_ASSERT(rowId >= 1 && rowId < nRows()-1);
// Temporary containers that will be used to rebuild new vertex space.
IndexVector2d newVertices2d;
resizeVertices2d(newVertices2d, nColumns(), nRows()-1);
QVector<QPointF> newVertices(vertices.size()-nColumns());
// Bottom displacement of points already there.
qreal bottomMoveProp = 1.0f/(nRows()-2) - 1.0f/(nRows()-1);
// Process all columns.
int k = 0;
for (int x=0; x<nColumns(); x++)
{
// Get top and bottom vertices.
QPointF top = getVertex2d(x, 0);
QPointF bottom = getVertex2d(x, nRows()-1);
QPointF diff = bottom - top;
// Move all rows.
for (int y=0; y<nRows(); y++)
{
// Ignore points from target row.
if (y == rowId)
continue;
// Get current vertex.
QPointF p = getVertex2d( x, y );
// The y value of this point in the new space.
int newY = y < rowId ? y : y-1;
// Move middle points.
if (y > 0 && y < nRows()-1)
{
p += (y < rowId ? +1 : -1) * diff * newY * bottomMoveProp;
}
// Assign new containers.
newVertices[k] = p;
newVertices2d[x][newY] = k;
k++;
}
}
// Copy new mapping.
vertices = newVertices;
_vertices2d = newVertices2d;
// Decrement number of rows.
_nRows--;
// Reorder.
_reorderVertices();
}
void Mesh::resize(int nColumns_, int nRows_)
{
// Brutal: if asked to reduce columns or rows, just delete and redo.
if (nColumns_ < nColumns())
{
while (nColumns_ != nColumns())
removeColumn(nColumns()-2);
}
if (nRows_ < nRows())
{
while (nRows_ != nRows())
removeRow(nRows()-2);
}
if (nColumns_ > nColumns())
{
while (nColumns_ != nColumns())
addColumn();
}
if (nRows_ > nRows())
{
while (nRows_ != nRows())
addRow();
}
}
QVector<Quad::ptr> Mesh::getQuads() const
{
QVector<Quad::ptr> quads;
for (int i=0; i<nHorizontalQuads(); i++)
{
for (int j=0; j<nVerticalQuads(); j++)
{
quads.push_back(Quad::ptr(
new Quad(
getVertex2d(i, j ),
getVertex2d(i+1, j ),
getVertex2d(i+1, j+1),
getVertex2d(i, j+1)
)
));
}
}
return quads;
}
QVector< QVector<Quad::ptr> > Mesh::getQuads2d() const
{
QVector< QVector<Quad::ptr> > quads2d;
for (int i=0; i<nHorizontalQuads(); i++)
{
QVector<Quad::ptr> column;
for (int j=0; j<nVerticalQuads(); j++)
{
column.push_back(Quad::ptr(
new Quad(
getVertex2d(i, j ),
getVertex2d(i+1, j ),
getVertex2d(i+1, j+1),
getVertex2d(i, j+1)
)
));
}
quads2d.push_back(column);
}
return quads2d;
}
void Mesh::_reorderVertices()
{
// Populate new vertices vector.
QVector<QPointF> newVertices(vertices.size());
int k = 0;
for (int y=0; y<nRows(); y++)
for (int x=0; x<nColumns(); x++)
newVertices[k++] = getVertex2d( x, y );
// Populate _vertices2d.
k = 0;
for (int y=0; y<nRows(); y++)
for (int x=0; x<nColumns(); x++)
_vertices2d[x][y] = k++;
// Copy.
vertices = newVertices;
}
+117
View File
@@ -0,0 +1,117 @@
/*
* Mesh.h
*
* (c) 2016 Sofian Audry -- info(@)sofianaudry(.)com
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef MESH_H_
#define MESH_H_
#include "Quad.h"
class Mesh : public Quad
{
Q_OBJECT
Q_PROPERTY(int nColumns READ nColumns)
Q_PROPERTY(int nRows READ nRows)
typedef QVector<QVector<int> > IndexVector2d;
public:
Mesh();
// This constructor creates a quad mesh (four corners) using the same order as for the quad
// constructor (ie. clockwise).
Mesh(QPointF p1, QPointF p2, QPointF p3, QPointF p4);
// Standard mesh constructor.
Mesh(const QVector<QPointF>& points, int nColumns, int nRows);
virtual ~Mesh() {}
// Performs the actual adding of points (used for loading).
void init(const QVector<QPointF>& points, int nColumns, int nRows);
virtual QString getType() const { return "mesh"; }
/// Returns a polygon that is formed by all the contour points of the mesh.
virtual QPolygonF toPolygon() const;
// Override the parent, checking to make sure the vertices are displaced correctly.
virtual void setVertex(int i, const QPointF& v);
QPointF getVertex2d(int i, int j) const
{
return vertices[_vertices2d[i][j]];
}
void setVertex2d(int i, int j, const QPointF& v)
{
vertices[_vertices2d[i][j]] = v; // copy
}
void setVertex2d(int i, int j, double x, double y)
{
vertices[_vertices2d[i][j]] = QPointF(x, y);
}
void resizeVertices2d(IndexVector2d& vertices2d, int nColumns, int nRows);
//
void addColumn();
void addRow();
void removeColumn(int columnId);
void removeRow(int rowId);
void resize(int nColumns_, int nRows_);
QVector<Quad::ptr> getQuads() const;
QVector<QVector<Quad::ptr> > getQuads2d() const;
int nColumns() const { return _nColumns; }
int nRows() const { return _nRows; }
int nHorizontalQuads() const { return _nColumns-1; }
int nVerticalQuads() const { return _nRows-1; }
protected:
int _nColumns;
int _nRows;
// _vertices[i][j] contains vertex id of vertex at position (i,j) where i = 0..nColumns and j = 0..nRows
IndexVector2d _vertices2d;
/**
* Reorder vertices in a standard order:
*
* 0----1----2----3
* | | | |
* 4----5----6----7
* | | | |
* 8----9---10----11
*/
void _reorderVertices();
/// Returns a new MShape (using default constructor).
virtual MShape* _create() const { return new Mesh(); }
};
#endif /* MESH_H_ */
+111
View File
@@ -0,0 +1,111 @@
/*
* Polygon.cpp
*
* (c) 2016 Sofian Audry -- info(@)sofianaudry(.)com
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include "Polygon.h"
void Polygon::setVertex(int i, const QPointF& v)
{
// Constrain vertex.
QPointF realV = v;
_constrainVertex(toPolygon(), i, realV);
// Really set the vertex.
_rawSetVertex(i, realV);
}
void Polygon::_constrainVertex(const QPolygonF& polygon, int i, QPointF& v)
{
// Weird, but nothing to do.
if (polygon.size() <= 3)
return;
// Save previous position of vertex.
QPointF prevV = polygon.at(i);
// Look at the two adjunct segments to vertex i and see if they
// intersect with any non-adjacent segments.
// Construct the list of segments (with the new candidate vertex).
QVector<QLineF> segments = _getSegments(polygon);
int prev = wrapAround(i - 1, segments.size());
int next = wrapAround(i + 1, segments.size());
segments[prev] = QLineF(polygon.at(prev), v);
segments[i] = QLineF(v, polygon.at(next));
// We now stretch segments a little bit to cope with approximation errors.
for (QVector<QLineF>::Iterator it = segments.begin(); it != segments.end(); ++it)
{
QLineF& seg = *it;
QPointF p1 = seg.p1();
QPointF p2 = seg.p2();
seg.setP1( p1 + (p1 - p2) * 0.35f);
seg.setP2( p2 + (p2 - p1) * 0.35f);
}
// For each adjunct segment.
for (int adj=0; adj<2; adj++)
{
int idx = wrapAround(i + adj - 1, segments.size());
for (int j=0; j<segments.size(); j++)
{
// If the segment to compare to is valid (ie. if it is not
// the segment itself nor an adjacent one) then check for
// intersection.
if (j != idx &&
j != wrapAround(idx-1, segments.size()) &&
j != wrapAround(idx+1, segments.size()))
{
QPointF intersection;
if (segments[idx].intersect(segments[j], &intersection) == QLineF::BoundedIntersection)
{
// Rearrange segments with new position at intersection point.
v = intersection;
segments[prev] = QLineF(polygon.at(prev), v);
segments[i] = QLineF(v, polygon.at(next));
}
}
}
}
}
QVector<QLineF> Polygon::_getSegments() const
{
return _getSegments(toPolygon());
}
QVector<QLineF> Polygon::_getSegments(const QPolygonF& polygon)
{
QVector<QLineF> segments;
for (int i=0; i<polygon.size(); i++)
segments.push_back(QLineF(polygon.at(i), polygon.at( (i+1) % polygon.size() )));
return segments;
}
QPolygonF Polygon::toPolygon() const
{
QPolygonF polygon;
for (QVector<QPointF>::const_iterator it = vertices.begin() ;
it != vertices.end(); ++it)
{
polygon.append(*it);
}
return polygon;
}
+56
View File
@@ -0,0 +1,56 @@
/*
* Polygon.h
*
* (c) 2016 Sofian Audry -- info(@)sofianaudry(.)com
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef POLYGON_H_
#define POLYGON_H_
#include "Shape.h"
/**
* This class represents a simple polygon (ie. the control points are vertices).
*/
class Polygon : public MShape
{
Q_OBJECT
public:
Polygon() {}
Polygon(QVector<QPointF> vertices_) : MShape(vertices_) {}
virtual ~Polygon() {}
virtual QPolygonF toPolygon() const;
virtual bool includesPoint(const QPointF& p) {
return toPolygon().containsPoint(p, Qt::OddEvenFill);
}
// Override the parent, checking to make sure the vertices are displaced correctly.
virtual void setVertex(int i, const QPointF& v);
protected:
/// Returns all line segments of the polygon.
QVector<QLineF> _getSegments() const;
/// Returns all line segments of a polygon.
static QVector<QLineF> _getSegments(const QPolygonF& polygon);
/// Makes sure vertex v as the i-th point of polygon stays inside the polygon.
static void _constrainVertex(const QPolygonF& polygon, int i, QPointF& v);
};
#endif /* POLYGON_H_ */
+53
View File
@@ -0,0 +1,53 @@
/*
* Quad.h
*
* (c) 2016 Sofian Audry -- info(@)sofianaudry(.)com
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef QUAD_H_
#define QUAD_H_
#include "Polygon.h"
/**
* Four-vertex shape.
*/
class Quad : public Polygon
{
Q_OBJECT
public:
typedef QSharedPointer<Quad> ptr;
Quad() {}
Quad(QPointF p1, QPointF p2, QPointF p3, QPointF p4)
{
_addVertex(p1);
_addVertex(p2);
_addVertex(p3);
_addVertex(p4);
}
virtual ~Quad() {}
virtual QString getType() const { return "quad"; }
protected:
/// Returns a new MShape (using default constructor).
virtual MShape* _create() const { return new Quad(); }
};
#endif /* QUAD_H_ */
-652
View File
@@ -38,655 +38,3 @@ void MShape::translate(const QPointF& offset)
*it += offset;
}
void Polygon::setVertex(int i, const QPointF& v)
{
// Constrain vertex.
QPointF realV = v;
_constrainVertex(toPolygon(), i, realV);
// Really set the vertex.
_rawSetVertex(i, realV);
}
void Polygon::_constrainVertex(const QPolygonF& polygon, int i, QPointF& v)
{
// Weird, but nothing to do.
if (polygon.size() <= 3)
return;
// Save previous position of vertex.
QPointF prevV = polygon.at(i);
// Look at the two adjunct segments to vertex i and see if they
// intersect with any non-adjacent segments.
// Construct the list of segments (with the new candidate vertex).
QVector<QLineF> segments = _getSegments(polygon);
int prev = wrapAround(i - 1, segments.size());
int next = wrapAround(i + 1, segments.size());
segments[prev] = QLineF(polygon.at(prev), v);
segments[i] = QLineF(v, polygon.at(next));
// We now stretch segments a little bit to cope with approximation errors.
for (QVector<QLineF>::Iterator it = segments.begin(); it != segments.end(); ++it)
{
QLineF& seg = *it;
QPointF p1 = seg.p1();
QPointF p2 = seg.p2();
seg.setP1( p1 + (p1 - p2) * 0.35f);
seg.setP2( p2 + (p2 - p1) * 0.35f);
}
// For each adjunct segment.
for (int adj=0; adj<2; adj++)
{
int idx = wrapAround(i + adj - 1, segments.size());
for (int j=0; j<segments.size(); j++)
{
// If the segment to compare to is valid (ie. if it is not
// the segment itself nor an adjacent one) then check for
// intersection.
if (j != idx &&
j != wrapAround(idx-1, segments.size()) &&
j != wrapAround(idx+1, segments.size()))
{
QPointF intersection;
if (segments[idx].intersect(segments[j], &intersection) == QLineF::BoundedIntersection)
{
// Rearrange segments with new position at intersection point.
v = intersection;
segments[prev] = QLineF(polygon.at(prev), v);
segments[i] = QLineF(v, polygon.at(next));
}
}
}
}
}
QVector<QLineF> Polygon::_getSegments() const
{
return _getSegments(toPolygon());
}
QVector<QLineF> Polygon::_getSegments(const QPolygonF& polygon)
{
QVector<QLineF> segments;
for (int i=0; i<polygon.size(); i++)
segments.push_back(QLineF(polygon.at(i), polygon.at( (i+1) % polygon.size() )));
return segments;
}
QPolygonF Polygon::toPolygon() const
{
QPolygonF polygon;
for (QVector<QPointF>::const_iterator it = vertices.begin() ;
it != vertices.end(); ++it)
{
polygon.append(*it);
}
return polygon;
}
Mesh::Mesh() : Quad(), _nColumns(0), _nRows(0) {}
Mesh::Mesh(QPointF p1, QPointF p2, QPointF p3, QPointF p4) : Quad()
{
// Add points in standard order.
QVector<QPointF> points;
points.push_back(p1);
points.push_back(p2);
points.push_back(p4);
points.push_back(p3);
// Init.
init(points, 2, 2);
}
Mesh::Mesh(const QVector<QPointF>& points, int nColumns, int nRows) : Quad()
{
init(points, nColumns, nRows);
}
void Mesh::init(const QVector<QPointF>& points, int nColumns, int nRows)
{
Q_ASSERT(nColumns >= 2 && nRows >= 2);
Q_ASSERT(points.size() == nColumns * nRows);
_nColumns = nColumns;
_nRows = nRows;
// Resize the vertices2d vector to appropriate dimensions.
resizeVertices2d(_vertices2d, _nColumns, _nRows);
// Just build vertices2d in the standard order.
int k = 0;
for (int y=0; y<_nRows; y++)
for (int x=0; x<_nColumns; x++)
{
vertices.push_back( points[k] );
_vertices2d[x][y] = k;
k++;
}
}
QPolygonF Mesh::toPolygon() const
{
QPolygonF polygon;
for (int i=0; i<nColumns(); i++)
polygon.append(getVertex2d(i, 0));
for (int i=0; i<nRows(); i++)
polygon.append(getVertex2d(nColumns()-1, i));
for (int i=nColumns()-1; i>=0; i--)
polygon.append(getVertex2d(i, nRows()-1));
for (int i=nRows()-1; i>=1; i--)
polygon.append(getVertex2d(0, i));
return polygon;
}
void Mesh::setVertex(int i, const QPointF& v)
{
// Extract column and row of vertex.
int col = i % nColumns();
int row = i / nColumns();
// Make a copy.
QPointF realV = v;
// Constrain vertex to stay within the internal quads it is part of.
if (col < nColumns()-1)
{
if (row < nRows() - 1)
{
Quad quad(getVertex2d(col, row), getVertex2d(col+1, row), getVertex2d(col+1, row+1), getVertex2d(col, row+1));
_constrainVertex(quad.toPolygon(), 0, realV);
}
if (row > 0)
{
Quad quad(getVertex2d(col, row), getVertex2d(col+1, row), getVertex2d(col+1, row-1), getVertex2d(col, row-1));
_constrainVertex(quad.toPolygon(), 0, realV);
}
}
if (col > 0)
{
if (row < nRows() - 1)
{
Quad quad(getVertex2d(col, row), getVertex2d(col-1, row), getVertex2d(col-1, row+1), getVertex2d(col, row+1));
_constrainVertex(quad.toPolygon(), 0, realV);
}
if (row > 0)
{
Quad quad(getVertex2d(col, row), getVertex2d(col-1, row), getVertex2d(col-1, row-1), getVertex2d(col, row-1));
_constrainVertex(quad.toPolygon(), 0, realV);
}
}
// Do set vertex.
_rawSetVertex(i, realV);
}
void Mesh::resizeVertices2d(IndexVector2d& vertices2d, int nColumns, int nRows)
{
vertices2d.resize(nColumns);
for (int i=0; i<nColumns; i++)
vertices2d[i].resize(nRows);
}
//void Mesh::init(int nColumns, int nRows)
//{
// // Create vertices correspondence of bouding quad.
// resizeVertices2d(_vertices2d, 2, 2);
// _vertices2d[0][0] = 0;
// _vertices2d[1][0] = 1;
// _vertices2d[1][1] = 2;
// _vertices2d[0][1] = 3;
//
// // Init number of columns and rows.
// _nColumns = _nRows = 2;
//
// // Add extra columns and rows.
// for (int i=0; i<nColumns-2; i++)
// addColumn();
// for (int i=0; i<nRows-2; i++)
// addRow();
//}
// vertices 0..3 = 4 corners
//
void Mesh::addColumn()
{
// Create new vertices 2d (temporary).
IndexVector2d newVertices2d;
resizeVertices2d(newVertices2d, nColumns()+1, nRows());
// Left displacement of points already there.
qreal leftMoveProp = 1.0f/(nColumns()-1) - 1.0f/nColumns();
// Add a point at each row.
int k = nVertices();
for (int y=0; y<nRows(); y++)
{
// Get left and right vertices.
QPointF left = getVertex2d( 0, y );
QPointF right = getVertex2d( nColumns()-1, y );
QPointF diff = right - left;
// First pass: move middle points.
for (int x=1; x<nColumns()-1; x++)
{
QPointF p = getVertex2d(x, y);
p -= diff * x * leftMoveProp;
_rawSetVertex( _vertices2d[x][y], p );
}
// Create and add new point.
QPointF newPoint = right - diff * 1.0f/nColumns();
_addVertex(newPoint);
// Assign new vertices 2d.
for (int x=0; x<nColumns()-1; x++)
newVertices2d[x][y] = _vertices2d[x][y];
// The new point.
newVertices2d[nColumns()-1][y] = k;
// The rightmost point.
newVertices2d[nColumns()][y] = _vertices2d[nColumns()-1][y];
k++;
}
// Copy new mapping.
_vertices2d = newVertices2d;
// Increment number of columns.
_nColumns++;
// Reorder.
_reorderVertices();
}
void Mesh::addRow()
{
// Create new vertices 2d (temporary).
IndexVector2d newVertices2d;
resizeVertices2d(newVertices2d, nColumns(), nRows()+1);
// Top displacement of points already there.
qreal topMoveProp = 1.0f/(nRows()-1) - 1.0f/nRows();
// Add a point at each row.
int k = nVertices();
for (int x=0; x<nColumns(); x++)
{
// Get left and right vertices.
QPointF top = getVertex2d(x, 0);
QPointF bottom = getVertex2d(x, nRows()-1);
QPointF diff = bottom - top;
// First pass: move middle points.
for (int y=1; y<nRows()-1; y++)
{
QPointF p = getVertex2d(x, y);
p -= diff * y * topMoveProp;
_rawSetVertex( _vertices2d[x][y], p );
}
// Create and add new point.
QPointF newPoint = bottom - diff * 1.0f/nRows();
_addVertex(newPoint);
// Assign new vertices 2d.
for (int y=0; y<nRows()-1; y++)
newVertices2d[x][y] = _vertices2d[x][y];
// The new point.
newVertices2d[x][nRows()-1] = k;
// The rightmost point.
newVertices2d[x][nRows()] = _vertices2d[x][nRows()-1];
k++;
}
// Copy new mapping.
_vertices2d = newVertices2d;
// Increment number of columns.
_nRows++;
// Reorder.
_reorderVertices();
}
void Mesh::removeColumn(int columnId)
{
// Cannot remove first and last columns
Q_ASSERT(columnId >= 1 && columnId < nColumns()-1);
// Temporary containers that will be used to rebuild new vertex space.
IndexVector2d newVertices2d;
resizeVertices2d(newVertices2d, nColumns()-1, nRows());
QVector<QPointF> newVertices(vertices.size()-nRows());
// Right displacement of points already there.
qreal rightMoveProp = 1.0f/(nColumns()-2) - 1.0f/(nColumns()-1);
// Process all rows.
int k = 0;
for (int y=0; y<nRows(); y++)
{
// Get left and right vertices.
QPointF left = getVertex2d( 0, y );
QPointF right = getVertex2d( nColumns()-1, y );
QPointF diff = right - left;
// Move all columns.
for (int x=0; x<nColumns(); x++)
{
// Ignore points from target column.
if (x == columnId)
continue;
// Get current vertex.
QPointF p = getVertex2d( x, y );
// The x value of this point in the new space.
int newX = x < columnId ? x : x-1;
// Move middle points.
if (x > 0 && x < nColumns()-1)
{
p += (x < columnId ? +1 : -1) * diff * newX * rightMoveProp;
}
// Assign new containers.
newVertices[k] = p;
newVertices2d[newX][y] = k;
k++;
}
}
// Copy new mapping.
vertices = newVertices;
_vertices2d = newVertices2d;
// Decrement number of columns.
_nColumns--;
// Reorder.
_reorderVertices();
}
void Mesh::removeRow(int rowId)
{
// Cannot remove first and last columns
Q_ASSERT(rowId >= 1 && rowId < nRows()-1);
// Temporary containers that will be used to rebuild new vertex space.
IndexVector2d newVertices2d;
resizeVertices2d(newVertices2d, nColumns(), nRows()-1);
QVector<QPointF> newVertices(vertices.size()-nColumns());
// Bottom displacement of points already there.
qreal bottomMoveProp = 1.0f/(nRows()-2) - 1.0f/(nRows()-1);
// Process all columns.
int k = 0;
for (int x=0; x<nColumns(); x++)
{
// Get top and bottom vertices.
QPointF top = getVertex2d(x, 0);
QPointF bottom = getVertex2d(x, nRows()-1);
QPointF diff = bottom - top;
// Move all rows.
for (int y=0; y<nRows(); y++)
{
// Ignore points from target row.
if (y == rowId)
continue;
// Get current vertex.
QPointF p = getVertex2d( x, y );
// The y value of this point in the new space.
int newY = y < rowId ? y : y-1;
// Move middle points.
if (y > 0 && y < nRows()-1)
{
p += (y < rowId ? +1 : -1) * diff * newY * bottomMoveProp;
}
// Assign new containers.
newVertices[k] = p;
newVertices2d[x][newY] = k;
k++;
}
}
// Copy new mapping.
vertices = newVertices;
_vertices2d = newVertices2d;
// Decrement number of rows.
_nRows--;
// Reorder.
_reorderVertices();
}
void Mesh::resize(int nColumns_, int nRows_)
{
// Brutal: if asked to reduce columns or rows, just delete and redo.
if (nColumns_ < nColumns())
{
while (nColumns_ != nColumns())
removeColumn(nColumns()-2);
}
if (nRows_ < nRows())
{
while (nRows_ != nRows())
removeRow(nRows()-2);
}
if (nColumns_ > nColumns())
{
while (nColumns_ != nColumns())
addColumn();
}
if (nRows_ > nRows())
{
while (nRows_ != nRows())
addRow();
}
}
QVector<Quad> Mesh::getQuads() const
{
QVector<Quad> quads;
for (int i=0; i<nHorizontalQuads(); i++)
{
for (int j=0; j<nVerticalQuads(); j++)
{
Quad quad(
getVertex2d(i, j ),
getVertex2d(i+1, j ),
getVertex2d(i+1, j+1),
getVertex2d(i, j+1)
);
quads.push_back(quad);
}
}
return quads;
}
QVector< QVector<Quad> > Mesh::getQuads2d() const
{
QVector< QVector<Quad> > quads2d;
for (int i=0; i<nHorizontalQuads(); i++)
{
QVector<Quad> column;
for (int j=0; j<nVerticalQuads(); j++)
{
Quad quad(
getVertex2d(i, j ),
getVertex2d(i+1, j ),
getVertex2d(i+1, j+1),
getVertex2d(i, j+1)
);
column.push_back(quad);
}
quads2d.push_back(column);
}
return quads2d;
}
void Mesh::_reorderVertices()
{
// Populate new vertices vector.
QVector<QPointF> newVertices(vertices.size());
int k = 0;
for (int y=0; y<nRows(); y++)
for (int x=0; x<nColumns(); x++)
newVertices[k++] = getVertex2d( x, y );
// Populate _vertices2d.
k = 0;
for (int y=0; y<nRows(); y++)
for (int x=0; x<nColumns(); x++)
_vertices2d[x][y] = k++;
// Copy.
vertices = newVertices;
}
void Ellipse::sanitize()
{
// Get horizontal axis rotated 90 degrees CW
QVector2D hAxis = getHorizontalAxis();
const QVector2D center(getCenter());
QVector2D hAxisRotated(hAxis.y(), -hAxis.x());
// Project vertex 1 onto it.
QVector2D vAxisNormalized = hAxisRotated.normalized();
QVector2D vFromCenter = QVector2D(getVertex(1)) - center;
const QVector2D& projection = QVector2D::dotProduct( vFromCenter, vAxisNormalized ) * vAxisNormalized;
MShape::setVertex(1, (center + projection).toPointF());
MShape::setVertex(3, (center - projection).toPointF());
if (hasCenterControl())
{
// Clip control point.
MShape::setVertex(4, clipInside(getVertex(4)));
}
}
QPointF Ellipse::clipInside(const QPointF& v) const
{
// Map point as vector on a unit circle.
QVector2D vector(toUnitCircle().map(v));
// Clip control point.
return (vector.length() <= 1 ?
v :
fromUnitCircle().map(vector.normalized().toPointF()));
}
QTransform Ellipse::toUnitCircle() const
{
const QPointF& center = getCenter();
return QTransform().scale(1.0/getHorizontalRadius(), 1.0/getVerticalRadius())
.rotateRadians(-getRotationRadians())
.translate(-center.x(), -center.y());
}
QTransform Ellipse::fromUnitCircle() const
{
return toUnitCircle().inverted();
}
bool Ellipse::includesPoint(qreal x, qreal y)
{
return (QVector2D(toUnitCircle().map(QPointF(x, y))).length() <= 1);
}
void Ellipse::setVertex(int i, const QPointF& v)
{
// Save vertical axis vector.
const QVector2D& vAxis = getVerticalAxis();
// If changed one of the two rotation-controlling points, adjust the other two points.
if (i == 0 || i == 2)
{
// Transformation ellipse_t --> circle.
QTransform transform = toUnitCircle();
// Change the vertex.
_rawSetVertex(i, v);
// Combine with transformation circle -> ellipse_{t+1}.
transform *= fromUnitCircle();
// Set vertices.
MShape::setVertex(1, transform.map( getVertex(1) ));
MShape::setVertex(3, transform.map( getVertex(3) ));
if (hasCenterControl())
MShape::setVertex(4, transform.map( getVertex(4) ));
}
// If changed one of the two other points, just change the vertical axis.
else if (i == 1 || i == 3)
{
// Retrieve the new horizontal axis vector and center.
const QVector2D center(getCenter());
QVector2D vFromCenter = QVector2D(v) - center;
// Find projection of v onto vAxis / 2.
QVector2D vAxisNormalized = vAxis.normalized();
const QVector2D& projection = QVector2D::dotProduct( vFromCenter, vAxisNormalized ) * vAxisNormalized;
// Assign vertical control points.
QPointF v1;
QPointF v3;
if (i == 1)
{
v1 = (center + projection).toPointF();
v3 = (center - projection).toPointF();
}
else
{
v1 = (center - projection).toPointF();
v3 = (center + projection).toPointF();
}
// Transformation ellipse_t --> circle.
QTransform transform = toUnitCircle();
// Change vertical points.
_rawSetVertex(1, v1);
_rawSetVertex(3, v3);
// Combine with transformation circle -> ellipse_{t+1}.
transform *= fromUnitCircle();
// Set vertices.
if (hasCenterControl())
_rawSetVertex(4, transform.map( getVertex(4) ));
}
// Center control point (make sure it stays inside!).
else if (hasCenterControl())
{
// Clip control point.
_rawSetVertex(4, clipInside(v));
}
// Just to be sure.
sanitize();
}
+14 -272
View File
@@ -17,8 +17,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SHAPE_H_
#define SHAPE_H_
#ifndef M_SHAPE_H_
#define M_SHAPE_H_
#include <iostream>
#include <cmath>
@@ -36,13 +36,18 @@
#include <QString>
#include <QMetaType>
#include <QSharedPointer>
#include "Maths.h"
/**
* Shape represented by a series of control points.
*/
class MShape
class MShape : public QObject
{
Q_OBJECT
Q_PROPERTY(QVector<QPointF> vertices READ getVertices WRITE setVertices)
public:
typedef QSharedPointer<MShape> ptr;
@@ -90,6 +95,12 @@ public:
virtual MShape* clone() const;
QVector<QPointF> getVertices() const { return vertices; }
virtual void setVertices(QVector<QPointF> vertices_)
{
vertices = vertices_;
}
protected:
QVector<QPointF> vertices;
@@ -107,274 +118,5 @@ protected:
virtual MShape* _create() const = 0;
};
/**
* This class represents a simple polygon (ie. the control points are vertices).
*/
class Polygon : public MShape {
public:
Polygon() {}
Polygon(QVector<QPointF> vertices_) : MShape(vertices_) {}
virtual ~Polygon() {}
virtual QPolygonF toPolygon() const;
virtual bool includesPoint(const QPointF& p) {
return toPolygon().containsPoint(p, Qt::OddEvenFill);
}
// Override the parent, checking to make sure the vertices are displaced correctly.
virtual void setVertex(int i, const QPointF& v);
protected:
/// Returns all line segments of the polygon.
QVector<QLineF> _getSegments() const;
/// Returns all line segments of a polygon.
static QVector<QLineF> _getSegments(const QPolygonF& polygon);
/// Makes sure vertex v as the i-th point of polygon stays inside the polygon.
static void _constrainVertex(const QPolygonF& polygon, int i, QPointF& v);
};
/**
* Four-vertex shape.
*/
class Quad : public Polygon
{
public:
Quad() {}
Quad(QPointF p1, QPointF p2, QPointF p3, QPointF p4)
{
_addVertex(p1);
_addVertex(p2);
_addVertex(p3);
_addVertex(p4);
}
virtual ~Quad() {}
virtual QString getType() const { return "quad"; }
protected:
/// Returns a new MShape (using default constructor).
virtual MShape* _create() const { return new Quad(); }
};
/**
* Triangle shape.
*/
class Triangle : public Polygon
{
public:
Triangle() {}
Triangle(QPointF p1, QPointF p2, QPointF p3)
{
_addVertex(p1);
_addVertex(p2);
_addVertex(p3);
}
virtual ~Triangle() {}
virtual QString getType() const { return "triangle"; }
protected:
/// Returns a new MShape (using default constructor).
virtual MShape* _create() const { return new Triangle(); }
};
class Mesh : public Quad
{
typedef QVector<QVector<int> > IndexVector2d;
public:
Mesh();
// This constructor creates a quad mesh (four corners) using the same order as for the quad
// constructor (ie. clockwise).
Mesh(QPointF p1, QPointF p2, QPointF p3, QPointF p4);
// Standard mesh constructor.
Mesh(const QVector<QPointF>& points, int nColumns, int nRows);
virtual ~Mesh() {}
// Performs the actual adding of points (used for loading).
void init(const QVector<QPointF>& points, int nColumns, int nRows);
virtual QString getType() const { return "mesh"; }
/// Returns a polygon that is formed by all the contour points of the mesh.
virtual QPolygonF toPolygon() const;
// Override the parent, checking to make sure the vertices are displaced correctly.
virtual void setVertex(int i, const QPointF& v);
QPointF getVertex2d(int i, int j) const
{
return vertices[_vertices2d[i][j]];
}
void setVertex2d(int i, int j, const QPointF& v)
{
vertices[_vertices2d[i][j]] = v; // copy
}
void setVertex2d(int i, int j, double x, double y)
{
vertices[_vertices2d[i][j]] = QPointF(x, y);
}
void resizeVertices2d(IndexVector2d& vertices2d, int nColumns, int nRows);
//
void addColumn();
void addRow();
void removeColumn(int columnId);
void removeRow(int rowId);
void resize(int nColumns_, int nRows_);
QVector<Quad> getQuads() const;
QVector<QVector<Quad> > getQuads2d() const;
int nColumns() const { return _nColumns; }
int nRows() const { return _nRows; }
int nHorizontalQuads() const { return _nColumns-1; }
int nVerticalQuads() const { return _nRows-1; }
protected:
int _nColumns;
int _nRows;
// _vertices[i][j] contains vertex id of vertex at position (i,j) where i = 0..nColumns and j = 0..nRows
IndexVector2d _vertices2d;
/**
* Reorder vertices in a standard order:
*
* 0----1----2----3
* | | | |
* 4----5----6----7
* | | | |
* 8----9---10----11
*/
void _reorderVertices();
/// Returns a new MShape (using default constructor).
virtual MShape* _create() const { return new Mesh(); }
};
class Ellipse : public MShape {
public:
Ellipse() {}
Ellipse(QPointF p1, QPointF p2, QPointF p3, QPointF p4, QPointF p5)
{
_addVertex(p1);
_addVertex(p2);
_addVertex(p3);
_addVertex(p4);
_addVertex(p5);
sanitize();
}
Ellipse(QPointF p1, QPointF p2, QPointF p3, QPointF p4, bool hasCenterControl=true)
{
_addVertex(p1);
_addVertex(p2);
_addVertex(p3);
_addVertex(p4);
if (hasCenterControl)
_addVertex(getCenter());
sanitize();
}
virtual ~Ellipse() {}
/// Remaps points so as to make sure this is a correct ellipse, keeping vertices 0 and 2 as
/// reference for the horizzontal axis.
void sanitize();
virtual QString getType() const { return "ellipse"; }
qreal getRotationRadians() const
{
QVector2D hAxis = getHorizontalAxis();
return atan2( hAxis.y(), hAxis.x() );
}
qreal getRotation() const
{
return radiansToDegrees( getRotationRadians() );
}
bool hasCenterControl() const
{
return (nVertices() == 5);
}
/// If v is outside boundaries, remap it to the border.
QPointF clipInside(const QPointF& v) const;
// QRect getBoundingRect() const {
// return QRect(0, getVerticalAxis().manhattanLength(),
// getHorizontalAxis().manhattanLength(), getVerticalAxis().manhattanLength());
// }
//
QPointF getCenter() const
{
return (QVector2D(getVertex(0)) - (getHorizontalAxis() / 2)).toPointF();
}
QVector2D getHorizontalAxis() const
{
return QVector2D(getVertex(0)) - QVector2D(getVertex(2));
}
QVector2D getVerticalAxis() const
{
return QVector2D(getVertex(1)) - QVector2D(getVertex(3));
}
qreal getHorizontalRadius() const
{
return getHorizontalAxis().length() / 2;
}
qreal getVerticalRadius() const
{
return getVerticalAxis().length() / 2;
}
/// Remaps point from ellipse to a circle with radius 1 set at origin (0,0).
QTransform toUnitCircle() const;
/// Remaps point from circle with radius 1 set at origin (0,0) to ellipse coordinates.
QTransform fromUnitCircle() const;
/** Return true if Shape includes point (x,y), false otherwise
* Algorithm should work for all polygons, including non-convex
* Found at http://www.cs.tufts.edu/comp/163/notes05/point_inclusion_handout.pdf
*/
virtual bool includesPoint(qreal x, qreal y);
virtual bool includesPoint(const QPointF& p)
{
return includesPoint(p.x(), p.y());
}
// Override the parent, checking to make sure the vertices are displaced correctly.
virtual void setVertex(int i, const QPointF& v);
protected:
/// Returns a new MShape (using default constructor).
virtual MShape* _create() const { return new Ellipse(); }
//protected:
// virtual void _vertexChanged(int i, Point* p=NULL) {
// // Get horizontal and vertical axis length.
// qreal hAxisLength = Point::dist(getVertex(0)->toPoint(), getVertex(2)->toPoint());
// qreal vAxisLength = Point::dist(getVertex(1)->toPoint(), getVertex(3)->toPoint());
// }
};
#endif /* SHAPE_H_ */
+3 -3
View File
@@ -88,10 +88,10 @@ void MeshControlPainter::_paintShape(QPainter *painter, MapperGLCanvas* canvas)
painter->setPen(getRescaledShapeStroke(canvas, true));
// Draw inner quads.
QVector<Quad> quads = mesh->getQuads();
for (QVector<Quad>::const_iterator it = quads.begin(); it != quads.end(); ++it)
QVector<Quad::ptr> quads = mesh->getQuads();
for (QVector<Quad::ptr>::const_iterator it = quads.begin(); it != quads.end(); ++it)
{
painter->drawPolygon(it->toPolygon());
painter->drawPolygon((*it)->toPolygon());
}
// Draw outer quad.
+26 -26
View File
@@ -317,8 +317,8 @@ void MeshTextureGraphicsItem::_doDrawOutput(QPainter* painter)
{
QSharedPointer<Mesh> outputMesh = qSharedPointerCast<Mesh>(_shape);
QSharedPointer<Mesh> inputMesh = qSharedPointerCast<Mesh>(_inputShape);
QVector<QVector<Quad> > outputQuads = outputMesh->getQuads2d();
QVector<QVector<Quad> > inputQuads = inputMesh->getQuads2d();
QVector<QVector<Quad::ptr> > outputQuads = outputMesh->getQuads2d();
QVector<QVector<Quad::ptr> > inputQuads = inputMesh->getQuads2d();
// Check if we increased or decreased number of columns/rows in mesh.
bool forceRebuild = false;
@@ -350,14 +350,14 @@ void MeshTextureGraphicsItem::_doDrawOutput(QPainter* painter)
{
for (int y = 0; y < outputMesh->nVerticalQuads(); y++)
{
Quad& inputQuad = inputQuads[x][y];
Quad& outputQuad = outputQuads[x][y];
Quad::ptr inputQuad = inputQuads[x][y];
Quad::ptr outputQuad = outputQuads[x][y];
// Verify if item needs recomputing.
CacheQuadItem& item = _cachedQuadItems[x][y];
if (forceRebuild ||
item.parent.input.toPolygon() != inputQuad.toPolygon() ||
item.parent.output.toPolygon() != outputQuad.toPolygon()) {
item.parent.input->toPolygon() != inputQuad->toPolygon() ||
item.parent.output->toPolygon() != outputQuad->toPolygon()) {
// Copy input and output quads for verification purposes.
item.parent.input = inputQuad;
@@ -366,7 +366,7 @@ void MeshTextureGraphicsItem::_doDrawOutput(QPainter* painter)
// Recompute sub quads.
item.subQuads.clear();
QSizeF size = mapFromScene(outputQuad.toPolygon()).boundingRect().size();
QSizeF size = mapFromScene(outputQuad->toPolygon()).boundingRect().size();
float area = size.width() * size.height();
// Rebuild cache quad item.
@@ -377,9 +377,9 @@ void MeshTextureGraphicsItem::_doDrawOutput(QPainter* painter)
foreach (CacheQuadMapping m, item.subQuads)
{
glBegin(GL_QUADS);
for (int i = 0; i < outputQuad.nVertices(); i++)
for (int i = 0; i < outputQuad->nVertices(); i++)
{
Util::setGlTexPoint(*_texture.toStrongRef(), m.input.getVertex(i), mapFromScene(m.output.getVertex(i)));
Util::setGlTexPoint(*_texture.toStrongRef(), m.input->getVertex(i), mapFromScene(m.output->getVertex(i)));
}
glEnd();
}
@@ -388,21 +388,21 @@ void MeshTextureGraphicsItem::_doDrawOutput(QPainter* painter)
}
}
void MeshTextureGraphicsItem::_buildCacheQuadItem(CacheQuadItem& item, const Quad& inputQuad, const Quad& outputQuad, float outputArea, float inputThreshod, float outputThreshold, int minArea, int maxDepth)
void MeshTextureGraphicsItem::_buildCacheQuadItem(CacheQuadItem& item, const Quad::ptr& inputQuad, const Quad::ptr& outputQuad, float outputArea, float inputThreshod, float outputThreshold, int minArea, int maxDepth)
{
bool stop = false;
if (maxDepth == 0 || outputArea < minArea)
stop = true;
else {
QPointF oa = mapFromScene(outputQuad.getVertex(0));
QPointF ob = mapFromScene(outputQuad.getVertex(1));
QPointF oc = mapFromScene(outputQuad.getVertex(2));
QPointF od = mapFromScene(outputQuad.getVertex(3));
QPointF oa = mapFromScene(outputQuad->getVertex(0));
QPointF ob = mapFromScene(outputQuad->getVertex(1));
QPointF oc = mapFromScene(outputQuad->getVertex(2));
QPointF od = mapFromScene(outputQuad->getVertex(3));
QPointF ia = inputQuad.getVertex(0);
QPointF ib = inputQuad.getVertex(1);
QPointF ic = inputQuad.getVertex(2);
QPointF id = inputQuad.getVertex(3);
QPointF ia = inputQuad->getVertex(0);
QPointF ib = inputQuad->getVertex(1);
QPointF ic = inputQuad->getVertex(2);
QPointF id = inputQuad->getVertex(3);
QPointF outputV1 = oa-ob;
QPointF outputV2 = oc-ob;
@@ -440,8 +440,8 @@ void MeshTextureGraphicsItem::_buildCacheQuadItem(CacheQuadItem& item, const Qua
}
else // subdivide
{
QList<Quad> inputSubQuads = _split(inputQuad);
QList<Quad> outputSubQuads = _split(outputQuad);
QList<Quad::ptr> inputSubQuads = _split(*inputQuad);
QList<Quad::ptr> outputSubQuads = _split(*outputQuad);
for (int i = 0; i < inputSubQuads.size(); i++)
{
_buildCacheQuadItem(item, inputSubQuads[i], outputSubQuads[i], outputArea*0.25, inputThreshod, outputThreshold, minArea, (maxDepth == -1 ? -1 : maxDepth - 1));
@@ -449,9 +449,9 @@ void MeshTextureGraphicsItem::_buildCacheQuadItem(CacheQuadItem& item, const Qua
}
}
QList<Quad> MeshTextureGraphicsItem::_split(const Quad& quad)
QList<Quad::ptr> MeshTextureGraphicsItem::_split(const Quad& quad)
{
QList<Quad> quads;
QList<Quad::ptr> quads;
QPointF a = quad.getVertex(0);
QPointF b = quad.getVertex(1);
@@ -465,10 +465,10 @@ QList<Quad> MeshTextureGraphicsItem::_split(const Quad& quad)
QPointF abcd = (ab + cd) * 0.5f;
quads.append(Quad(a, ab, abcd, ad));
quads.append(Quad(ab, b, bc, abcd));
quads.append(Quad(abcd, bc, c, cd));
quads.append(Quad(ad, abcd, cd, d));
quads.append(Quad::ptr(new Quad(a, ab, abcd, ad)));
quads.append(Quad::ptr(new Quad(ab, b, bc, abcd)));
quads.append(Quad::ptr(new Quad(abcd, bc, c, cd)));
quads.append(Quad::ptr(new Quad(ad, abcd, cd, d)));
return quads;
}
+6 -5
View File
@@ -31,7 +31,8 @@
#include <stdlib.h>
#include <stdio.h>
#include "Shape.h"
#include "Shapes.h"
#include "Paint.h"
#include "Mapping.h"
#include "MapperGLCanvas.h"
@@ -204,8 +205,8 @@ class MeshTextureGraphicsItem : public PolygonTextureGraphicsItem
{
// Internal use (cache). A structure consisting of the input and output quads of mapping.
struct CacheQuadMapping {
Quad input;
Quad output;
Quad::ptr input;
Quad::ptr output;
};
// Internal use (cache). Contains a parent mapping and all its sub-mappings.
@@ -224,12 +225,12 @@ private:
* Builds cache item recursively using the technique described in
* Oliveira, M. "Correcting Texture Mapping Errors Introduced by Graphics Hardware"
*/
void _buildCacheQuadItem(CacheQuadItem& item, const Quad& inputQuad, const Quad& outputQuad,
void _buildCacheQuadItem(CacheQuadItem& item, const Quad::ptr& inputQuad, const Quad::ptr& outputQuad,
float outputArea, float inputThreshold = 0.0001f, float outputThreshold = 0.001f,
int minArea=MM::MESH_SUBDIVISION_MIN_AREA, int maxDepth=-1);
// Help function that returns four equal-size sub-quads from a quad.
QList<Quad> _split(const Quad& quad);
QList<Quad::ptr> _split(const Quad& quad);
// Contains the current cache.
QVector<QVector<CacheQuadItem> > _cachedQuadItems;
+29
View File
@@ -0,0 +1,29 @@
/*
* Shapes.h
*
* (c) 2016 Sofian Audry -- info(@)sofianaudry(.)com
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef SHAPES_H_
#define SHAPES_H_
#include "Quad.h"
#include "Ellipse.h"
#include "Mesh.h"
#include "Triangle.h"
#endif /* SHAPES_H_ */
+49
View File
@@ -0,0 +1,49 @@
/*
* Triangle.h
*
* (c) 2016 Sofian Audry -- info(@)sofianaudry(.)com
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef TRIANGLE_H_
#define TRIANGLE_H_
#include "Polygon.h"
/**
* Triangle shape.
*/
class Triangle : public Polygon
{
Q_OBJECT
public:
Triangle() {}
Triangle(QPointF p1, QPointF p2, QPointF p3)
{
_addVertex(p1);
_addVertex(p2);
_addVertex(p3);
}
virtual ~Triangle() {}
virtual QString getType() const { return "triangle"; }
protected:
/// Returns a new MShape (using default constructor).
virtual MShape* _create() const { return new Triangle(); }
};
#endif /* TRIANGLE_H_ */
+3 -3
View File
@@ -216,10 +216,10 @@ void drawControlsMesh(QPainter* painter, const QList<int>* selectedVertices, con
painter->setPen(MM::SHAPE_INNER_STROKE);
// Draw inner quads.
QVector<Quad> quads = mesh.getQuads();
for (QVector<Quad>::const_iterator it = quads.begin(); it != quads.end(); ++it)
QVector<Quad::ptr> quads = mesh.getQuads();
for (QVector<Quad::ptr>::const_iterator it = quads.begin(); it != quads.end(); ++it)
{
painter->drawPolygon(it->toPolygon());
painter->drawPolygon((*it)->toPolygon());
}
// Draw outer quad.
+2 -1
View File
@@ -26,8 +26,9 @@
#include <GL/gl.h>
#endif
#include "Shapes.h"
#include "MM.h"
#include "Shape.h"
#include "Paint.h"
#include <QString>
+9
View File
@@ -10,6 +10,7 @@ HEADERS = \
Commands.h \
DestinationGLCanvas.h \
Element.h \
Ellipse.h \
MM.h \
MainApplication.h \
MainWindow.h \
@@ -19,6 +20,7 @@ HEADERS = \
MappingManager.h \
Maths.h \
MediaImpl.h \
Mesh.h \
MetaObjectRegistry.h \
OscInterface.h \
OscReceiver.h \
@@ -26,13 +28,17 @@ HEADERS = \
OutputGLWindow.h \
Paint.h \
PaintGui.h \
Polygon.h \
PreferencesDialog.h \
ProjectReader.h \
ProjectWriter.h \
Quad.h \
Shape.h \
Shapes.h \
ShapeControlPainter.h \
ShapeGraphicsItem.h \
SourceGLCanvas.h \
Triangle.h \
UidAllocator.h \
Util.h
@@ -40,6 +46,7 @@ SOURCES = \
Commands.cpp \
DestinationGLCanvas.cpp \
Element.cpp \
Ellipse.cpp \
MM.cpp \
MainApplication.cpp \
MainWindow.cpp \
@@ -48,6 +55,7 @@ SOURCES = \
Mapping.cpp \
MappingManager.cpp \
MediaImpl.cpp \
Mesh.cpp \
MetaObjectRegistry.cpp \
OscInterface.cpp \
OscReceiver.cpp \
@@ -55,6 +63,7 @@ SOURCES = \
OutputGLWindow.cpp \
Paint.cpp \
PaintGui.cpp \
Polygon.cpp \
PreferencesDialog.cpp \
ProjectReader.cpp \
ProjectWriter.cpp \