/** * @brief Blits a sprite interpolating, position is relative to the player. * * Since position is in "game coordinates" it is subject to all * sorts of position transformations. * * Interpolation is: sa*inter + sb*1.-inter) * * @param sa Sprite A to blit. * @param sb Sprite B to blit. * @param inter Amount to interpolate. * @param bx X position of the texture relative to the player. * @param by Y position of the texture relative to the player. * @param sx X position of the sprite to use. * @param sy Y position of the sprite to use. * @param c Colour to use (modifies texture colour). */ void gl_blitSpriteInterpolateScale( const glTexture* sa, const glTexture *sb, double inter, const double bx, const double by, double scalew, double scaleh, const int sx, const int sy, const glColour *c ) { double x,y, w,h, tx,ty, z; /* Translate coords. */ gl_gameToScreenCoords( &x, &y, bx - scalew * sa->sw/2., by - scaleh * sa->sh/2. ); /* Scaled sprite dimensions. */ z = cam_getZoom(); w = sa->sw*z*scalew; h = sa->sh*z*scaleh; /* check if inbounds */ if ((x < -w) || (x > SCREEN_W+w) || (y < -h) || (y > SCREEN_H+h)) return; /* texture coords */ tx = sa->sw*(double)(sx)/sa->rw; ty = sa->sh*(sa->sy-(double)sy-1)/sa->rh; gl_blitTextureInterpolate( sa, sb, inter, x, y, w, h, tx, ty, sa->srw, sa->srh, c ); }
/** * @brief Blits a sprite, position is relative to the player. * * Since position is in "game coordinates" it is subject to all * sorts of position transformations. * * @param sprite Sprite to blit. * @param bx X position of the texture relative to the player. * @param by Y position of the texture relative to the player. * @param sx X position of the sprite to use. * @param sy Y position of the sprite to use. * @param c Colour to use (modifies texture colour). */ void gl_blitSprite( const glTexture* sprite, const double bx, const double by, const int sx, const int sy, const glColour* c ) { double x,y, w,h, tx,ty, z; /* Translate coords. */ z = cam_getZoom(); gl_gameToScreenCoords( &x, &y, bx - sprite->sw/2., by - sprite->sh/2. ); /* Scaled sprite dimensions. */ w = sprite->sw*z; h = sprite->sh*z; /* check if inbounds */ if ((x < -w) || (x > SCREEN_W+w) || (y < -h) || (y > SCREEN_H+h)) return; /* texture coords */ tx = sprite->sw*(double)(sx)/sprite->rw; ty = sprite->sh*(sprite->sy-(double)sy-1)/sprite->rh; gl_blitTexture( sprite, x, y, w, h, tx, ty, sprite->srw, sprite->srh, c ); }
/** * @brief Updates the camera zoom. */ static void cam_updatePilotZoom( Pilot *follow, Pilot *target, double dt ) { double d, x,y, z,tz, dx, dy; double zfar, znear; double c; /* Must have auto zoom enabled. */ if (conf.zoom_manual) return; /* Minimum depends on velocity normally. * * w*h = A, cte area constant * w/h = K, cte proportion constant * d^2 = A, cte geometric longitud * * A_v = A*(1+v/d) area of view is based on speed * A_v / A = (1 + v/d) * * z = A / A_v = 1. / (1 + v/d) */ d = sqrt(SCREEN_W*SCREEN_H); znear = MIN( conf.zoom_near, 1. / (0.8 + VMOD(follow->solid->vel)/d) ); /* Maximum is limited by nebulae. */ if (cur_system->nebu_density > 0.) { c = MIN( SCREEN_W, SCREEN_H ) / 2; zfar = CLAMP( conf.zoom_far, conf.zoom_near, c / nebu_getSightRadius() ); } else zfar = conf.zoom_far; znear = MAX( znear, zfar ); /* * Set Zoom to pilot target. */ z = cam_getZoom(); if (target != NULL) { /* Get current relative target position. */ gui_getOffset( &x, &y ); x += target->solid->pos.x - follow->solid->pos.x; y += target->solid->pos.y - follow->solid->pos.y; /* Get distance ratio. */ dx = (SCREEN_W/2.) / (FABS(x) + 2*target->ship->gfx_space->sw); dy = (SCREEN_H/2.) / (FABS(y) + 2*target->ship->gfx_space->sh); /* Get zoom. */ tz = MIN( dx, dy ); } else tz = znear; /* Aim at in. */ /* Gradually zoom in/out. */ d = CLAMP(-conf.zoom_speed, conf.zoom_speed, tz - z); d *= dt / dt_mod; /* Remove dt dependence. */ if (d < 0) /** Speed up if needed. */ d *= 2.; camera_Z = CLAMP( zfar, znear, z + d); }
/** * @brief Converts screen coordinates to ingame coordinates. * * @param[out] nx New ingame X coord. * @param[out] ny New ingame Y coord. * @param bx Screen X coord to translate. * @param by Screen Y coord to translate. */ void gl_screenToGameCoords( double *nx, double *ny, int bx, int by ) { double cx,cy, gx,gy, z; /* Get parameters. */ cam_getPos( &cx, &cy ); z = cam_getZoom(); gui_getOffset( &gx, &gy ); /* calculate position - we'll use relative coords to player */ *nx = (bx - SCREEN_W/2. - gx) / z + cx; *ny = (by - SCREEN_H/2. - gy) / z + cy; }
/** * @brief Converts ingame coordinates to screen coordinates. * * @param[out] nx New screen X coord. * @param[out] ny New screen Y coord. * @param bx Game X coord to translate. * @param by Game Y coord to translate. */ void gl_gameToScreenCoords( double *nx, double *ny, double bx, double by ) { double cx,cy, gx,gy, z; /* Get parameters. */ cam_getPos( &cx, &cy ); z = cam_getZoom(); gui_getOffset( &gx, &gy ); /* calculate position - we'll use relative coords to player */ *nx = (bx - cx) * z + gx + SCREEN_W/2.; *ny = (by - cy) * z + gy + SCREEN_H/2.; }
/** * @brief Renders the background images. */ static void background_renderImages( background_image_t *bkg_arr ) { int i; background_image_t *bkg; double px,py, x,y, xs,ys, z; /* Must have an image array created. */ if (bkg_arr == NULL) return; /* Render images in order. */ for (i=0; i<array_size(bkg_arr); i++) { bkg = &bkg_arr[i]; cam_getPos( &px, &py ); x = px + (bkg->x - px) * bkg->move - bkg->scale*bkg->image->sw/2.; y = py + (bkg->y - py) * bkg->move - bkg->scale*bkg->image->sh/2.; gl_gameToScreenCoords( &xs, &ys, x, y ); z = cam_getZoom(); z *= bkg->scale; gl_blitScale( bkg->image, xs, ys, z*bkg->image->sw, z*bkg->image->sh, &bkg->col ); } }
/** * @brief Renders the nebula overlay (hides what player can't see). * * @param dt Current delta tick. */ void nebu_renderOverlay( const double dt ) { (void) dt; double gx, gy; double ox, oy; double z; double sx, sy; /* Get GUI offsets. */ gui_getOffset( &gx, &gy ); /* Get zoom. */ z = cam_getZoom(); /* * Renders the puffs */ nebu_renderPuffs( 0 ); /* Prepare the matrix */ ox = gx; oy = gy; spfx_getShake( &sx, &sy ); ox += sx; oy += sy; gl_matrixPush(); gl_matrixTranslate( SCREEN_W/2.+ox, SCREEN_H/2.+oy ); gl_matrixScale( z, z ); /* * Mask for area player can still see (partially) */ glShadeModel(GL_SMOOTH); gl_vboActivateOffset( nebu_vboOverlay, GL_VERTEX_ARRAY, 0, 2, GL_FLOAT, 0 ); gl_vboActivateOffset( nebu_vboOverlay, GL_COLOR_ARRAY, sizeof(GLfloat)*2*18, 4, GL_FLOAT, 0 ); glDrawArrays( GL_TRIANGLE_FAN, 0, 18 ); /* * Solid nebula for areas the player can't see */ glShadeModel(GL_FLAT); /* Colour is shared. */ gl_vboActivateOffset( nebu_vboOverlay, GL_COLOR_ARRAY, sizeof(GLfloat)*((2+4)*18 + 2*28), 4, GL_FLOAT, 0 ); /* Top left. */ gl_vboActivateOffset( nebu_vboOverlay, GL_VERTEX_ARRAY, sizeof(GLfloat)*((2+4)*18 + 0*2*7), 2, GL_FLOAT, 0 ); glDrawArrays( GL_TRIANGLE_FAN, 0, 7 ); /* Top right. */ gl_vboActivateOffset( nebu_vboOverlay, GL_VERTEX_ARRAY, sizeof(GLfloat)*((2+4)*18 + 1*2*7), 2, GL_FLOAT, 0 ); glDrawArrays( GL_TRIANGLE_FAN, 0, 7 ); /* Bottom right. */ gl_vboActivateOffset( nebu_vboOverlay, GL_VERTEX_ARRAY, sizeof(GLfloat)*((2+4)*18 + 2*2*7), 2, GL_FLOAT, 0 ); glDrawArrays( GL_TRIANGLE_FAN, 0, 7 ); /* Bottom left. */ gl_vboActivateOffset( nebu_vboOverlay, GL_VERTEX_ARRAY, sizeof(GLfloat)*((2+4)*18 + 3*2*7), 2, GL_FLOAT, 0 ); glDrawArrays( GL_TRIANGLE_FAN, 0, 7 ); gl_vboDeactivate(); gl_matrixPop(); /* Reset puff movement. */ puff_x = 0.; puff_y = 0.; gl_checkErr(); }
/** * @brief Renders the starry background. * * @param dt Current delta tick. */ void background_renderStars( const double dt ) { (void) dt; unsigned int i; GLfloat hh, hw, h, w; GLfloat x, y, m, b; GLfloat brightness; double z; double sx, sy; int shade_mode; int j, n; /* * gprof claims it's the slowest thing in the game! */ /* Do some scaling for now. */ z = cam_getZoom(); z = 1. * (1. - conf.zoom_stars) + z * conf.zoom_stars; gl_matrixPush(); gl_matrixTranslate( SCREEN_W/2., SCREEN_H/2. ); gl_matrixScale( z, z ); if (!paused && (player.p != NULL) && !player_isFlag(PLAYER_DESTROYED) && !player_isFlag(PLAYER_CREATING)) { /* update position */ /* Calculate some dimensions. */ w = (SCREEN_W + 2.*STAR_BUF); w += conf.zoom_stars * (w / conf.zoom_far - 1.); h = (SCREEN_H + 2.*STAR_BUF); h += conf.zoom_stars * (h / conf.zoom_far - 1.); hw = w/2.; hh = h/2.; if ((star_x > SCREEN_W) || (star_y > SCREEN_H)) { sx = ceil( star_x / SCREEN_W ); sy = ceil( star_y / SCREEN_H ); n = MAX( sx, sy ); star_x /= (double)n; star_y /= (double)n; } else n = 1; /* Calculate new star positions. */ for (j=0; j < n; j++) { for (i=0; i < nstars; i++) { /* calculate new position */ b = 1./(9. - 10.*star_colour[8*i+3]); star_vertex[4*i+0] = star_vertex[4*i+0] + star_x*b; star_vertex[4*i+1] = star_vertex[4*i+1] + star_y*b; /* check boundaries */ if (star_vertex[4*i+0] > hw) star_vertex[4*i+0] -= w; else if (star_vertex[4*i+0] < -hw) star_vertex[4*i+0] += w; if (star_vertex[4*i+1] > hh) star_vertex[4*i+1] -= h; else if (star_vertex[4*i+1] < -hh) star_vertex[4*i+1] += h; } } /* Upload the data. */ gl_vboSubData( star_vertexVBO, 0, nstars * 4 * sizeof(GLfloat), star_vertex ); } /* Decide on shade mode. */ shade_mode = 0; if ((player.p != NULL) && !player_isFlag(PLAYER_DESTROYED) && !player_isFlag(PLAYER_CREATING)) { if (pilot_isFlag(player.p,PILOT_HYPERSPACE) && /* hyperspace fancy effects */ (player.p->ptimer < HYPERSPACE_STARS_BLUR)) { glShadeModel(GL_SMOOTH); shade_mode = 1; /* lines will be based on velocity */ m = HYPERSPACE_STARS_BLUR-player.p->ptimer; m /= HYPERSPACE_STARS_BLUR; m *= HYPERSPACE_STARS_LENGTH; x = m*cos(VANGLE(player.p->solid->vel)); y = m*sin(VANGLE(player.p->solid->vel)); } else if (dt_mod > 3.) { glShadeModel(GL_SMOOTH); shade_mode = 1; /* lines will be based on velocity */ m = (dt_mod-3.)*VMOD(player.p->solid->vel)/10.; x = m*cos(VANGLE(player.p->solid->vel)); y = m*sin(VANGLE(player.p->solid->vel)); } if (shade_mode) { /* Generate lines. */ for (i=0; i < nstars; i++) { brightness = star_colour[8*i+3]; star_vertex[4*i+2] = star_vertex[4*i+0] + x*brightness; star_vertex[4*i+3] = star_vertex[4*i+1] + y*brightness; } /* Upload new data. */ gl_vboSubData( star_vertexVBO, 0, nstars * 4 * sizeof(GLfloat), star_vertex ); } } /* Render. */ gl_vboActivate( star_vertexVBO, GL_VERTEX_ARRAY, 2, GL_FLOAT, 2 * sizeof(GLfloat) ); gl_vboActivate( star_colourVBO, GL_COLOR_ARRAY, 4, GL_FLOAT, 4 * sizeof(GLfloat) ); if (shade_mode) { glDrawArrays( GL_LINES, 0, nstars ); glDrawArrays( GL_POINTS, 0, nstars ); /* This second pass is when the lines are very short that they "lose" intensity. */ glShadeModel(GL_FLAT); } else { glDrawArrays( GL_POINTS, 0, nstars ); } /* Clear star movement. */ star_x = 0.; star_y = 0.; /* Disable vertex array. */ gl_vboDeactivate(); /* Pop matrix. */ gl_matrixPop(); /* Check for errors. */ gl_checkErr(); }
/** * @brief Handles a click event. */ static void input_clickevent( SDL_Event* event ) { unsigned int pid; int mx, my, mxr, myr, pntid, jpid; int rx, ry, rh, rw, res; int autonav; double x, y, zoom, px, py; double ang, angp, mouseang; HookParam hparam[2]; /* Generate hook. */ hparam[0].type = HOOK_PARAM_NUMBER; hparam[0].u.num = event->button.button; hparam[1].type = HOOK_PARAM_SENTINEL; hooks_runParam( "mouse", hparam ); #if !SDL_VERSION_ATLEAST(2,0,0) /* Handle zoom. */ if (event->button.button == SDL_BUTTON_WHEELUP) { input_clickZoom( 1.1 ); return; } else if (event->button.button == SDL_BUTTON_WHEELDOWN) { input_clickZoom( 0.9 ); return; } #endif /* !SDL_VERSION_ATLEAST(2,0,0) */ /* Player must not be NULL. */ if ((player.p == NULL) || player_isFlag(PLAYER_DESTROYED)) return; /* Player must not be dead. */ if (pilot_isFlag(player.p, PILOT_DEAD)) return; /* Middle mouse enables mouse flying. */ if (event->button.button == SDL_BUTTON_MIDDLE) { player_toggleMouseFly(); return; } /* Mouse targeting only uses left and right buttons. */ if (event->button.button != SDL_BUTTON_LEFT && event->button.button != SDL_BUTTON_RIGHT) return; autonav = (event->button.button == SDL_BUTTON_RIGHT) ? 1 : 0; px = player.p->solid->pos.x; py = player.p->solid->pos.y; gl_windowToScreenPos( &mx, &my, event->button.x, event->button.y ); if ((mx <= 15 || my <= 15 ) || (my >= gl_screen.h - 15 || mx >= gl_screen.w - 15)) { /* Border targeting is handled as a special case, as it uses angles, * not coordinates. */ x = (mx - (gl_screen.w / 2.)) + px; y = (my - (gl_screen.h / 2.)) + py; mouseang = atan2(py - y, px - x); angp = pilot_getNearestAng( player.p, &pid, mouseang, 1 ); ang = system_getClosestAng( cur_system, &pntid, &jpid, x, y, mouseang ); if ((ABS(angle_diff(mouseang, angp)) > M_PI / 64) || ABS(angle_diff(mouseang, ang)) < ABS(angle_diff(mouseang, angp))) pid = PLAYER_ID; /* Pilot angle is too great, or planet/jump is closer. */ if (ABS(angle_diff(mouseang, ang)) > M_PI / 64 ) jpid = pntid = -1; /* Asset angle difference is too great. */ if (!autonav && pid != PLAYER_ID) { if (input_clickedPilot(pid)) return; } else if (pntid >= 0) { /* Planet is closest. */ if (input_clickedPlanet(pntid, autonav)) return; } else if (jpid >= 0) { /* Jump point is closest. */ if (input_clickedJump(jpid, autonav)) return; } /* Fall-through and handle as a normal click. */ } /* Radar targeting requires raw coordinates. */ mxr = event->button.x; myr = gl_screen.rh - event->button.y; gui_radarGetPos( &rx, &ry ); gui_radarGetDim( &rw, &rh ); if ((mxr > rx && mxr <= rx + rw ) && (myr > ry && myr <= ry + rh )) { /* Radar */ zoom = 1.; gui_radarGetRes( &res ); x = (mxr - (rx + rw / 2.)) * res + px; y = (myr - (ry + rh / 2.)) * res + py; if (input_clickPos( event, x, y, zoom, 10. * res, 15. * res )) return; } /* Visual (on-screen) */ gl_screenToGameCoords( &x, &y, (double)mx, (double)my ); zoom = res = 1. / cam_getZoom(); input_clickPos( event, x, y, zoom, 10. * res, 15. * res ); return; }
/** * @brief Handles a click event. */ static void input_clickevent( SDL_Event* event ) { unsigned int pid; Pilot *p; int mx, my, mxr, myr, pntid, jpid; int rx, ry, rh, rw, res; double x, y, m, r, rp, d, dp, px, py; double ang, angp, mouseang; Planet *pnt; JumpPoint *jp; HookParam hparam[2]; /* Generate hook. */ hparam[0].type = HOOK_PARAM_NUMBER; hparam[0].u.num = event->button.button; hparam[1].type = HOOK_PARAM_SENTINAL; hooks_runParam( "mouse", hparam ); /* Handle zoom. */ if (event->button.button == SDL_BUTTON_WHEELUP) { input_clickZoom( 1.1 ); return; } else if (event->button.button == SDL_BUTTON_WHEELDOWN) { input_clickZoom( 0.9 ); return; } /* Middle mouse enables mouse flying. */ if (event->button.button == SDL_BUTTON_MIDDLE) { player_toggleMouseFly(); return; } /* Mouse targetting is left only. */ if (event->button.button != SDL_BUTTON_LEFT) return; /* Player must not be NULL. */ if (player_isFlag(PLAYER_DESTROYED) || (player.p == NULL)) return; px = player.p->solid->pos.x; py = player.p->solid->pos.y; gl_windowToScreenPos( &mx, &my, event->button.x, event->button.y ); gl_screenToGameCoords( &x, &y, (double)mx, (double)my ); if ((mx <= 15 || my <= 15 ) || (my >= gl_screen.h - 15 || mx >= gl_screen.w - 15)) { /* Border */ x = (mx - (gl_screen.w / 2.)) + px; y = (my - (gl_screen.h / 2.)) + py; mouseang = atan2(py - y, px - x); angp = pilot_getNearestAng( player.p, &pid, mouseang, 1 ); ang = system_getClosestAng( cur_system, &pntid, &jpid, x, y, mouseang ); if ((ABS(angle_diff(mouseang, angp)) > M_PI / 64) || ABS(angle_diff(mouseang, ang)) < ABS(angle_diff(mouseang, angp))) pid = PLAYER_ID; /* Pilot angle is too great, or planet/jump is closer. */ if (ABS(angle_diff(mouseang, ang)) > M_PI / 64 ) jpid = pntid = -1; /* Asset angle difference is too great. */ } else { /* Radar targeting requires raw coordinates. */ mxr = event->button.x; myr = gl_screen.rh - event->button.y; gui_radarGetPos( &rx, &ry ); gui_radarGetDim( &rw, &rh ); if ((mxr > rx && mxr <= rx + rw ) && (myr > ry && myr <= ry + rh )) { /* Radar */ m = 1; gui_radarGetRes( &res ); x = (mxr - (rx + rw / 2.)) * res + px; y = (myr - (ry + rh / 2.)) * res + py; } else /* Visual (on-screen) */ m = res = 1. / cam_getZoom(); dp = pilot_getNearestPos( player.p, &pid, x, y, 1 ); d = system_getClosest( cur_system, &pntid, &jpid, x, y ); rp = MAX( 1.5 * PILOT_SIZE_APROX * pilot_get(pid)->ship->gfx_space->sw / 2 * m, 10. * res); if (pntid >=0) { /* Planet is closer. */ pnt = cur_system->planets[ pntid ]; r = MAX( 1.5 * pnt->radius, 100. ); } else if (jpid >= 0) { jp = &cur_system->jumps[ jpid ]; r = MAX( 1.5 * jp->radius, 100. ); } else { r = 0.; } /* Reject pilot if it's too far or a valid asset is closer. */ if (dp > pow2(rp) || (d < pow2(r) && dp < pow2(rp) && dp > d)) pid = PLAYER_ID; if (d > pow2(r)) /* Planet or jump point is too far. */ jpid = pntid = -1; } if (pid != PLAYER_ID) { /* Apply an action if already selected. */ if (!pilot_isFlag(player.p, PILOT_DEAD) && (pid == player.p->target)) { p = pilot_get(pid); if (pilot_isDisabled(p) || pilot_isFlag(p, PILOT_BOARDABLE)) player_board(); else player_hail(); } else player_targetSet( pid ); } else if (pntid >= 0) { /* Planet is closest. */ if (pntid == player.p->nav_planet) { pnt = cur_system->planets[ pntid ]; if (planet_hasService(pnt, PLANET_SERVICE_LAND) && (!areEnemies( player.p->faction, pnt->faction ) || pnt->bribed )) player_land(); else player_hailPlanet(); } else player_targetPlanetSet( pntid ); } else if (jpid >= 0) { /* Jump point is closest. */ jp = &cur_system->jumps[ jpid ]; if (jpid == player.p->nav_hyperspace) { if (space_canHyperspace(player.p)) { if (!paused) player_autonavAbort(NULL); player_jump(); } else player_autonavStart(); } else player_targetHyperspaceSet( jpid ); } }