/**********************************************************************
Painter - drawing spheres, cylinders and text in a GLWidget
Copyright (C) 2007 Benoit Jacob
Copyright (C) 2007 Donald Ephraim Curtis
Copyright (C) 2007-2008 Marcus D. Hanwell
This file is part of the Avogadro molecular editor project.
For more information, see
Avogadro 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.
Avogadro is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
**********************************************************************/
#include "glpainter.h"
#include "glwidget.h"
#include "sphere.h"
#include "cylinder.h"
#include "textrenderer.h"
#include
#include
#include
#include
#include
#include
#include
#include
namespace Avogadro
{
const int PAINTER_GLOBAL_QUALITY_SETTINGS = 5;
const int DEFAULT_GLOBAL_QUALITY_SETTING = PAINTER_GLOBAL_QUALITY_SETTINGS - 3;
const int PAINTER_DETAIL_LEVELS = 10;
// Sphere detail level array. Each row is a detail level.
// The first column is the sphere detail level at the furthest
// point and the last column is the detail level at the closest
// point.
const int PAINTER_SPHERES_LEVELS_ARRAY[5][10]
=
{ {0, 0, 1, 1, 2, 2, 3, 3, 4, 4},
{0, 1, 2, 3, 4, 4, 5, 5, 6, 6},
{1, 2, 3, 4, 5, 6, 7, 8, 9, 9},
{1, 2, 3, 4, 6, 7, 8, 9, 11, 12},
{2, 3, 4, 5, 7, 9, 12, 15, 18, 22}
};
const double PAINTER_SPHERES_LIMIT_MIN_LEVEL = 0.005;
const double PAINTER_SPHERES_LIMIT_MAX_LEVEL = 0.15;
// Cylinder detail level array. Each row is a detail level.
// The first column is the cylinder detail level at the furthest
// point and the last column is the detail level at the closest
// point.
const int PAINTER_CYLINDERS_LEVELS_ARRAY[5][10]
=
{ {0, 3, 5, 5, 8, 8, 12, 12, 16, 16},
{0, 4, 6, 9, 12, 12, 16, 16, 20, 20},
{0, 4, 6, 10, 14, 18, 22, 26, 32, 40},
{0, 4, 6, 12, 16, 20, 24, 28, 34, 42},
{0, 5, 10, 15, 20, 25, 30, 35, 40, 45}
};
const double PAINTER_CYLINDERS_LIMIT_MIN_LEVEL = 0.001;
const double PAINTER_CYLINDERS_LIMIT_MAX_LEVEL = 0.03;
const int PAINTER_MAX_DETAIL_LEVEL = PAINTER_DETAIL_LEVELS - 1;
const double PAINTER_SPHERES_SQRT_LIMIT_MIN_LEVEL
= sqrt ( PAINTER_SPHERES_LIMIT_MIN_LEVEL );
const double PAINTER_SPHERES_SQRT_LIMIT_MAX_LEVEL
= sqrt ( PAINTER_SPHERES_LIMIT_MAX_LEVEL );
const double PAINTER_SPHERES_DETAIL_COEFF
= static_cast ( PAINTER_MAX_DETAIL_LEVEL - 1 )
/ ( PAINTER_SPHERES_SQRT_LIMIT_MAX_LEVEL - PAINTER_SPHERES_SQRT_LIMIT_MIN_LEVEL );
const double PAINTER_CYLINDERS_SQRT_LIMIT_MIN_LEVEL
= sqrt ( PAINTER_CYLINDERS_LIMIT_MIN_LEVEL );
const double PAINTER_CYLINDERS_SQRT_LIMIT_MAX_LEVEL
= sqrt ( PAINTER_CYLINDERS_LIMIT_MAX_LEVEL );
const double PAINTER_CYLINDERS_DETAIL_COEFF
= static_cast ( PAINTER_MAX_DETAIL_LEVEL - 1 )
/ ( PAINTER_CYLINDERS_SQRT_LIMIT_MAX_LEVEL - PAINTER_CYLINDERS_SQRT_LIMIT_MIN_LEVEL );
const double PAINTER_FRUSTUM_CULL_TRESHOLD = -0.8;
class GLPainterPrivate
{
public:
GLPainterPrivate() : widget ( 0 ), newQuality(-1), quality ( 0 ), overflow(0),
spheres ( 0 ), cylinders ( 0 ),
textRenderer ( new TextRenderer ), initialized ( false ), sharing ( 0 ),
type(Primitive::OtherType), id ( -1 ), color(0) {};
~GLPainterPrivate()
{
deleteObjects();
delete textRenderer;
}
GLWidget *widget;
int newQuality;
int quality;
int overflow;
/** array of pointers to Spheres. You might ask, why not have
* a plain array of Spheres. The idea is that more than one global detail level
* may use a given sphere detail level. It is therefore interesting to be able
* to share that sphere, instead of having redundant spheres in memory.
*/
Sphere **spheres;
/** array of pointers to Cylinders. You might ask, why not have
* a plain array of Cylinders. The idea is that more than one global detail level
* may use a given cylinder detail level. It is therefore interesting to be able
* to share that cylinder, instead of having redundant cylinder in memory.
*/
Cylinder **cylinders;
TextRenderer *textRenderer;
bool initialized;
void deleteObjects();
void createObjects();
inline bool isValid();
/**
* Painters can be shared, we must keep track of this.
*/
int sharing;
// The primitive type and id of the current object
Primitive::Type type;
int id;
Color color;
};
inline bool GLPainterPrivate::isValid()
{
if(!widget)
{
qWarning("GLPainter not active.");
return false;
}
if(!initialized)
{
if(newQuality != -1) {
quality = newQuality;
}
qDebug() << "createObjects()";
createObjects();
initialized = true;
}
else if(newQuality != -1)
{
if(newQuality != quality)
{
qDebug() << "updateObjects()";
deleteObjects();
quality = newQuality;
createObjects();
}
newQuality = -1;
}
return true;
}
void GLPainterPrivate::deleteObjects()
{
int level, lastLevel, n;
// delete the spheres. One has to be wary that more than one sphere
// pointer may have the same value. One wants to avoid deleting twice the same sphere.
if (spheres) {
lastLevel = -1;
for (n = 0; n < PAINTER_DETAIL_LEVELS; ++n) {
level = PAINTER_SPHERES_LEVELS_ARRAY[quality][n];
if (level != lastLevel) {
lastLevel = level;
if (spheres[n]) {
delete spheres[n];
spheres[n] = 0;
}
}
}
delete[] spheres;
spheres = 0;
}
// delete the cylinders. One has to be wary that more than one cylinder
// pointer may have the same value. One wants to avoid deleting twice the same cylinder.
if (cylinders)
{
lastLevel = -1;
for (n = 0; n < PAINTER_DETAIL_LEVELS; ++n) {
level = PAINTER_CYLINDERS_LEVELS_ARRAY[quality][n];
if (level != lastLevel) {
lastLevel = level;
if (cylinders[n]) {
delete cylinders[n];
cylinders[n] = 0;
}
}
}
delete[] cylinders;
cylinders = 0;
}
}
void GLPainterPrivate::createObjects()
{
// create the spheres. More than one sphere detail level may have the same value.
// in that case we want to reuse the corresponding sphere by just copying the pointer,
// instead of creating redundant spheres.
if ( spheres == 0 )
{
spheres = new Sphere*[PAINTER_DETAIL_LEVELS];
int level, lastLevel;
lastLevel = PAINTER_SPHERES_LEVELS_ARRAY[quality][0];
spheres[0] = new Sphere ( lastLevel );
for (int n = 1; n < PAINTER_DETAIL_LEVELS; ++n) {
level = PAINTER_SPHERES_LEVELS_ARRAY[quality][n];
if (level == lastLevel) {
spheres[n] = spheres[n-1];
}
else {
lastLevel = level;
spheres[n] = new Sphere ( level );
}
}
}
// create the cylinders. More than one cylinder detail level may have the same value.
// in that case we want to reuse the corresponding cylinder by just copying the pointer,
// instead of creating redundant cylinders.
if (cylinders == 0) {
cylinders = new Cylinder*[PAINTER_DETAIL_LEVELS];
int level, lastLevel;
lastLevel = PAINTER_SPHERES_LEVELS_ARRAY[quality][0];
cylinders[0] = new Cylinder ( lastLevel );
for (int n = 1; n < PAINTER_DETAIL_LEVELS; ++n) {
level = PAINTER_CYLINDERS_LEVELS_ARRAY[quality][n];
if (level == lastLevel) {
cylinders[n] = cylinders[n-1];
}
else {
lastLevel = level;
cylinders[n] = new Cylinder ( level );
}
}
}
}
GLPainter::GLPainter(int quality) : d(new GLPainterPrivate),
m_dynamicScaling(true)
{
if (quality < 0 || quality >= PAINTER_MAX_DETAIL_LEVEL)
quality = DEFAULT_GLOBAL_QUALITY_SETTING;
else
d->quality = quality;
}
GLPainter::~GLPainter()
{
delete d;
}
void GLPainter::setQuality ( int quality )
{
assert ( quality >= 0 && quality < PAINTER_GLOBAL_QUALITY_SETTINGS );
d->newQuality = quality;
}
int GLPainter::quality() const
{
return d->quality;
}
void GLPainter::setName ( const Primitive *primitive )
{
d->type = primitive->type();
if (d->type == Primitive::AtomType)
d->id = static_cast(primitive)->index();
else if (d->type == Primitive::BondType)
d->id = static_cast(primitive)->index();
}
void GLPainter::setName ( Primitive::Type type, int id )
{
d->type = type;
d->id = id;
}
void GLPainter::setColor (const Color *color)
{
d->color.set(color->red(), color->green(), color->blue(), color->alpha());
}
void GLPainter::setColor (const QColor *color)
{
d->color.set(color->redF(), color->greenF(), color->blueF(),
color->alphaF());
}
void GLPainter::setColor ( float red, float green, float blue, float alpha )
{
d->color.set(red, green, blue, alpha);
}
void GLPainter::drawSphere ( const Eigen::Vector3d *center, float radius )
{
if(!d->isValid()) { return; }
// Default to the minimum detail level for this quality
int detailLevel = PAINTER_MAX_DETAIL_LEVEL / 3;
if (m_dynamicScaling) {
double apparentRadius = radius / d->widget->camera()->distance(*center);
detailLevel = 1 + static_cast(floor (PAINTER_SPHERES_DETAIL_COEFF
* (sqrt(apparentRadius) - PAINTER_SPHERES_SQRT_LIMIT_MIN_LEVEL)));
if (detailLevel < 0)
detailLevel = 0;
if (detailLevel > PAINTER_MAX_DETAIL_LEVEL)
detailLevel = PAINTER_MAX_DETAIL_LEVEL;
}
d->color.applyAsMaterials();
pushName();
d->spheres[detailLevel]->draw (*center, radius);
popName();
}
void GLPainter::drawCylinder ( const Eigen::Vector3d &end1, const Eigen::Vector3d &end2,
double radius )
{
if(!d->isValid()) { return; }
// Default to the minimum detail level for this quality
int detailLevel = PAINTER_MAX_DETAIL_LEVEL / 3;
if (m_dynamicScaling) {
double apparentRadius = radius / d->widget->camera()->distance(end1);
detailLevel = 1 + static_cast ( floor (
PAINTER_CYLINDERS_DETAIL_COEFF
* ( sqrt ( apparentRadius ) - PAINTER_CYLINDERS_SQRT_LIMIT_MIN_LEVEL )
) );
if (detailLevel < 0)
detailLevel = 0;
if (detailLevel > PAINTER_MAX_DETAIL_LEVEL)
detailLevel = PAINTER_MAX_DETAIL_LEVEL;
}
d->color.applyAsMaterials();
pushName();
d->cylinders[detailLevel]->draw ( end1, end2, radius );
popName();
}
void GLPainter::drawMultiCylinder ( const Eigen::Vector3d &end1, const Eigen::Vector3d &end2,
double radius, int order, double shift )
{
if(!d->isValid()) { return; }
// Default to the minimum detail level for this quality
int detailLevel = PAINTER_MAX_DETAIL_LEVEL / 3;
if (m_dynamicScaling) {
double apparentRadius = radius / d->widget->camera()->distance(end1);
detailLevel = 1 + static_cast ( floor (
PAINTER_CYLINDERS_DETAIL_COEFF
* ( sqrt ( apparentRadius ) - PAINTER_CYLINDERS_SQRT_LIMIT_MIN_LEVEL )
) );
if (detailLevel < 0)
detailLevel = 0;
if (detailLevel > PAINTER_MAX_DETAIL_LEVEL)
detailLevel = PAINTER_MAX_DETAIL_LEVEL;
}
d->color.applyAsMaterials();
pushName();
d->cylinders[detailLevel]->drawMulti ( end1, end2, radius, order,
shift, d->widget->normalVector() );
popName();
}
void GLPainter::drawCone(const Eigen::Vector3d &base, const Eigen::Vector3d &tip,
double radius)
{
const int CONE_TESS_LEVEL = 30;
// This draws a cone which will be most useful for drawing arrows etc.
Eigen::Vector3d axis = tip - base;
Eigen::Vector3d axisNormalized = axis.normalized();
Eigen::Vector3d ortho1, ortho2;
ortho1 = axisNormalized.unitOrthogonal();
ortho1 *= radius;
ortho2 = axisNormalized.cross(ortho1);
d->color.applyAsMaterials();
// Draw the cone
// unfortunately we can't use a GL_TRIANGLE_FAN because this would force
// having a common normal vector at the tip.
for (int j = 0; j < CONE_TESS_LEVEL; j++) {
const double alphaStep = 2.0 * M_PI / CONE_TESS_LEVEL;
double alpha = j * alphaStep;
double alphaNext = alpha + alphaStep;
double alphaPrec = alpha - alphaStep;
Eigen::Vector3d v = sin(alpha) * ortho1 + cos(alpha) * ortho2 + base;
Eigen::Vector3d vNext = sin(alphaNext) * ortho1 + cos(alphaNext) * ortho2 + base;
Eigen::Vector3d vPrec = sin(alphaPrec) * ortho1 + cos(alphaPrec) * ortho2 + base;
Eigen::Vector3d n = (tip - v).cross(v - vPrec).normalized();
Eigen::Vector3d nNext = (tip - vNext).cross(vNext - v).normalized();
glBegin(GL_TRIANGLES);
glNormal3dv((n+nNext).normalized().data());
glVertex3dv(tip.data());
glNormal3dv(nNext.data());
glVertex3dv(vNext.data());
glNormal3dv(n.data());
glVertex3dv(v.data());
glEnd();
}
// Now to draw the base
glBegin(GL_TRIANGLE_FAN);
glNormal3dv((-axisNormalized).eval().data());
glVertex3dv(base.data());
for (int j = 0; j <= CONE_TESS_LEVEL; j++) {
double alpha = -j * M_PI / (CONE_TESS_LEVEL/2.0);
Eigen::Vector3d v = cos(alpha) * ortho1 + sin(alpha) * ortho2 + base;
glVertex3dv(v.data());
}
glEnd();
}
void GLPainter::drawLine(const Eigen::Vector3d &start, const Eigen::Vector3d &end,
double lineWidth)
{
// Draw a line between two points of the specified thickness
if(!d->isValid()) { return; }
glDisable(GL_LIGHTING);
glLineWidth(lineWidth);
d->color.apply();
// Draw the line
glBegin(GL_LINE_STRIP);
glVertex3dv(start.data());
glVertex3dv(end.data());
glEnd();
glEnable(GL_LIGHTING);
}
void GLPainter::drawMultiLine(const Eigen::Vector3d &end1,
const Eigen::Vector3d &end2,
double lineWidth, int order, short stipple)
{
// Draw multiple lines between two points of the specified thickness
if(!d->isValid()) { return; }
// construct the 4D transformation matrix
Eigen::Matrix4d matrix;
matrix.row(3) << 0,0,0,1;
matrix.block<3,1>(0,3) = end1;
matrix.block<3,1>(0,2) = end2 - end1; // the "axis vector" of the line
// Now we want to construct an orthonormal basis whose third
// vector is axis.normalized(). The first vector in this
// basis, which we call ortho1, should be approximately lying in the
// z=0 plane if possible. This is to ensure double bonds don't look
// like single bonds from the default point of view.
Eigen::Vector3d axisNormalized = matrix.block<3,1>(0,2).normalized();
Eigen::Block ortho1(matrix, 0, 0);
ortho1 = axisNormalized.cross(d->widget->normalVector());
double ortho1Norm = ortho1.norm();
if( ortho1Norm > 0.001 ) ortho1 = ortho1.normalized() * lineWidth;
else ortho1 = axisNormalized.unitOrthogonal() * lineWidth;
matrix.block<3,1>(0,1) = axisNormalized.cross(ortho1);
// now the matrix is entirely filled, so we can do the actual drawing !
glPushMatrix();
glMultMatrixd( matrix.data() );
glDisable(GL_LIGHTING);
glLineWidth(lineWidth);
d->color.apply();
glEnable(GL_LINE_STIPPLE);
glLineStipple(1, stipple);
// Draw the line
if (order == 1 || order == -1) { // single or aromatic
glBegin(GL_LINE_STRIP);
glVertex3f(0.0, 0.0, 0.0);
glVertex3f(0.0, 0.0, 1.0);
glEnd();
}
else {
double angleOffset = 0.0;
if( order >= 3 ) {
if( order == 3 ) angleOffset = 90.0;
else angleOffset = 22.5;
}
// these may need further refinement
double displacementFactor = 0.0004 * lineWidth + 0.018;
for( int i = 0; i < order; i++) {
glPushMatrix();
glRotated( angleOffset + 360.0 * i / order,
0.0, 0.0, 1.0 );
glTranslated( displacementFactor, 0.0, 0.0 );
glBegin(GL_LINE_STRIP);
glVertex3f(0.0, 0.0, 0.0);
glVertex3f(0.0, 0.0, 1.0);
glEnd();
glPopMatrix();
}
}
glDisable(GL_LINE_STIPPLE);
glPopMatrix();
glEnable(GL_LIGHTING);
}
void GLPainter::drawTriangle(const Eigen::Vector3d &p1, const Eigen::Vector3d &p2,
const Eigen::Vector3d &p3)
{
if(!d->isValid()) { return; }
// Sort out the winding order by assigning in the correct order
Eigen::Vector3d tp2, tp3;
// Don't want planes to be too shiny.
d->color.applyAsFlatMaterials();
// The plane normal vector of the view
const Eigen::Vector3d planeNormalVector = d->widget->normalVector();
// Calculate the normal for the triangle as GL_AUTO_NORMAL doesn't seem to work
Eigen::Vector3d v1, v2, n;
v1 = p2 - p1;
v2 = p3 - p2;
n = v1.cross(v2);
n.normalize();
// Dot product is 1 or -1 - want normals facing the same direction
if (n.dot(p1 - d->widget->camera()->backTransformedZAxis()) < 0) {
n *= -1;
tp2 = p3;
tp3 = p2;
}
else {
tp2 = p2;
tp3 = p3;
}
glBegin(GL_TRIANGLES);
glNormal3dv(n.data());
glVertex3dv(p1.data());
glVertex3dv(tp2.data());
glVertex3dv(tp3.data());
glEnd();
}
void GLPainter::drawTriangle(const Eigen::Vector3d &p1, const Eigen::Vector3d &p2,
const Eigen::Vector3d &p3, const Eigen::Vector3d &n)
{
if(!d->isValid()) { return; }
// Don't want planes to be too shiny.
d->color.applyAsFlatMaterials();
d->color.apply();
glBegin(GL_TRIANGLES);
glNormal3dv(n.data());
glVertex3dv(p1.data());
glVertex3dv(p2.data());
glVertex3dv(p3.data());
glEnd();
}
void GLPainter::drawSpline(const QVector& pts, double radius)
{
// Draw a spline between two points of the specified thickness
if(!d->isValid()) { return; }
// The first value is repeated three times as is the last in order to complete the curve
QVector points = pts;
// glColor4f(d->color.red(), d->color.green(), d->color.blue(), d->color.alpha());
/* QVector p, a;
a.resize(4);
p.resize(4);
// Define the number of interpolated points between control points
int numPts = 40;
double step = 1. / double(numPts);
Eigen::Vector3d last, cur;
for (int i = 2; i < pts.size()+1; i++) {
p[0] = points.at(i-1);
p[1] = points.at(i);
p[2] = points.at(i+1);
p[3] = points.at(i+2);
// Now calculate the basis
a[0] = (-p[0] + 3.*p[1] - 3.*p[2] + p[3]) / 6.;
a[1] = (3.*p[0] - 6.*p[1] + 3.*p[2]) / 6.;
a[2] = (-3.*p[0] + 3.*p[2]) / 6.;
a[3] = (p[0] + 4.*p[1] + p[2]) / 6.;
// Now interpolate some points and draw them...
last = a[3];
for (int j = 0; j < numPts; j++) {
double t = step * j;
cur = a[3] + t*(a[2] + t*(a[1] + t*a[0]));
// drawCylinder(last, cur, radius/4.);
last = cur;
}
}
*/
glEnable(GL_AUTO_NORMAL);
GLUnurbsObj *nurb = gluNewNurbsRenderer();
// These settings were inspired by the code supplied by Thomas Margraf
// and tweaked a little more by me - performance seems good.
// FIXME Should still be linked to our global quality level.
gluNurbsProperty(nurb, GLU_V_STEP, 4);
gluNurbsProperty(nurb, GLU_U_STEP, 10);
gluNurbsProperty(nurb, GLU_CULLING, GL_TRUE);
gluNurbsProperty(nurb, GLU_SAMPLING_METHOD, GLU_DOMAIN_DISTANCE);
// This seems reasonable but should be linked to the detail level
int TUBE_TESS = 6;
QVarLengthArray ctrlpts(points.size()*TUBE_TESS*3);
QVarLengthArray uknots(points.size() + 4);
// The first one is a special case
Eigen::Vector3d axis = points[1] - points[0];
Eigen::Vector3d axisNormalized = axis.normalized();
Eigen::Vector3d ortho1, ortho2;
ortho1 = axisNormalized.unitOrthogonal() * radius;
ortho2 = axisNormalized.cross(ortho1);
for (int j = 0; j < TUBE_TESS; j++) {
double alpha = j * M_PI / 1.5f;
Eigen::Vector3d v = cosf(alpha) * ortho1 + sinf(alpha) * ortho2;
ctrlpts[3*j+0] = v.x() + points[0].x();
ctrlpts[3*j+1] = v.y() + points[0].y();
ctrlpts[3*j+2] = v.z() + points[0].z();
}
uknots[2] = 0.0;
for (int i = 1; i < points.size(); i++) {
axis = Eigen::Vector3d(points[i-1].x() - points[i].x(),
points[i-1].y() - points[i].y(),
points[i-1].z() - points[i].z());
axisNormalized = axis.normalized();
ortho1 = axisNormalized.unitOrthogonal();
ortho1 *= radius;
ortho2 = axisNormalized.cross(ortho1);
for (int j = 0; j < TUBE_TESS; j++) {
double alpha = j * M_PI / 1.5f;
Eigen::Vector3d v = cosf(alpha) * ortho1 + sinf(alpha) * ortho2;
ctrlpts[(i*TUBE_TESS + j)*3 + 0] = v.x() + points[i].x();
ctrlpts[(i*TUBE_TESS + j)*3 + 1] = v.y() + points[i].y();
ctrlpts[(i*TUBE_TESS + j)*3 + 2] = v.z() + points[i].z();
}
uknots[i+2] = i - 1.0;
}
uknots[0] = 0.0;
uknots[1] = 0.0;
uknots[points.size()] = points.size() - 1.0;
uknots[points.size()+1] = points.size() - 1.0;
uknots[points.size()+2] = points.size() - 1.0;
uknots[points.size()+3] = points.size() - 1.0;
// Hard coded right now - will generalise for arbitrary TUBE_TESS values
GLfloat vknots[10] = {0., 0., 1., 2., 3., 4., 5., 6., 7., 7.};
d->color.applyAsMaterials();
// Actually draw the tube as a nurb
gluBeginSurface(nurb);
gluNurbsSurface(nurb,
points.size() + 4, uknots.data(),
TUBE_TESS + 4, vknots,
TUBE_TESS*3,
3,
ctrlpts.data(),
4, 4,
GL_MAP2_VERTEX_3);
gluEndSurface(nurb);
gluDeleteNurbsRenderer(nurb);
glDisable(GL_AUTO_NORMAL);
}
void GLPainter::drawShadedSector(const Eigen::Vector3d & origin, const Eigen::Vector3d & direction1,
const Eigen::Vector3d & direction2, double radius, bool alternateAngle)
{
assert( d->widget );
// Get vectors representing the two lines out from the center of the circle.
Eigen::Vector3d u = direction1 - origin;
Eigen::Vector3d v = direction2 - origin;
// Adjust the length of u and v to the radius given.
u = u.normalized() * radius;
v = v.normalized() * radius;
// Angle between u and v.
double uvAngle = acos(u.dot(v) / v.squaredNorm()) * 180.0 / M_PI;
// If angle is less than 1 (will be approximated to 0), attempting to draw
// will crash, so return.
if (abs((int)uvAngle) <= 1)
return;
// If alternateAngle is set, subtract this angle from 360 to get the alternate angle.
if (alternateAngle) {
uvAngle = 360.0 - (uvAngle > 0 ? uvAngle : -uvAngle);
}
// Vector perpindicular to both u and v.
Eigen::Vector3d n = u.cross(v);
if (n.norm() < 1e-3)
{
Eigen::Vector3d A = u.cross(Eigen::Vector3d::UnitX());
Eigen::Vector3d B = u.cross(Eigen::Vector3d::UnitY());
n = A.norm() >= B.norm() ? A : B;
}
n.normalize();
// Calculate the points along the curve at each half-degree increment until we
// reach the next line.
Eigen::Vector3d points[720];
for (int theta = 1; theta < (uvAngle * 2); theta++)
{
// Apply a rotation about a vector perpindicular
// to the plane to the vector to find the new point.
if (alternateAngle) {
points[theta-1] = Eigen::AngleAxisd(theta * (M_PI / 180.0) / 2, n) * v;
} else {
points[theta-1] = Eigen::AngleAxisd(theta * (M_PI / 180.0) / 2, n) * u;
}
points[theta-1] = d->widget->camera()->modelview() * (origin + points[theta-1]);
}
// Get vectors representing the points' positions in terms of the model view.
Eigen::Vector3d _origin = d->widget->camera()->modelview() * origin;
Eigen::Vector3d _direction1 = d->widget->camera()->modelview() * (origin+u);
Eigen::Vector3d _direction2 = d->widget->camera()->modelview() * (origin+v);
glPushAttrib(GL_ALL_ATTRIB_BITS);
glPushMatrix();
glLoadIdentity();
glDisable(GL_LIGHTING);
glDisable(GL_CULL_FACE);
d->color.apply();
// Draw the transparent polygon that makes up the sector.
glBegin(GL_TRIANGLE_FAN);
glVertex3dv(_origin.data());
if (alternateAngle)
{
glVertex3dv(_direction2.data());
for (int i = 0; i < uvAngle*2 - 1; i++)
glVertex3dv(points[i].data());
glVertex3dv(_direction1.data());
}
else
{
glVertex3dv(_direction1.data());
for (int i = 0; i < uvAngle*2 - 1; i++)
glVertex3dv(points[i].data());
glVertex3dv(_direction2.data());
}
glEnd();
glPopMatrix();
glPopAttrib();
}
void GLPainter::drawArc(const Eigen::Vector3d & origin, const Eigen::Vector3d & direction1,
const Eigen::Vector3d & direction2, double radius, double lineWidth,
bool alternateAngle)
{
assert( d->widget );
// Get vectors representing the two lines out from the center of the circle.
Eigen::Vector3d u = direction1 - origin;
Eigen::Vector3d v = direction2 - origin;
// Adjust the length of u and v to the radius given.
u = u.normalized() * radius;
v = v.normalized() * radius;
// Angle between u and v.
double uvAngle = acos(u.dot(v) / v.squaredNorm()) * 180.0 / M_PI;
// If angle is less than 1 (will be approximated to 0), attempting to draw
// will crash, so return.
if (abs((int)uvAngle) <= 1)
return;
// If alternateAngle is set, subtract this angle from 360 to get the alternate angle.
if (alternateAngle) {
uvAngle = 360.0 - (uvAngle > 0 ? uvAngle : -uvAngle);
}
// Vector perpindicular to both u and v.
Eigen::Vector3d n = u.cross(v);
if (n.norm() < 1e-3)
{
Eigen::Vector3d A = u.cross(Eigen::Vector3d::UnitX());
Eigen::Vector3d B = u.cross(Eigen::Vector3d::UnitY());
n = A.norm() >= B.norm() ? A : B;
}
n.normalize();
// Calculate the points along the curve at each half-degree increment until we
// reach the next line.
Eigen::Vector3d points[720];
for (int theta = 1; theta < (uvAngle * 2); theta++)
{
// Apply a rotation about a vector perpindicular
// to the plane to the vector to find the new point.
if (alternateAngle) {
points[theta-1] = Eigen::AngleAxisd(theta * (M_PI / 180.0) / 2, n) * v;
} else {
points[theta-1] = Eigen::AngleAxisd(theta * (M_PI / 180.0) / 2, n) * u;
}
points[theta-1] = d->widget->camera()->modelview() * (origin + points[theta-1]);
}
// Get vectors representing the points' positions in terms of the model view.
Eigen::Vector3d _direction1 = d->widget->camera()->modelview() * (origin + u);
Eigen::Vector3d _direction2 = d->widget->camera()->modelview() * (origin + v);
glPushAttrib(GL_ALL_ATTRIB_BITS);
glPushMatrix();
glLoadIdentity();
glDisable(GL_LIGHTING);
glDisable(GL_CULL_FACE);
glLineWidth(lineWidth);
d->color.apply();
// Draw the arc.
glBegin(GL_LINE_STRIP);
if (alternateAngle)
{
glVertex3dv(_direction2.data());
for (int i = 0; i < uvAngle*2 - 1; i++)
glVertex3dv(points[i].data());
glVertex3dv(_direction1.data());
}
else
{
glVertex3dv(_direction1.data());
for (int i = 0; i < uvAngle*2 - 1; i++)
glVertex3dv(points[i].data());
glVertex3dv(_direction2.data());
}
glEnd();
glPopMatrix();
glPopAttrib();
}
void GLPainter::drawShadedQuadrilateral(const Eigen::Vector3d & point1, const Eigen::Vector3d & point2,
const Eigen::Vector3d & point3, const Eigen::Vector3d & point4)
{
assert( d->widget );
glPushAttrib(GL_ALL_ATTRIB_BITS);
glPushMatrix();
glLoadIdentity();
glDisable(GL_LIGHTING);
glDisable(GL_CULL_FACE);
d->color.apply();
glBegin(GL_TRIANGLE_FAN);
glVertex3dv(point1.data());
glVertex3dv(point2.data());
glVertex3dv(point3.data());
glVertex3dv(point4.data());
glEnd();
glPopMatrix();
glPopAttrib();
}
void GLPainter::drawQuadrilateral(const Eigen::Vector3d & point1, const Eigen::Vector3d & point2,
const Eigen::Vector3d & point3, const Eigen::Vector3d & point4,
double lineWidth)
{
assert( d->widget );
glPushAttrib(GL_ALL_ATTRIB_BITS);
glPushMatrix();
glLoadIdentity();
glDisable(GL_LIGHTING);
glDisable(GL_CULL_FACE);
glLineWidth(lineWidth);
d->color.apply();
glBegin(GL_LINE_LOOP);
glVertex3dv(point1.data());
glVertex3dv(point2.data());
glVertex3dv(point3.data());
glVertex3dv(point4.data());
glEnd();
glPopMatrix();
glPopAttrib();
}
void GLPainter::drawMesh(const Mesh & mesh, int mode)
{
// Now we draw the given mesh to the OpenGL widget
switch (mode)
{
case 0:
glPolygonMode(GL_FRONT, GL_FILL);
glEnable(GL_LIGHTING);
break;
case 1:
glPolygonMode(GL_FRONT, GL_LINE);
glDisable(GL_LIGHTING);
break;
case 2:
glPolygonMode(GL_FRONT, GL_POINT);
glDisable(GL_LIGHTING);
break;
}
d->color.apply();
d->color.applyAsMaterials();
glBegin(GL_TRIANGLES);
// Render the triangles of the mesh
std::vector v = mesh.vertices();
std::vector n = mesh.normals();
if (v.size() != n.size()) {
qDebug() << "Vertices size does not equal normals size:" << v.size()
<< n.size();
return;
}
for(unsigned int i = 0; i < v.size(); ++i) {
glNormal3fv(n.at(i).data());
glVertex3fv(v.at(i).data());
}
glEnd();
glPolygonMode(GL_FRONT, GL_FILL);
glEnable(GL_LIGHTING);
}
void GLPainter::drawColorMesh(const Mesh & mesh, int mode)
{
// Now we draw the given mesh to the OpenGL widget
switch (mode)
{
case 0:
glPolygonMode(GL_FRONT, GL_FILL);
glEnable(GL_LIGHTING);
break;
case 1:
glPolygonMode(GL_FRONT, GL_LINE);
glDisable(GL_LIGHTING);
break;
case 2:
glPolygonMode(GL_FRONT, GL_POINT);
glDisable(GL_LIGHTING);
break;
}
glBegin(GL_TRIANGLES);
// Render the triangles of the mesh
std::vector v = mesh.vertices();
std::vector n = mesh.normals();
std::vector c = mesh.colors();
if (v.size() != n.size() || v.size() != c.size()) {
qDebug() << "Vertices size does not equal normals size or color size:"
<< v.size() << n.size() << c.size();
return;
}
// Normal or reverse winding?
Color color;
for(unsigned int i = 0; i < v.size(); ++i) {
color.set(c[i].redF(), c[i].greenF(), c[i].blueF(), d->color.alpha());
color.applyAsMaterials();
glNormal3fv(n[i].data());
glVertex3fv(v[i].data());
}
glEnd();
glPolygonMode(GL_FRONT, GL_FILL);
glEnable(GL_LIGHTING);
}
int GLPainter::drawText ( int x, int y, const QString &string ) const
{
if(!d->isValid()) { return 0; }
d->textRenderer->begin ( d->widget );
int val = d->textRenderer->draw ( x, y, string );
d->textRenderer->end( );
return val;
}
int GLPainter::drawText ( const QPoint& pos, const QString &string ) const
{
assert( d->widget );
if(!d->isValid()) { return 0; }
d->textRenderer->begin( d->widget );
d->textRenderer->draw ( pos.x(), pos.y(), string );
d->textRenderer->end( );
return 0;
}
int GLPainter::drawText ( const Eigen::Vector3d &pos, const QString &string ) const
{
if(!d->isValid()) { return 0; }
d->textRenderer->begin ( d->widget );
int val = d->textRenderer->draw ( pos, string );
d->textRenderer->end( );
return val;
}
int GLPainter::defaultQuality()
{
return DEFAULT_GLOBAL_QUALITY_SETTING;
}
int GLPainter::maxQuality()
{
return PAINTER_GLOBAL_QUALITY_SETTINGS-1;
}
bool GLPainter::isShared()
{
return d->sharing-1;
}
bool GLPainter::isActive()
{
return (d->widget);
}
void GLPainter::incrementShare()
{
d->sharing++;
}
void GLPainter::decrementShare()
{
d->sharing--;
}
void GLPainter::begin(GLWidget *widget)
{
d->widget = widget;
d->overflow++;
// Ensure that the painter is properly initialised
d->isValid();
}
void GLPainter::end()
{
d->overflow--;
if(!d->overflow)
{
d->widget = 0;
}
}
void GLPainter::pushName()
{
// Push the type and id if they are set
if (d->id != -1)
{
glPushName(d->type);
glPushName(d->id);
}
}
void GLPainter::resetName()
{
d->type = Primitive::OtherType;
d->id = -1;
}
void GLPainter::popName()
{
// Pop the type and id if they are set, then reset them
if (d->id != -1)
{
glPopName();
glPopName();
resetName();
}
}
void GLPainter::setDynamicScaling(bool scaling)
{
m_dynamicScaling = scaling;
}
} // end namespace Avogadro