/****************************************************************************** * 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 . * * * ******************************************************************************/ #include "pulsaranimationwidget.h" 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_quadricCompanionOrbitPlane = NULL; m_quadricCompanion = NULL; m_quadricPulsarOrbitPlane = NULL; m_quadricPulsar = NULL; m_quadricPulsarCone1Shell = NULL; m_quadricPulsarCone2Shell = NULL; m_quadricPulsarSpinAxis = NULL; m_quadricPulsarSpinAxisTop1 = NULL; m_quadricPulsarSpinAxisTop2 = NULL; m_quadricPulsarMagneticAxis = NULL; // initialize texture pointers m_backgroundTexture = 0; m_beamTexture = 0; // initial render timing settings m_framesPerSecond = 25; m_pulsarRotationDelta = 0.0; m_orbitRotationDelta = 0.0; m_pulsarRotationAngle = 0.0; m_orbitRotationAngle = 0.0; // initial binary system parameters (have to match GUI!) m_pulsarMass = 1.4; m_pulsarRadius = 1.0; m_pulsarSpinAxisInclination = 0.0; m_pulsarMagneticAxisInclination = 60.0; m_pulsarSemiMajorAxis = 5.0; // initial spin frequency of 0.5 Hz m_pulsarRotationDelta = (360.0 * 0.5) / m_framesPerSecond; // beam properties (keep this order!) m_pulsarBeamLength = 3.0f; setPulsarBeamAngle(30); // initial companion is "Neutron Star" m_companionMass = 1.4; m_companionSemiMajorAxis = (m_pulsarMass/m_companionMass) * m_pulsarSemiMajorAxis; // update orbital period (based on settings above) updateOrbitPeriod(); // initial view features m_showOrbits = false; m_showRotationAxes = false; m_cameraInteraction = false; // initial view settings m_mouseAngleH = 90.0; m_mouseAngleV = 30.0; m_cameraZoom = 150.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_quadricCompanionOrbitPlane) gluDeleteQuadric(m_quadricCompanionOrbitPlane); if(m_quadricCompanion) gluDeleteQuadric(m_quadricCompanion); if(m_quadricPulsarOrbitPlane) gluDeleteQuadric(m_quadricPulsarOrbitPlane); if(m_quadricPulsar) gluDeleteQuadric(m_quadricPulsar); if(m_quadricPulsarCone1Shell) gluDeleteQuadric(m_quadricPulsarCone1Shell); if(m_quadricPulsarCone2Shell) gluDeleteQuadric(m_quadricPulsarCone2Shell); 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_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, 3.0, 1.0}; GLfloat spot_direction[] = {0.0, 0.0, -1.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); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); m_quadricCompanionOrbitPlane = gluNewQuadric(); m_quadricCompanion = gluNewQuadric(); m_quadricPulsarOrbitPlane = gluNewQuadric(); m_quadricPulsar = gluNewQuadric(); m_quadricPulsarCone1Shell = gluNewQuadric(); m_quadricPulsarCone2Shell = gluNewQuadric(); m_quadricPulsarSpinAxis = gluNewQuadric(); m_quadricPulsarSpinAxisTop1 = gluNewQuadric(); m_quadricPulsarSpinAxisTop2 = gluNewQuadric(); m_quadricPulsarMagneticAxis = gluNewQuadric(); gluQuadricNormals(m_quadricCompanionOrbitPlane, GLU_SMOOTH); gluQuadricNormals(m_quadricCompanion, GLU_SMOOTH); gluQuadricNormals(m_quadricPulsarOrbitPlane, GLU_SMOOTH); gluQuadricNormals(m_quadricPulsar, GLU_SMOOTH); gluQuadricNormals(m_quadricPulsarCone1Shell, GLU_SMOOTH); gluQuadricNormals(m_quadricPulsarCone2Shell, GLU_SMOOTH); gluQuadricNormals(m_quadricPulsarSpinAxis, GLU_SMOOTH); gluQuadricNormals(m_quadricPulsarSpinAxisTop1, GLU_SMOOTH); gluQuadricNormals(m_quadricPulsarSpinAxisTop2, GLU_SMOOTH); gluQuadricNormals(m_quadricPulsarMagneticAxis, 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); // 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}; static GLfloat translucent[] = {1.0, 1.0, 1.0, 0.33}; 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); 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 (independent parallel projection) glColor3f(0.5, 0.5, 0.5f); 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(); // draw companion glPushMatrix(); { glTranslatef(sin((m_orbitRotationAngle + 180.0) * deg2rad) * m_companionSemiMajorAxis, 0.0, cos((m_orbitRotationAngle + 180.0) * deg2rad) * m_companionSemiMajorAxis); // draw, with proper lighting glEnable(GL_LIGHTING); gluSphere(m_quadricCompanion, 1.0, 32, 32); glDisable(GL_LIGHTING); } glPopMatrix(); // enable wireframe mode glPolygonMode(GL_FRONT_AND_BACK,GL_LINE); // draw pulsar glPushMatrix(); { glTranslatef(sin(m_orbitRotationAngle * deg2rad) * m_pulsarSemiMajorAxis, 0.0, cos(m_orbitRotationAngle * deg2rad) * m_pulsarSemiMajorAxis); glPushMatrix(); { glRotatef(m_pulsarSpinAxisInclination, 0.0, 0.0, 1.0); glRotatef(m_pulsarRotationAngle, 0.0, 1.0, 0.0); glRotatef(90, 0.0, 0.0, 0.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); } // draw sphere, with proper lighting 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 glPushMatrix(); { glRotatef(90.0, 1.0, 0.0, 0.0); glRotatef(m_pulsarSpinAxisInclination, 0.0, 1.0, 0.0); glRotatef(-m_pulsarRotationAngle - 90.0, 0.0, 0.0, 1.0); glRotatef(-m_pulsarMagneticAxisInclination, 1.0, 0.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 + m_pulsarRadius + 0.1)); // 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_quadricPulsarCone1Shell, GL_TRUE); glBindTexture(GL_TEXTURE_2D, m_beamTexture); glEnable(GL_TEXTURE_2D); gluDisk(m_quadricPulsarCone1Shell, 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(-90.0, 1.0, 0.0, 0.0); glRotatef(-m_pulsarSpinAxisInclination, 0.0, 1.0, 0.0); glRotatef(m_pulsarRotationAngle - 90.0, 0.0, 0.0, 1.0); glRotatef(m_pulsarMagneticAxisInclination, 1.0, 0.0, 0.0); glTranslatef(0.0, 0.0, -(m_pulsarBeamLength + m_pulsarRadius + 0.1)); // 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_quadricPulsarCone2Shell, GL_TRUE); glBindTexture(GL_TEXTURE_2D, m_beamTexture); glEnable(GL_TEXTURE_2D); gluDisk(m_quadricPulsarCone2Shell, 0, m_pulsarBeamOuterRadius, 32, 1); // disable texturing glDisable(GL_TEXTURE_2D); } glPopMatrix(); } glPopMatrix(); // back to solid rendering glPolygonMode(GL_FRONT_AND_BACK,GL_FILL); // draw orbital planes if(m_cameraInteraction || m_showOrbits) { glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, translucent); glEnable(GL_LIGHTING); static double sizeOffset = 0.25; // companion's plane (only if not identical with pulsar's) if(m_companionSemiMajorAxis != m_pulsarSemiMajorAxis) { glPushMatrix(); { glRotatef(90.0, 1.0, 0.0, 0.0); // separate them slightly in case of overlap // based on ugly mass diff check (no collision detection) // single known problematic pair: (m_c=1.4, m_p=1.5) if((int)(10*(m_pulsarMass-m_companionMass)+0.5) == 1){ glTranslatef(0.0, 0.0, 0.01); } gluDisk(m_quadricCompanionOrbitPlane, m_companionSemiMajorAxis - sizeOffset, m_companionSemiMajorAxis + sizeOffset, 64, 1); } glPopMatrix(); } // pulsar's plane glPushMatrix(); { glRotatef(90.0, 1.0, 0.0, 0.0); gluDisk(m_quadricPulsarOrbitPlane, m_pulsarSemiMajorAxis - sizeOffset, m_pulsarSemiMajorAxis + sizeOffset, 64, 1); } glPopMatrix(); glDisable(GL_LIGHTING); } // 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-2011"), 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::updateFrame() { m_pulsarRotationAngle += m_pulsarRotationDelta; if(m_pulsarRotationAngle > 360.0) { m_pulsarRotationAngle -= 360.0; updatePulseProfile(); } m_orbitRotationAngle += m_orbitRotationDelta; if(m_orbitRotationAngle > 360.0) { m_orbitRotationAngle -= 360.0; } updateGL(); emit pulsarAnimationStep(m_pulsarRotationAngle); } void PulsarAnimationWidget::showOrbits(bool enabled) { m_showOrbits = enabled; updateGL(); } 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; updatePulseProfile(); updateGL(); } void PulsarAnimationWidget::setFramePerSecond(const unsigned int fps) { m_framesPerSecond = fps; } void PulsarAnimationWidget::setPulsarSemiMajorAxis(const double length) { m_pulsarSemiMajorAxis = length; m_companionSemiMajorAxis = (m_pulsarMass/m_companionMass) * m_pulsarSemiMajorAxis; updateOrbitPeriod(); updatePulseProfile(); updateGL(); } void PulsarAnimationWidget::setCompanionMass(const double mass) { m_companionMass = mass; updateOrbitRadii(); updatePulseProfile(); updateGL(); } void PulsarAnimationWidget::setPulsarMass(const double mass) { m_pulsarMass = mass; updateOrbitRadii(); updatePulseProfile(); updateGL(); } 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::updateOrbitPeriod() { m_orbitalPeriod = 3.1553e7 * sqrt( (pow(m_pulsarSemiMajorAxis, 3.0) * pow(m_pulsarMass+m_companionMass, 2.0)) / pow(m_companionMass, 3.0) ); // visual correction factor (increase orbital momentum) double visualCorrection = 1e-8; m_orbitRotationDelta = (360.0 / (m_orbitalPeriod*visualCorrection)) / m_framesPerSecond; } void PulsarAnimationWidget::updateOrbitRadii() { m_pulsarSemiMajorAxis = 1.0015e-5 * pow( (pow(m_orbitalPeriod, 2.0) * pow(m_companionMass, 3.0)) / pow(m_pulsarMass+m_companionMass, 2.0), 1.0/3.0 ); m_companionSemiMajorAxis = (m_pulsarMass/m_companionMass) * m_pulsarSemiMajorAxis; emit pulsarSemiMajorAxisUpdated(m_pulsarSemiMajorAxis); } void PulsarAnimationWidget::resetParameters() { m_pulsarRotationAngle = 0.0; m_orbitRotationAngle = 0.0; updatePulseProfile(); emit pulsarAnimationStep(m_pulsarRotationAngle); } void PulsarAnimationWidget::updatePulseProfile() { // prepare parameters (e.g. convert to radians where necessary) const double i = deg2rad * m_pulsarSpinAxisInclination; const double y = deg2rad * m_pulsarMagneticAxisInclination; double phiOrb = deg2rad * (m_orbitRotationAngle + 90.0); const double deltaPhiRot = deg2rad * 1.0; const double deltaPhiOrb = deg2rad * deltaPhiRot * m_orbitRotationDelta / m_pulsarRotationDelta; const double rp = m_pulsarSemiMajorAxis; const double xk = -m_cameraPosZ; const double yk = -m_cameraPosX; const double zk = m_cameraPosY; const double cam = pow(xk, 2.0) + pow(yk, 2.0) + pow(zk, 2.0); const double alpha = deg2rad * (90.0 - m_mouseAngleH); const double delta = deg2rad * m_mouseAngleV; const double gaussProfile = 0.012337; for(int x = 0; x < 360; ++x) { // determine angle between pulsar's magnetic axis and line of sight phiOrb += deltaPhiOrb; const double phiRot = x * deltaPhiRot; double a = -sin(y) * sin(phiRot) * (xk + rp * cos(phiOrb)) \ + (cos(i) * sin(y) * cos(phiRot) + sin(i) * cos(y)) * (yk + rp * sin(phiOrb)) \ - (sin(i) * sin(y) * cos(phiRot) - cos(i) * cos(y)) * zk; double b = sqrt(pow(rp,2.0) + cam - (2.0 * sqrt(cam) * rp * cos(delta) * sin(alpha + phiOrb))); // determine and store pulse amplitude m_pulseProfile[x] = exp(-2.0 * (1.0 - fabs(a/b)) / gaussProfile); } // propagate new profile emit pulseProfileUpdated(m_pulseProfile); }