/****************************************************************************** * 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_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); } // initialize quadric pointers m_quadricVirgoh = NULL; m_quadricVirgov = NULL; m_quadricEarth = NULL; m_quadricLLOh = NULL; m_quadricLLOv = NULL; m_quadricSourcePlane = NULL; m_quadricSourcePlaneNormal = NULL; m_quadricSourcePlaneNormalCap = NULL; m_quadricLHOv = NULL; m_quadricLHOh = NULL; // initialize texture pointers m_beamTexture = 0; // initial parameters (have to match GUI!) m_LHOAngle = 180; m_LLOAngle = -100; m_VirgoAngle = 165; m_sourceIota = 0.0; m_sourceInclination = 0; m_earthRadius = 3.0; // initial view features m_cameraInteraction = false; // initial view settings m_mouseAngleH = 0.0; m_mouseAngleV = 0.0; m_cameraZoom = 100.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_quadricVirgoh) gluDeleteQuadric(m_quadricVirgoh); if(m_quadricVirgov) gluDeleteQuadric(m_quadricVirgov); if(m_quadricEarth) gluDeleteQuadric(m_quadricEarth); if(m_quadricLLOh) gluDeleteQuadric(m_quadricLLOh); if(m_quadricLLOv) gluDeleteQuadric(m_quadricLLOv); if(m_quadricSourcePlane) gluDeleteQuadric(m_quadricSourcePlane); if(m_quadricSourcePlaneNormal) gluDeleteQuadric(m_quadricSourcePlaneNormal); if(m_quadricSourcePlaneNormalCap) gluDeleteQuadric(m_quadricSourcePlaneNormalCap); if(m_quadricLHOv) gluDeleteQuadric(m_quadricLHOv); if(m_quadricLHOh) gluDeleteQuadric(m_quadricLHOh); 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_quadricVirgoh = gluNewQuadric(); m_quadricVirgov = gluNewQuadric(); m_quadricEarth = gluNewQuadric(); m_quadricLLOh = gluNewQuadric(); m_quadricLLOv = gluNewQuadric(); m_quadricSourcePlane = gluNewQuadric(); m_quadricSourcePlaneNormal = gluNewQuadric(); m_quadricSourcePlaneNormalCap = gluNewQuadric(); m_quadricLHOv = gluNewQuadric(); m_quadricLHOh = gluNewQuadric(); gluQuadricNormals(m_quadricVirgoh, GLU_SMOOTH); gluQuadricNormals(m_quadricVirgov, GLU_SMOOTH); gluQuadricNormals(m_quadricEarth, GLU_SMOOTH); gluQuadricNormals(m_quadricLLOh, GLU_SMOOTH); gluQuadricNormals(m_quadricLLOv, GLU_SMOOTH); gluQuadricNormals(m_quadricSourcePlane, GLU_SMOOTH); gluQuadricNormals(m_quadricSourcePlaneNormal, GLU_SMOOTH); gluQuadricNormals(m_quadricSourcePlaneNormalCap, GLU_SMOOTH); gluQuadricNormals(m_quadricLHOv, GLU_SMOOTH); gluQuadricNormals(m_quadricLHOh, GLU_SMOOTH); // query max texture size (estimate) GLint maxTextureSize; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); // prepare and check beam texture QImage beamTexture(":/textures/resources/World-Map-7.jpg"); // bind textures 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.0, 0.0, 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(1.0, 1.0, 1.0f); glTranslatef(0.0, 0.0, -501.0); // restore original state glMatrixMode(GL_PROJECTION); } glPopMatrix(); glMatrixMode(GL_MODELVIEW); } glPopMatrix(); // draw earth glPushMatrix(); { glRotatef(130, 0.0, 1.0, 0.0); glPushMatrix(); { glRotatef(90, 1.0, 0.0, 0.0); glRotatef(180, 1.0, 1.0, 0.0); // create texture coordinates and enable texturing gluQuadricTexture(m_quadricEarth, GL_TRUE); glBindTexture(GL_TEXTURE_2D, m_beamTexture); glEnable(GL_TEXTURE_2D); gluSphere(m_quadricEarth, m_earthRadius, 32, 32); } glPopMatrix(); } glPopMatrix(); // draw LHO glPushMatrix(); { glColor3f(1.0f, 1.0f, 1.0f); glRotatef(-81, 1.0, 1.0, 0.0); glTranslatef(0.0, 0.0, m_earthRadius); glRotatef(m_LHOAngle, 0.0, 0.0, 1.0); glPushMatrix(); { glRotatef(90, 0.0, 1.0, 0.0); gluCylinder(m_quadricLHOh, 0.010, 0.010, 0.20, 32, 1); } glPopMatrix(); glPushMatrix(); { glRotatef(90, 0.0, 1.0, 0.0); glRotatef(90, 1.0, 0.0, 0.0); glColor3f(0.5f, 0.5f, 1.0f); gluCylinder(m_quadricLHOv, 0.010, 0.010, 0.20, 32, 1); } glPopMatrix(); } glPopMatrix(); // draw LLO glPushMatrix(); { glColor3f(1.0f, 1.0f, 1.0f); glRotatef(-2, 0.0, 1.0, 0.0); glRotatef(5, 1.0, 0.0, 0.0); glRotatef(-60, 1.0, 1.0, 0.0); glTranslatef(0.0, 0.0, m_earthRadius); glRotatef(m_LLOAngle, 0.0, 0.0, 1.0); glPushMatrix(); { glRotatef(90, 0.0, 1.0, 0.0); gluCylinder(m_quadricLLOh, 0.010, 0.010, 0.20, 32, 1); } glPopMatrix(); glPushMatrix(); { glRotatef(90, 0.0, 1.0, 0.0); glRotatef(90, 1.0, 0.0, 0.0); glColor3f(1.0f, 0.5f, 0.5f); gluCylinder(m_quadricLLOv, 0.010, 0.010, 0.20, 32, 1); } glPopMatrix(); } glPopMatrix(); // draw Virgo glPushMatrix(); { glColor3f(1.0f, 1.0f, 1.0f); glRotatef(50, 0.0, 1.0, 0.0); glRotatef(-43, 1.0, 0.0, 0.0); glTranslatef(0.0, 0.0, m_earthRadius); glRotatef(m_VirgoAngle, 0.0, 0.0, 1.0); glPushMatrix(); { glRotatef(90, 0.0, 1.0, 0.0); gluCylinder(m_quadricVirgoh, 0.010, 0.010, 0.20, 32, 1); } glPopMatrix(); glPushMatrix(); { glRotatef(90, 0.0, 1.0, 0.0); glRotatef(90, 1.0, 0.0, 0.0); glColor3f(0.5f, 1.0f, 0.5f); gluCylinder(m_quadricVirgov, 0.010, 0.010, 0.20, 32, 1); } glPopMatrix(); } glPopMatrix(); // draw source glPushMatrix(); { glTranslatef(3.0, 3.0, -3.0); glRotatef(m_sourceInclination, 0.0, 1.0, 0.0); glRotatef(-45, 0.0, 1.0, 0.0); glRotatef(90, 1.0, 0.0, 0.0); glPushMatrix(); { glRotatef(m_sourceIota, 1.0, 0.0, 0.0); glRotatef(-45, 1.0, 0.0, 0.0); // draw source plane normal glColor3f(1.0f, 0.0f, 0.0f); glPushMatrix(); { glTranslatef(0.0, 0.0, -1.0); gluCylinder(m_quadricSourcePlaneNormal, 0.020, 0.020, 1.5, 32, 1); } glPopMatrix(); glPushMatrix(); { glTranslatef(0.0, 0.0, -1.0); gluDisk(m_quadricSourcePlaneNormalCap, 0, 0.020, 32, 8); } glPopMatrix(); glPushMatrix(); { glTranslatef(0.0, 0.0, 0.5f); glBegin(GL_TRIANGLE_FAN); { // Pinnacle of cone is shared vertex for fan, moved up z-axis // to produce a cone instead of a circle glVertex3f(0.0f, 0.0f, 0.5f); // 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 = 0.1f * sin(angle); y = 0.1f * cos(angle); // Specify the next vertex for the triangle fan glVertex2f(x, y); } } glEnd(); } glPopMatrix(); // draw disc glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, translucent); glEnable(GL_LIGHTING); glPushMatrix(); { gluDisk(m_quadricSourcePlane, 0, 1.0, 64, 1); } glPopMatrix(); glDisable(GL_LIGHTING); } glPopMatrix(); } glPopMatrix(); // 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("Copyright %1 2017").arg(QChar(0x00A9)), font); renderText(10, 10, -100, tr("Max Planck Institute for Gravitational Physics"), font); // restore original state glMatrixMode(GL_PROJECTION); } glPopMatrix(); glMatrixMode(GL_MODELVIEW); } glPopMatrix(); } 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::setSourceInclination(const double degrees) { m_sourceInclination = degrees; updatePulseProfile(); updateGL(); } void PulsarAnimationWidget::setLHOAngle(const double degrees) { m_LHOAngle = degrees + 180; updatePulseProfile(); updateGL(); } void PulsarAnimationWidget::setLLOAngle(const double degrees) { m_LLOAngle = degrees - 100; updatePulseProfile(); updateGL(); } void PulsarAnimationWidget::setVirgoAngle(const int degrees) { m_VirgoAngle = degrees + 165; updatePulseProfile(); updateGL(); } void PulsarAnimationWidget::setSourceIota(const int degrees) { m_sourceIota = degrees; updatePulseProfile(); updateGL(); } void PulsarAnimationWidget::getCameraPosition(double& cameraAngleH, double& cameraAngleV, double& cameraZoom) { cameraAngleH = m_mouseAngleH; cameraAngleV = m_mouseAngleV; cameraZoom = m_cameraZoom; } void PulsarAnimationWidget::resetCameraPosition(const double angleH, const double angleV, const double zoom) { m_mouseAngleH = angleH; m_mouseAngleV = angleV; m_cameraZoom = zoom; updateCameraPosition(m_mouseAngleH, m_mouseAngleV, m_cameraZoom); } void PulsarAnimationWidget::updatePulseProfile() { for(int x = 0; x < 360; ++x) { m_pulseProfile[x] = 0; } // propagate new profile emit pulseProfileUpdated(m_pulseProfile); }