Skip to content
Snippets Groups Projects
Select Git revision
  • 205855acabc4c7f1b2f0e20ad6cb6cd0abad7520
  • master default protected
  • antenna-patterns
  • qt5-qopenglwidget
  • license-demo
  • isolated
  • isolated-fixedprofile
  • release_1.1
  • press-conference
  • rim-only
  • release_1.0
11 results

pulsaranimationwidget.cpp

Blame
  • pulsaranimationwidget.cpp 25.34 KiB
    /******************************************************************************
     *   Copyright (C) 2008 by Oliver Bock                                        *
     *   oliver.bock[AT]aei.mpg.de                                                *
     *                                                                            *
     *   This file is part of PulsatingScience.                                   *
     *                                                                            *
     *   PulsatingScience 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, version 3 of the License.               *
     *                                                                            *
     *   PulsatingScience 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 PulsatingScience. If not, see <http://www.gnu.org/licenses/>. *
     *                                                                            *
     ******************************************************************************/
    
    #include "pulsaranimationwidget.h"
    
    // workaround for lack of C99 support in C++
    #define isnan(x) ((x) != (x))
    
    const double PulsarAnimationWidget::deg2rad = PI/180.0;
    
    PulsarAnimationWidget::PulsarAnimationWidget(QWidget *parent) :
            QGLWidget(QGLFormat(QGL::AlphaChannel | QGL::SampleBuffers), parent),
            m_frameTimer(),
            m_pulseProfile(360, 0.0)
    {
        QString msgThis = tr("3D animation");
        if(!format().directRendering()) {
            QString msg = tr("Sorry, no direct rendering support for %1...");
            qWarning() << msg.arg(msgThis);
        }
        if(!format().doubleBuffer()) {
            QString msg = tr("Sorry, no double buffering support for %1...");
            qWarning() << msg.arg(msgThis);
        }
        if(!format().rgba()) {
            QString msg = tr("Sorry, no RGBA support for %1...");
            qWarning() << msg.arg(msgThis);
        }
        if(!format().alpha()) {
            QString msg = tr("Sorry, no alpha channel support for %1...");
            qWarning() << msg.arg(msgThis);
        }
        if(!format().sampleBuffers()) {
            QString msg = tr("Sorry, no multisampling support for %1...");
            qWarning() << msg.arg(msgThis);
        }
    
        // connect primary rendering timer to local callback
        connect(&m_frameTimer, SIGNAL(timeout()), this, SLOT(updateFrame()));
    
        // initialize quadric pointers
        m_quadricPulsar = NULL;
        m_quadricPulsarSpinAxis = NULL;
        m_quadricPulsarSpinAxisTop1 = NULL;
        m_quadricPulsarSpinAxisTop2 = NULL;
        m_quadricPulsarMagneticAxis = NULL;
        m_quadricPulsarConeRim1 = NULL;
        m_quadricPulsarConeRim2 = NULL;
        m_quadricLineOfSight = NULL;
        m_quadricLineOfSightTop = NULL;
        m_quadricLineOfSightCone = NULL;
        m_quadricLineOfSightConeBase = NULL;
    
        // initialize texture pointers
        m_backgroundTexture = 0;
        m_beamTexture = 0;
    
        // initial render timing settings
        m_framesPerSecond = 25;
        m_pulsarRotationDelta = 0.0;
        m_pulsarRotationAngle = 0.0;
    
        m_pulsarRadius = 0.5;
        m_pulsarBeamLength = 3.0f;
        setPulsarSpinAxisInclination(30);
        setPulsarMagneticAxisInclination(20);
        setPulsarBeamAngle(74);
    
    
        // initial spin frequency of 0.5 Hz
        m_pulsarRotationDelta = (360.0 * 0.5) / m_framesPerSecond;
    
        // initial view features
        m_showRotationAxes = false;
        m_cameraInteraction = false;
    
        // initial view settings
        m_mouseAngleH = 0.0;
        m_mouseAngleV = 0.0;
        m_cameraZoom = 80.0;
        m_cameraZoomLBound = 10.0;
        m_cameraZoomUBound = 4500.0;
        m_mouseLastX = 0;
        m_mouseLastY = 0;
    
        // update camera based on settings above
        updateCameraPosition(m_mouseAngleH, m_mouseAngleV, m_cameraZoom);
    }
    
    PulsarAnimationWidget::~PulsarAnimationWidget()
    {
        if(m_quadricPulsar) gluDeleteQuadric(m_quadricPulsar);
        if(m_quadricPulsarConeRim1) gluDeleteQuadric(m_quadricPulsarConeRim1);
        if(m_quadricPulsarConeRim2) gluDeleteQuadric(m_quadricPulsarConeRim2);
        if(m_quadricPulsarSpinAxis) gluDeleteQuadric(m_quadricPulsarSpinAxis);
        if(m_quadricPulsarSpinAxisTop1) gluDeleteQuadric(m_quadricPulsarSpinAxisTop1);
        if(m_quadricPulsarSpinAxisTop2) gluDeleteQuadric(m_quadricPulsarSpinAxisTop2);
        if(m_quadricPulsarMagneticAxis) gluDeleteQuadric(m_quadricPulsarMagneticAxis);
        if(m_quadricLineOfSight) gluDeleteQuadric(m_quadricLineOfSight);
        if(m_quadricLineOfSightTop) gluDeleteQuadric(m_quadricLineOfSightTop);
        if(m_quadricLineOfSightCone) gluDeleteQuadric(m_quadricLineOfSightCone);
        if(m_quadricLineOfSightConeBase) gluDeleteQuadric(m_quadricLineOfSightConeBase);
        if(m_backgroundTexture) deleteTexture(m_backgroundTexture);
        if(m_beamTexture) deleteTexture(m_beamTexture);
    }
    
    void PulsarAnimationWidget::initializeGL()
    {
        glClearColor(0.0, 0.0, 0.0, 0.0);
        glClearDepth(1.0);
        glDepthFunc(GL_LEQUAL);
        glEnable(GL_DEPTH_TEST);
    
        glShadeModel(GL_SMOOTH);
        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
    
        GLfloat LightAmbient[] = {0.3, 0.3, 0.3, 1.0};
        GLfloat LightDiffuse[] = {1.0, 1.0, 1.0, 1.0};
        GLfloat LightSpecular[] = {1.0, 1.0, 1.0, 1.0};
        GLfloat LightPosition[] = {0.0, 0.0, 0.0, 1.0};
        GLfloat spot_direction[] = {0.0, 0.0, 0.0};
    
        glLightfv(GL_LIGHT0, GL_AMBIENT, LightAmbient);
        glLightfv(GL_LIGHT0, GL_DIFFUSE, LightDiffuse);
        glLightfv(GL_LIGHT0, GL_SPECULAR, LightSpecular);
        glLightfv(GL_LIGHT0, GL_POSITION, LightPosition);
        glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 50.0);
        glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, spot_direction);
        glLightf(GL_LIGHT0, GL_SPOT_EXPONENT, 10.0);
    
        glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
        glEnable(GL_LIGHT0);
        //        glEnable(GL_LIGHTING);
    
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glEnable(GL_BLEND);
    
        m_quadricPulsar = gluNewQuadric();
        m_quadricPulsarConeRim1 = gluNewQuadric();
        m_quadricPulsarConeRim2 = gluNewQuadric();
        m_quadricPulsarSpinAxis = gluNewQuadric();
        m_quadricPulsarSpinAxisTop1 = gluNewQuadric();
        m_quadricPulsarSpinAxisTop2 = gluNewQuadric();
        m_quadricPulsarMagneticAxis = gluNewQuadric();
        m_quadricLineOfSight = gluNewQuadric();
        m_quadricLineOfSightTop = gluNewQuadric();
        m_quadricLineOfSightCone = gluNewQuadric();
        m_quadricLineOfSightConeBase = gluNewQuadric();
    
        gluQuadricNormals(m_quadricPulsar, GLU_SMOOTH);
        gluQuadricNormals(m_quadricPulsarConeRim1, GLU_SMOOTH);
        gluQuadricNormals(m_quadricPulsarConeRim2, GLU_SMOOTH);
        gluQuadricNormals(m_quadricPulsarSpinAxis, GLU_SMOOTH);
        gluQuadricNormals(m_quadricPulsarSpinAxisTop1, GLU_SMOOTH);
        gluQuadricNormals(m_quadricPulsarSpinAxisTop2, GLU_SMOOTH);
        gluQuadricNormals(m_quadricPulsarMagneticAxis, GLU_SMOOTH);
        gluQuadricNormals(m_quadricLineOfSight, GLU_SMOOTH);
        gluQuadricNormals(m_quadricLineOfSightTop, GLU_SMOOTH);
        gluQuadricNormals(m_quadricLineOfSightCone, GLU_SMOOTH);
        gluQuadricNormals(m_quadricLineOfSightConeBase, GLU_SMOOTH);
    
        // query max texture size (estimate)
        GLint maxTextureSize;
        glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
    
        // prepare local messages
        QString msgShape = tr("%1 texture shape not quadratic!");
        QString msgPower = tr("%1 texture dimensions not a power of 2!");
        QString msgSize = tr("Maximum texture size exceeded! Scaling down %1 texture to %2x%3...");
    
        // prepare and check background texture
        QImage backgroundTexture(":/textures/resources/texture_background_carina.png");
        if(backgroundTexture.width() != backgroundTexture.height()) {
            qWarning() << msgShape.arg(tr("Background"));
        }
        else {
            double integer = 0.0;
            double fraction = 0.0;
            fraction = modf(log(backgroundTexture.width()) / log(2.0), &integer);
            if(fraction > 0.0) {
                qWarning() << msgPower.arg(tr("Background"));
            }
        }
        if(backgroundTexture.width() > maxTextureSize) {
            qWarning() << msgSize.arg(tr("background").arg(maxTextureSize).arg(maxTextureSize));
            backgroundTexture = backgroundTexture.scaled(maxTextureSize, maxTextureSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
        }
    
        // prepare and check beam texture
        QImage beamTexture(":/textures/resources/texture_beam.png");
        if(beamTexture.width() != beamTexture.height()) {
            qWarning() << msgShape.arg(tr("Beam"));
        }
        else {
            double integer = 0.0;
            double fraction = 0.0;
            fraction = modf(log(beamTexture.width()) / log(2.0), &integer);
            if(fraction > 0.0) {
                qWarning() << msgPower.arg(tr("Beam"));
            }
        }
        if(beamTexture.width() > maxTextureSize) {
            qWarning() << msgSize.arg(tr("beam").arg(maxTextureSize).arg(maxTextureSize));
            beamTexture = beamTexture.scaled(maxTextureSize, maxTextureSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
        }
    
        // bind textures
        m_backgroundTexture = bindTexture(backgroundTexture, GL_TEXTURE_2D, GL_RGBA);
        m_beamTexture = bindTexture(beamTexture, GL_TEXTURE_2D, GL_RGBA);
    
        // use mipmapped textures
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
    }
    
    void PulsarAnimationWidget::resizeGL(int w, int h)
    {
        glViewport(0, 0, w, h);
    
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
    
        gluPerspective(4.5, (GLfloat)w / (GLfloat)h, 0.1, 5000.0);
    
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
    }
    
    void PulsarAnimationWidget::paintGL()
    {
        GLfloat x,y,angle;
    
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
        // initial perspective/camera setup
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
    
        gluLookAt(m_cameraPosX, m_cameraPosY, m_cameraPosZ,
                  0.0, 0.0, 0.0,
                  0.0, 1.0, 0.0);
    
        // save current state (the following is using parallel projection)
        glMatrixMode(GL_PROJECTION);
        glPushMatrix();
        {
            glLoadIdentity();
            glOrtho(0, width(), 0, height(), 0.1, 501.0);
            glMatrixMode(GL_MODELVIEW);
            glPushMatrix();
            {
                glLoadIdentity();
    
                // draw backdrop first so that foreground alpha blending works correctly
                glColor3f(1.0f, 1.0f, 1.0f);
                glTranslatef(0.0, 0.0, -501.0);
                drawTexture(QPointF(0.0, 0.0), m_backgroundTexture);
    
                // restore original state
                glMatrixMode(GL_PROJECTION);
            }
            glPopMatrix();
            glMatrixMode(GL_MODELVIEW);
        }
        glPopMatrix();
    
        // define materials
        // TODO: should be located elsewhere
        static GLfloat no_mat[] = {0.0, 0.0, 0.0, 1.0};
        static GLfloat mat_diffuse[] = {0.5, 0.5, 0.5, 1.0};
        static GLfloat mat_specular[] = {1.0, 1.0, 1.0, 1.0};
        static GLfloat low_shininess[] = {2.5};
    
        glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat);
        glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
        glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
        glMaterialfv(GL_FRONT, GL_SHININESS, low_shininess);
        glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
    
        // let's draw everything as wireframe model
        glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
    
        // draw scence
        glPushMatrix();
        {
            //draw line of sight (observer)
            if(m_showRotationAxes) {
                glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
                glColor3f(0.33f, 0.33f, 1.0f);
                glPushMatrix();
                {
                    glTranslatef(0.0, 0.0, m_pulsarRadius);
                    gluCylinder(m_quadricLineOfSightCone, 0, 0.1, 0.5, 32, 1);
                }
                glPopMatrix();
                glPushMatrix();
                {
                    glTranslatef(0.0, 0.0, m_pulsarRadius + 0.5);
                    gluDisk(m_quadricLineOfSightConeBase, 0, 0.1, 32, 8);
                }
                glPopMatrix();
                glPushMatrix();
                {
                    glTranslatef(0.0, 0.0, m_pulsarRadius + 0.5);
                    gluCylinder(m_quadricLineOfSight, 0.025, 0.025, 3.01, 32, 1);
                }
                glPopMatrix();
                glPushMatrix();
                {
                    glTranslatef(0.0, 0.0, m_pulsarRadius + 3.51);
                    gluDisk(m_quadricLineOfSightTop, 0, 0.025, 32, 8);
                }
                glPopMatrix();
                glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
            }
    
            // draw pulsar
            glPushMatrix();
            {
                glRotatef(-m_pulsarSpinAxisInclination, 1.0, 0.0, 0.0);
                glRotatef(m_pulsarRotationAngle, 0.0, 0.0, 1.0);
    
                // draw spin axis
                if(m_showRotationAxes) {
                    glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
                    glColor3f(1.0f, 1.0f, 1.0f);
                    glPushMatrix();
                    {
                        glTranslatef(0.0, 0.0, -4.0);
                        gluCylinder(m_quadricPulsarSpinAxis, 0.020, 0.020, 8.0, 32, 1);
                    }
                    glPopMatrix();
                    glPushMatrix();
                    {
                        glTranslatef(0.0, 0.0, -4.0);
                        gluDisk(m_quadricPulsarSpinAxisTop1, 0, 0.020, 32, 8);
                    }
                    glPopMatrix();
                    glPushMatrix();
                    {
                        glTranslatef(0.0, 0.0, 4.0);
                        gluDisk(m_quadricPulsarSpinAxisTop2, 0, 0.020, 32, 8);
                    }
                    glPopMatrix();
                    glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
                }
    
                // gives pulsar spherical appearance
                glEnable(GL_LIGHTING);
    
                glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat);
                glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
                gluSphere(m_quadricPulsar, m_pulsarRadius, 32, 32);
    
                glDisable(GL_LIGHTING);
            }
            glPopMatrix();
    
            // first cone (pointing away from camera)
            glPushMatrix();
            {
                glRotatef(-m_pulsarSpinAxisInclination, 1.0, 0.0, 0.0);
    
                glRotatef(m_pulsarRotationAngle - 90.0, 0.0, 0.0, 1.0);
                glRotatef(m_pulsarMagneticAxisInclination, 0.0, 1.0, 0.0);
    
                // draw magnetic axis (for both cones)
                if(m_showRotationAxes) {
                    glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
                    glColor3f(1.0f, 1.0f, 0.0f);
                    glPushMatrix();
                    {
                        glTranslatef(0.0, 0.0, -4.0);
                        gluCylinder(m_quadricPulsarMagneticAxis, 0.020, 0.020, 8.0, 32, 1);
                    }
                    glPopMatrix();
                    glPushMatrix();
                    {
                        glTranslatef(0.0, 0.0, -4.0);
                        gluDisk(m_quadricPulsarSpinAxisTop1, 0, 0.020, 32, 8);
                    }
                    glPopMatrix();
                    glPushMatrix();
                    {
                        glTranslatef(0.0, 0.0, 4.0);
                        gluDisk(m_quadricPulsarSpinAxisTop2, 0, 0.020, 32, 8);
                    }
                    glPopMatrix();
                    glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
                }
    
                glTranslatef(0.0, 0.0, -m_pulsarBeamLength);
    
    
                // draw first cone's outer layer
                glBegin(GL_TRIANGLE_FAN);
                {
                    // Pinnacle of cone is shared vertex for fan, moved up z-axis
                    // to produce a cone instead of a circle
                    glColor4f(1.0f, 1.0f, 0.0f, 0.33f);
                    glVertex3f(0.0f, 0.0f, m_pulsarBeamLength);
    
                    // Loop around in a circle and specify even points along the circle
                    // as the vertices of the triangle fan (32 sections)
                    for(angle = 0.0f; angle < (2.0f*PI); angle += (PI/32.0f))
                    {
                        // Calculate x and y position of the next vertex
                        x = m_pulsarBeamOuterRadius * sin(angle);
                        y = m_pulsarBeamOuterRadius * cos(angle);
    
                        // Specify the next vertex for the triangle fan
                        glVertex2f(x, y);
                    }
                }
                glEnd();
    
                // draw first cone's "base" (textured bottom to visualize beam intensity)
                glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
                glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
    
                // create texture coordinates and enable texturing
                gluQuadricTexture(m_quadricPulsarConeRim1, GL_TRUE);
                glBindTexture(GL_TEXTURE_2D, m_beamTexture);
                glEnable(GL_TEXTURE_2D);
    
                gluDisk(m_quadricPulsarConeRim1, 0, m_pulsarBeamOuterRadius, 32, 1);
    
                // disable texturing
                glDisable(GL_TEXTURE_2D);
    
                glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
            }
            glPopMatrix();
    
            // second cone (pointing towards from camera)
            glPushMatrix();
            {
                glRotatef(180.0, 1.0, 0.0, 0.0);
                glRotatef(-m_pulsarSpinAxisInclination, 1.0, 0.0, 0.0);
    
                glRotatef(-m_pulsarRotationAngle - 90.0, 0.0, 0.0, 1.0);
                glRotatef(m_pulsarMagneticAxisInclination, 0.0, 1.0, 0.0);
    
                glTranslatef(0.0, 0.0, -m_pulsarBeamLength);
    
                // draw second cone's outer layer
                glBegin(GL_TRIANGLE_FAN);
                {
                    // Pinnacle of cone is shared vertex for fan, moved up z-axis
                    // to produce a cone instead of a circle
                    glColor4f(1.0f, 1.0f, 0.0f, 0.33f);
                    glVertex3f(0.0f, 0.0f, m_pulsarBeamLength);
    
                    // Loop around in a circle and specify even points along the circle
                    // as the vertices of the triangle fan (32 sections)
                    for(angle = 0.0f; angle < (2.0f*PI); angle += (PI/32.0f))
                    {
                        // Calculate x and y position of the next vertex
                        x = m_pulsarBeamOuterRadius * sin(angle);
                        y = m_pulsarBeamOuterRadius * cos(angle);
    
                        // Specify the next vertex for the triangle fan
                        glVertex2f(x, y);
                    }
                }
                glEnd();
    
                // draw second cone's "base" (textured bottom to visualize beam intensity)
                glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
                glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
    
                // create texture coordinates and enable texturing
                gluQuadricTexture(m_quadricPulsarConeRim2, GL_TRUE);
                glBindTexture(GL_TEXTURE_2D, m_beamTexture);
                glEnable(GL_TEXTURE_2D);
    
                gluDisk(m_quadricPulsarConeRim2, 0, m_pulsarBeamOuterRadius, 32, 1);
    
                // disable texturing
                glDisable(GL_TEXTURE_2D);
    
                glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
            }
            glPopMatrix();
        }
        glPopMatrix();
    
        glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
    
        // save current state (the following is using parallel projection)
        glMatrixMode(GL_PROJECTION);
        glPushMatrix();
        {
            glLoadIdentity();
            glOrtho(0, width(), 0, height(), 0.1, 501.0);
            glMatrixMode(GL_MODELVIEW);
            glPushMatrix();
            {
                glLoadIdentity();
    
                // draw copyright info last (appears in front of everything)
                glColor4f(1.0f, 1.0f, 1.0f, 0.5f);
                QFont font;
                font.setPointSize(11);
                font.setBold(true);
                font.setFamily("Arial");
                font.setStyleStrategy((QFont::StyleStrategy) (QFont::OpenGLCompatible | QFont::PreferQuality));
                renderText(10, 25, -100, QString::fromLocal8Bit("Copyright © 2009-2010"), font);
                renderText(10, 10, -100, QString::fromLocal8Bit("Max-Planck-Insitut für Gravitationsphysik"), font);
    
                // restore original state
                glMatrixMode(GL_PROJECTION);
            }
            glPopMatrix();
            glMatrixMode(GL_MODELVIEW);
        }
        glPopMatrix();
    }
    
    void PulsarAnimationWidget::runAnimation()
    {
        m_frameTimer.start(qRound(1000.0 / m_framesPerSecond));
    }
    
    void PulsarAnimationWidget::pauseAnimation()
    {
        m_frameTimer.stop();
    }
    
    void PulsarAnimationWidget::stopAnimation()
    {
        m_frameTimer.stop();
        resetParameters();
    
        updateGL();
    }
    
    void PulsarAnimationWidget::resetViewpoint()
    {
        m_mouseAngleH = 0.0;
        m_mouseAngleV = 0.0;
        m_cameraZoom = 80.0;
    
        updateCameraPosition(m_mouseAngleH, m_mouseAngleV, m_cameraZoom);
    }
    
    void PulsarAnimationWidget::updateFrame()
    {
        m_pulsarRotationAngle += m_pulsarRotationDelta;
        if(m_pulsarRotationAngle > 360.0) {
            m_pulsarRotationAngle -= 360.0;
            updatePulseProfile();
        }
    
        updateGL();
    
        emit pulsarAnimationStep(m_pulsarRotationAngle);
    }
    
    void PulsarAnimationWidget::showRotationAxes(bool enabled)
    {
        m_showRotationAxes = enabled;
    
        updateGL();
    }
    
    void PulsarAnimationWidget::mousePressEvent(QMouseEvent *event)
    {
        Q_UNUSED(event);
    
        m_cameraInteraction = true;
        updateGL();
    }
    
    void PulsarAnimationWidget::mouseMoveEvent(QMouseEvent *event)
    {
        Qt::MouseButtons buttons = event->buttons();
        if((buttons & Qt::LeftButton) == Qt::LeftButton) {
            if(m_mouseLastX != 0) {
                m_mouseAngleH += (m_mouseLastX - event->x());
                m_mouseAngleH = m_mouseAngleH < 360 ? m_mouseAngleH : 0;
                m_mouseAngleH = m_mouseAngleH >= 0 ? m_mouseAngleH : 359;
            }
            if(m_mouseLastY != 0) {
                m_mouseAngleV -= (m_mouseLastY - event->y());
                m_mouseAngleV = m_mouseAngleV < 90 ? m_mouseAngleV : 90;
                m_mouseAngleV = m_mouseAngleV > -90 ? m_mouseAngleV : -90;
            }
    
            m_mouseLastX = event->x();
            m_mouseLastY = event->y();
        }
        else if((buttons & Qt::RightButton) == Qt::RightButton) {
            if(m_mouseLastY != 0) {
                m_cameraZoom -= (m_mouseLastY - event->y());
                m_cameraZoom = m_cameraZoom >= m_cameraZoomLBound ? m_cameraZoom : m_cameraZoomLBound;
                m_cameraZoom = m_cameraZoom >= m_cameraZoomUBound ? m_cameraZoomUBound : m_cameraZoom;
            }
    
            m_mouseLastY = event->y();
        }
    
        updateCameraPosition(m_mouseAngleH, m_mouseAngleV, m_cameraZoom);
    }
    
    void PulsarAnimationWidget::mouseReleaseEvent(QMouseEvent *event)
    {
        Q_UNUSED(event);
    
        m_mouseLastX = 0;
        m_mouseLastY = 0;
        m_cameraInteraction = false;
    
        updateGL();
    }
    
    void PulsarAnimationWidget::showEvent(QShowEvent *event)
    {
        Q_UNUSED(event);
    
        // update and propagate pulse profile
        updatePulseProfile();
    }
    
    void PulsarAnimationWidget::updateCameraPosition(const double angleH, const double angleV, const double zoom)
    {
        m_cameraPosX = sin(angleH * deg2rad) * cos(angleV * deg2rad) * zoom;
        m_cameraPosY = sin(angleV * deg2rad) * zoom;
        m_cameraPosZ = cos(angleH * deg2rad) * cos(angleV * deg2rad) * zoom;
    
        updateGL();
    
        qDebug("Camera (x,y,z,+,h,v): %f, %f, %f, %f, %f, %f", m_cameraPosX, m_cameraPosY, m_cameraPosZ, zoom, angleH, angleV);
    }
    
    void PulsarAnimationWidget::setFramePerSecond(const unsigned int fps)
    {
        m_framesPerSecond = fps;
    }
    
    void PulsarAnimationWidget::setPulsarSpinFrequency(const double frequency)
    {
        m_pulsarRotationDelta = (360.0 * frequency) / m_framesPerSecond;
        updatePulseProfile();
    }
    
    void PulsarAnimationWidget::setPulsarSpinAxisInclination(const int degrees)
    {
        m_pulsarSpinAxisInclination = degrees;
        updatePulseProfile();
    
        updateGL();
    }
    
    void PulsarAnimationWidget::setPulsarMagneticAxisInclination(const int degrees)
    {
        m_pulsarMagneticAxisInclination = degrees;
        updatePulseProfile();
    
        updateGL();
    }
    
    void PulsarAnimationWidget::setPulsarBeamAngle(const int degrees)
    {
        double beamTexturePeakCorrectionFactor = 0.83;
        double correctedOuterRadius;
    
        // compute visual radius
        m_pulsarBeamOuterRadius = tan(deg2rad * degrees * 0.5f) * m_pulsarBeamLength;
    
        // compute corrected angle for pulse profile
        correctedOuterRadius = m_pulsarBeamOuterRadius * beamTexturePeakCorrectionFactor;
        m_pulsarBeamAngle = 2 * atan(correctedOuterRadius / m_pulsarBeamLength) * 180.0/PI;
    
        updatePulseProfile();
    
        updateGL();
    }
    
    void PulsarAnimationWidget::resetParameters()
    {
        m_pulsarRotationAngle = 0.0;
        updatePulseProfile();
    
        emit pulsarAnimationStep(m_pulsarRotationAngle);
    }
    
    void PulsarAnimationWidget::updatePulseProfile()
    {
        // prepare parameters (e.g. convert to radians where necessary)
        const double    deltaPhiRot     = deg2rad * 1.0;
        const double    gamma           = deg2rad * m_pulsarSpinAxisInclination;
        const double    alpha           = deg2rad * m_pulsarMagneticAxisInclination;
        const double    beta            = -alpha + gamma;
        const double    rho             = deg2rad * m_pulsarBeamAngle * 0.5;
        const double    gaussProfile    = 0.04;
    
        const double    t1              = pow(sin(rho*0.5), 2) - pow(sin(beta*0.5), 2);
        const double    t2              = sin(alpha) * sin(beta+alpha);
        const double    W               = 2 * asin(sqrt( t1 / t2 ));
    
        qDebug("alpha: %f, beta: %f, rho: %f, t1: %f, t2: %f, W: %f", alpha*180/PI, beta*180/PI, rho*180/PI, t1*180/PI, t2*180/PI, W*180/PI);
    
        for(int x = 0; x < 360; ++x) {
            // update pulsar rotation angle
            const double phiRot = x * deltaPhiRot;
    
            // determine and store pulse amplitude
            if(!isnan(W)){
                m_pulseProfile[x] = exp(-2.0 * pow(phiRot - W, 2) / gaussProfile) + exp(-2.0 * pow(phiRot + W - 2.0 * PI, 2) / gaussProfile);
            }
            else {
                m_pulseProfile[x] = 0.0;
            }
        }
    
        // propagate new profile
        emit pulseProfileUpdated(m_pulseProfile);
    }