/*============================================================================= Name : Pieplate.c Purpose : Code for the movement pieplate mechanism. Created 4/9/1998 by lmoloney Copyright Relic Entertainment, Inc. All rights reserved. =============================================================================*/ #ifndef SW_Render #include #endif #include "glinc.h" #include #include #include #include "spaceobj.h" #include "universe.h" #include "mouse.h" #include "select.h" #include "undo.h" #include "font.h" #include "fontreg.h" #include "main.h" #include "color.h" #include "mainrgn.h" #include "prim2d.h" #include "prim3d.h" #include "fastmath.h" #include "render.h" #include "sensors.h" #include "mainrgn.h" #include "lod.h" #include "clipper.h" #include "pieplate.h" #include "glcaps.h" #include "proximitySensor.h" #include "tutor.h" #include "soundevent.h" /*============================================================================= Data: =============================================================================*/ //parameters of the point spec overlays real32 piePizzaDishRadius = PIE_PizzaDishRadius; sdword piePizzaSlices = PIE_PizzaSlices; sdword piePointSpecMode = PSM_Idle; sdword pieOldPointSpecMode; sdword piePointSpecMouseReset; real32 piePointSpecZ; vector piePlanePoint = { 0.0f, 0.0f, 0.0f }; vector pieHeightPoint = { 0.0f, 0.0f, 0.0f }; real32 pieOriginSizeCentre = PIE_OriginSizeCentre; real32 pieOriginSizeDish = PIE_OriginSizeDish; real32 pieOriginSizeHeight = PIE_OriginSizeHeight; real32 pieShipLineTickSize = PIE_ShipLineTickSize; real32 pieClosestDistance = PIE_ClosestDistance; real32 pieDottedDistance = PIE_DottedDistance; ShipPtr pieLastClosestShip; void (*pieLineDrawCallback)(real32 distance) = NULL; void (*pieMovementCursorDrawCallback)(real32 distance) = NULL; void (*piePlaneDrawCallback)(real32 distance) = NULL; real32 pieMaxMoveHorizontal = PIE_MaxMoveHorizontal; real32 pieMaxMoveVertical = PIE_MaxMoveVertical; real32 pieCircleSizeMax; real32 pieCircleSizeMin; real32 pieHeight = 0; real32 pieDistance = 0; vector pieMoveTo = {0.0f, 0.0f, 0.0f}; static fonthandle pieDistReadFont = 0; //parameters for dashed lines udword pieStipplePattern; uword pieLineStipple = PIE_LineStipple; real32 pieLineStippleCounter, pieLineStippleSpeed = PIE_StippleSpeed; //function prototype - located in mainrgn.c void toDrawPulsedLine(vector linestart, vector lineend, real32 pulsesize, color linecolor, color pulsecolor); //number of segments to use to render the ship - plane circles at various LOD's pieplanecirclesegments piePlaneCircleSegments[PIE_NumberCircleLODs]; //heading of the mothership as it last existed vector pieMotherShipHeading = {0.0f, 1.0f, 0.0f}; color moveLineColor = colWhite; #if PIE_VISUALIZE_EXTENTS bool pieVisualizeExtents = FALSE; #endif vector pieScreen0, pieScreen1; bool pieOnScreen = FALSE; extern real32 smZoomMax; /*============================================================================= Functions: =============================================================================*/ /*----------------------------------------------------------------------------- Name : pieStartup Description : initialize the pieplate module Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void pieStartup(void) { pieCircleSizeMax = primScreenToGLScaleX(MAIN_WindowWidth / 6.0f); pieCircleSizeMin = primScreenToGLScaleX(8); piePlaneCircleSegments[0].screenRadius = primScreenToGLScaleX(200); piePlaneCircleSegments[0].nSegments = 32; piePlaneCircleSegments[1].screenRadius = primScreenToGLScaleX(80); piePlaneCircleSegments[1].nSegments = 20; piePlaneCircleSegments[2].screenRadius = primScreenToGLScaleX(32); piePlaneCircleSegments[2].nSegments = 16; piePlaneCircleSegments[3].screenRadius = primScreenToGLScaleX(10); piePlaneCircleSegments[3].nSegments = 12; piePlaneCircleSegments[4].screenRadius = primScreenToGLScaleX(4); piePlaneCircleSegments[4].nSegments = 8; piePlaneCircleSegments[5].screenRadius = primScreenToGLScaleX(2); piePlaneCircleSegments[5].nSegments = 6; piePlaneCircleSegments[6].screenRadius = primScreenToGLScaleX(0); piePlaneCircleSegments[6].nSegments = 4; } /*----------------------------------------------------------------------------- Name : pieCircleSegmentsCompute Description : Compute a suitable number of segments required to draw a circle. Inputs : screenRadius - radius of circle, in GL coordinates Outputs : Return : number of segments ----------------------------------------------------------------------------*/ sdword pieCircleSegmentsCompute(real32 screenRadius) { sdword nSegments, index; nSegments = piePlaneCircleSegments[PIE_NumberCircleLODs - 1].nSegments; for (index = 0; index < PIE_NumberCircleLODs; index++) { if (screenRadius > piePlaneCircleSegments[index].screenRadius) { nSegments = piePlaneCircleSegments[index].nSegments; return(nSegments); } } return(piePlaneCircleSegments[0].nSegments); } /*----------------------------------------------------------------------------- Name : pieCancelPointSpecMode Description : Undo callback to cancel point-spec mode. Inputs : Outputs : Just sets piePointSpecMode to idle Return : void ----------------------------------------------------------------------------*/ bool pieCancelPointSpecMode(sdword userID, ubyte *userData, sdword length) { if (piePointSpecMode != PSM_Idle) { piePointSpecMode = PSM_Idle; return TRUE; } return FALSE; } /*----------------------------------------------------------------------------- Name : piePointModeOnOff Description : Toggles ship movement mode and associated navigation aids. Inputs : void Outputs : Return : void ----------------------------------------------------------------------------*/ void piePointModeOnOff(void) { if (piePointSpecMode != PSM_Idle) { mouseCursorClear(MCM_Pointmode); piePointSpecMode = PSM_Idle; // soundEvent(NULL, UI_MovementGUIoff); tutGameMessage("Game_MoveQuit"); } else if (selSelected.numShips != 0) { //if any ships selected tutGameMessage("Game_Move"); soundEvent(NULL, UI_MovementGUIon); mouseCursorSet(MCM_Pointmode); piePointSpecMode = PSM_XY; piePointSpecMouseReset = FALSE; piePointSpecZ = 0.0f; pieLineStippleCounter = 0.0f; selCentrePointCompute(); piePlanePoint.x = pieHeightPoint.x = selCentrePoint.x; piePlanePoint.y = pieHeightPoint.y = selCentrePoint.y; piePlanePoint.z = pieHeightPoint.z = selCentrePoint.z; udLatestThingPush(pieCancelPointSpecMode, 0, NULL, 0); } } /*----------------------------------------------------------------------------- Name : piePointModePause Description : Pause/unpause point spec mode. Overlays will still be drawn, just the origins will not follow the mouse. Inputs : bPause - flag telling whether to pause or unpause point Outputs : Saves current piePointSpecMode to pieOldPointSpecMode Return : void ----------------------------------------------------------------------------*/ void piePointModePause(sdword bPause) { if (bPause) { pieOldPointSpecMode = piePointSpecMode; //save current spec mode if (piePointSpecMode != PSM_Idle) { //if pause active point spec mode piePointSpecMode = PSM_Waiting; //pause the specification } } else if (piePointSpecMode == PSM_Waiting) { piePointSpecMode = pieOldPointSpecMode; //restore the point spec mode piePointSpecMouseReset = TRUE; //reset location of mouse cursor } } /*----------------------------------------------------------------------------- Name : pieMousePosSet3D Description : Sets the on-screen location of the mouse based upon a 3D vector and the current projection/movelview matrices. Inputs : modelView/projection - current matrices mouse3D - 3D location to set the mouse to Outputs : Return : OKAY if the mouse was repositioned properly, ERROR if the mouse was off-screen. ----------------------------------------------------------------------------*/ sdword pieMousePosSet3D(hmatrix *modelView, hmatrix *projection, vector *mouse3D) { hvector mouseWorld, mouseCamera, mouseScreen; sdword mouseViewX, mouseViewY; mouseWorld.x = mouse3D->x; //make a homo coord out of the mouse coordinate mouseWorld.y = mouse3D->y; mouseWorld.z = mouse3D->z; mouseWorld.w = 1.0f; hmatMultiplyHMatByHVec(&mouseCamera, modelView, &mouseWorld);//in camera space hmatMultiplyHMatByHVec(&mouseScreen, projection, &mouseCamera);//in screen space if (mouseScreen.z <= 0.0f) //if point behind camera { return(ERROR); } mouseViewX = primGLToScreenX(mouseScreen.x / mouseScreen.w);//get location in viewport coords mouseViewY = primGLToScreenY(mouseScreen.y / mouseScreen.w); //dbgMessagef("\n3d->2d mouse pos: (%.2f, %.2f, %.2f) -> (%d,%d).", mouse3D->x, mouse3D->y, mouse3D->z, mouseViewX, mouseViewY); if (mouseViewX < 0 || mouseViewX >= MAIN_WindowWidth || //if offscreen mouseViewY < 0 || mouseViewY >= MAIN_WindowHeight) { return(ERROR); } mousePositionSet(mouseViewX, mouseViewY); return(OKAY); } /*----------------------------------------------------------------------------- Name : pieSameOnScreenPoint Description : See if two points will be differnt pixels on-screen Inputs : modelView/projection - camera/viewport matrices world0, world1 - worldspace coordinates to compare Outputs : Return : TRUE if both points will be in the same place on-screen ----------------------------------------------------------------------------*/ bool pieSameOnScreenPoint(hmatrix *modelView, hmatrix *projection, vector *world0, vector *world1) { sdword x0, x1, y0, y1; hvector world, camera, screen; world.x = world0->x; world.y = world0->y; world.z = world0->z; world.w = 1.0f; hmatMultiplyHMatByHVec(&camera, modelView, &world); //in camera space hmatMultiplyHMatByHVec(&screen, projection, &camera); //in screen space if (screen.z <= 0.0f) { //if behind camera return(FALSE); } x0 = primGLToScreenX(screen.x / screen.w); //get location in viewport coords y0 = primGLToScreenY(screen.y / screen.w); if (x0 < 0 || x0 >= MAIN_WindowWidth || //if offscreen y0 < 0 || y0 >= MAIN_WindowHeight) { return(FALSE); } world.x = world1->x; world.y = world1->y; world.z = world1->z; world.w = 1.0f; hmatMultiplyHMatByHVec(&camera, modelView, &world); //in camera space hmatMultiplyHMatByHVec(&screen, projection, &camera); //in screen space if (screen.z <= 0.0f) { //if behind camera return(FALSE); } x1 = primGLToScreenX(screen.x / screen.w); //get location in viewport coords y1 = primGLToScreenY(screen.y / screen.w); if (x1 < 0 || x1 >= MAIN_WindowWidth || //if offscreen y1 < 0 || y1 >= MAIN_WindowHeight) { return(FALSE); } return(x0 == x1 && y0 == y1); } /*----------------------------------------------------------------------------- Name : pieOriginDraw Description : Draw arrows along the axes. Inputs : centre - centre or 'origin' of the origin. size - length from the centre to end of line segments. color - color to draw in Outputs : Return : Note : Currently we will draw just plain lines. Later, we may want to add arrowheads or use a mesh. ----------------------------------------------------------------------------*/ void pieOriginDraw(vector *centre, real32 size, color c) { vector p0, p1; //draw line on z axis p0.x = p1.x = centre->x; p0.y = p1.y = centre->y; p0.z = centre->z - size; p1.z = centre->z + size; primLine3(&p0, &p1, c); //draw line on y axis p0.y = centre->y - size; p1.y = centre->y + size; p0.z = p1.z = centre->z; primLine3(&p0, &p1, c); //draw line on x axis p0.x = centre->x - size; p1.x = centre->x + size; p0.y = p1.y = centre->y; primLine3(&p0, &p1, c); } /*----------------------------------------------------------------------------- Name : pieDistanceReadoutDraw Description : Draw a numerical representation of the distance from the ship to the movement mechanism pointer Inputs : movepoint - the point where the ship is supposed to go origin - the point where the ship is located c - the color for drawing the text Outputs : .. Return : void ----------------------------------------------------------------------------*/ #define PIE_DIST_READOUT_X_OFFSET 10 #define PIE_DIST_READOUT_Y_OFFSET 10 #define PIE_RU_READOUT_X_OFFSET 10 #define PIE_RU_READOUT_Y_OFFSET 2 void pieDistanceReadoutDraw(vector *movepoint, vector *origin, color c) { real32 dist_x, dist_y, dist_z; real32 screen_x, screen_y, dummy_r; udword distance; udword resources; real32 cost; fonthandle fhSave; color ruReadoutColour; hmatrix modelview, projection; bool depthOn; char dist_str[20],ru_str[20]; dist_x = movepoint->x - origin->x; dist_y = movepoint->y - origin->y; dist_z = movepoint->z - origin->z; //update global variable pieHeight = dist_z; dist_x *= dist_x; dist_y *= dist_y; dist_z *= dist_z; //find la distance (using that wonderful formula by Pythagoras) pieDistance = fsqrt(dist_x + dist_y + dist_z); distance = (udword)pieDistance; //update the global moveto vector and distance variables pieMoveTo = *movepoint; if(MP_HyperSpaceFlag) { //multiplayer... cost = hyperspaceCost(pieDistance,(SelectCommand *)&selSelected); resources = (udword) cost; sprintf(ru_str, "%i RU's", resources); if(universe.curPlayerPtr->resourceUnits < resources) { ruReadoutColour = TW_RU_READOUT_COLOR_BAD; } else { ruReadoutColour = TW_RU_READOUT_COLOR_GOOD; } } //depending on the distance, scale the readout if (distance < 100) { sprintf(dist_str, "%i m", distance); } else if ((distance >= 100) && (distance < 10000)) { distance /= 100; distance *= 100; sprintf(dist_str, "%i m", distance); } else if (distance >= 10000) { distance /= 1000; sprintf(dist_str, "%i km", distance); } //find the screen coordinates of the moveto pointer glGetFloatv(GL_MODELVIEW_MATRIX, (GLfloat *)(&modelview)); glGetFloatv(GL_PROJECTION_MATRIX, (GLfloat *)(&projection)); selCircleComputeGeneral(&modelview, &projection, movepoint, 0, &screen_x, &screen_y, &dummy_r); fhSave = fontCurrentGet(); fontMakeCurrent(pieDistReadFont); //and here I set up my own 2D space glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); depthOn = (bool)glIsEnabled(GL_DEPTH_TEST); if (depthOn) glDisable(GL_DEPTH_TEST); //using the mousecursor position to position the distance readout is more //stable, but can't be used when the camera is being moved, so the moveto //position is projected onto the screen in that case if ((mrHoldRight == mrNULL) && (smHoldRight == smNULL)) { fontPrint((mouseCursorXPosition + PIE_DIST_READOUT_X_OFFSET), (mouseCursorYPosition + PIE_DIST_READOUT_Y_OFFSET), TW_DISTANCE_READOUT_COLOR, dist_str); if(MP_HyperSpaceFlag) { fontPrint((mouseCursorXPosition + PIE_RU_READOUT_X_OFFSET), (mouseCursorYPosition + PIE_DIST_READOUT_Y_OFFSET+PIE_RU_READOUT_Y_OFFSET+fontHeight("K")), ruReadoutColour, ru_str); } } else { fontPrint((primGLToScreenX(screen_x) + PIE_DIST_READOUT_X_OFFSET), (primGLToScreenY(screen_y) + PIE_DIST_READOUT_Y_OFFSET), TW_DISTANCE_READOUT_COLOR, dist_str); if(MP_HyperSpaceFlag) { fontPrint((primGLToScreenX(screen_x) + PIE_RU_READOUT_X_OFFSET), (primGLToScreenY(screen_y) + PIE_DIST_READOUT_Y_OFFSET+PIE_RU_READOUT_Y_OFFSET+fontHeight("K")), ruReadoutColour, ru_str); } } if (depthOn) glEnable(GL_DEPTH_TEST); //back to previous space glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); fontMakeCurrent(fhSave); } #undef PIE_DIST_READOUT_X_OFFSET #undef PIE_DIST_READOUT_Y_OFFSET //list of points to draw for the dots where the vertical lines meet the pie plane struct { real32 x, y; } piePlaneScreenPoint[PIE_PlaneScreenPointIndex]; sdword piePlaneScreenPointIndex; /*----------------------------------------------------------------------------- Name : pieAllShipsToPiePlateDraw Description : Draw a line from all ships to the point-spec pie plate Inputs : distance - adjusted distance from camera to ship Outputs : .. Return : void ----------------------------------------------------------------------------*/ void pieAllShipsToPiePlateDraw(real32 distance) { Node *objnode; Ship *ship; vector planePoint, shipPoint;//, innerPoint; real32 length; real32 closestDistance = REALlyBig; bool bHeightPointDrawn, bClosestHeightDrawn; ShipPtr closestShip = NULL; color c; real32 screenX, screenY, screenRadius, radius; sdword nSegments; objnode = universe.RenderList.head; //get first node in list distance *= pieShipLineTickSize; //get distance-adjusted size piePlaneScreenPointIndex = 0; while (objnode != NULL) { //for every ship ship = (Ship *)listGetStructOfNode(objnode); if (ship->flags & SOF_Dead) { goto nextnode; } if (ship->shiptype == Drone) { goto nextnode; } if (ship->objtype != OBJ_ShipType) { goto nextnode; } if (bitTest(ship->flags,SOF_Cloaked) && ship->playerowner != universe.curPlayerPtr) { //if ship is cloaked, don't draw this stuff if(!proximityCanPlayerSeeShip(universe.curPlayerPtr, ship)) { goto nextnode; } } shipPoint.x = planePoint.x = ship->posinfo.position.x; shipPoint.y = planePoint.y = ship->posinfo.position.y; shipPoint.z = ship->posinfo.position.z; planePoint.z = selCentrePoint.z; if (ship == pieLastClosestShip) { //if closest ship from last frame c = TW_SHIP_LINE_CLOSEST_COLOR; } else { c = TW_SHIP_LINE_COLOR; } primLine3(&planePoint, &shipPoint, c); //draw line from plane to ship length = fsqrt(((shipPoint.x - selCentrePoint.x) * (shipPoint.x - selCentrePoint.x) + (shipPoint.y - selCentrePoint.y) * (shipPoint.y - selCentrePoint.y))); //now draw circle on the plane bHeightPointDrawn = FALSE; if (abs(shipPoint.z - planePoint.z) > ship->staticinfo->staticheader.staticCollInfo.collspheresize) { radius = ship->staticinfo->staticheader.staticCollInfo.collspheresize; selCircleComputeGeneral(&rndCameraMatrix, &rndProjectionMatrix, &planePoint, radius, &screenX, &screenY, &screenRadius); if (screenRadius > 0.0f) { // dbgAssert(piePlaneScreenPointIndex < PIE_PlaneScreenPointIndex); if (piePlaneScreenPointIndex < PIE_PlaneScreenPointIndex) { piePlaneScreenPoint[piePlaneScreenPointIndex].x = screenX; piePlaneScreenPoint[piePlaneScreenPointIndex].y = screenY; piePlaneScreenPointIndex++; nSegments = pieCircleSegmentsCompute(screenRadius); primCircleOutlineZ(&planePoint, radius, nSegments, c); bHeightPointDrawn = TRUE; } } } //see if this is the closest ship to the cursor length = abs(piePlanePoint.x - planePoint.x) + //manhattan distance abs(piePlanePoint.y - planePoint.y); if (length < closestDistance) { //if this ship closest so far closestDistance = length; closestShip = ship; bClosestHeightDrawn = bHeightPointDrawn; } nextnode: objnode = objnode->next; } if (closestShip != NULL) { if (closestDistance > max(distance, pieClosestDistance)) { //if the closest ship wasn't close enough closestShip = NULL; //there was no closest ship } } /* play the movement gui click sound */ if ((closestShip != NULL) && (pieLastClosestShip != closestShip) && !selShipInSelection(selSelected.ShipPtr, selSelected.numShips, closestShip) && bClosestHeightDrawn) { soundEvent(NULL, UI_MoveChimes); } pieLastClosestShip = closestShip; } /*----------------------------------------------------------------------------- Name : pieWorldLocationCompute Description : Compute the location of the mouse in world coordinates by 'unprojecting' them. Outputs : fills in world0 and world1 with 3d points describing a line under the mouse. eyeMag - approximate magnitude of the camera's eye position Return : void ----------------------------------------------------------------------------*/ void pieWorldLocationCompute(vector *world0, vector *world1, real32 *eyeMag) { world0->x = mrCamera->eyeposition.x; world0->y = mrCamera->eyeposition.y; world0->z = mrCamera->eyeposition.z; *eyeMag = fsqrt(vecMagnitudeSquared(*world0)); /* //ray-cast the four corners of the frustum cameraRayCast(world1, mrCamera, modelView, 0, 0, MAIN_WindowWidth, MAIN_WindowHeight); cameraRayCast(world1, mrCamera, modelView, MAIN_WindowWidth - 1, 0, MAIN_WindowWidth, MAIN_WindowHeight); cameraRayCast(world1, mrCamera, modelView, 0, MAIN_WindowHeight - 1, MAIN_WindowWidth - 1, MAIN_WindowHeight); cameraRayCast(world1, mrCamera, modelView, MAIN_WindowWidth - 1, MAIN_WindowHeight - 1, MAIN_WindowWidth, MAIN_WindowHeight); */ cameraRayCast(world1, mrCamera, mouseCursorX(), mouseCursorY(), MAIN_WindowWidth, MAIN_WindowHeight); vecMultiplyByScalar(*world1, *eyeMag); vecAdd(*world1, *world1, *world0); } /*----------------------------------------------------------------------------- Name : pieScreenSizeOfCircleCompute Description : Compute the size of the movement circle, clipping it to a minimum and maximum in screen space. Inputs : position - where on screen it is to be rendered radius - radius, world space of the thingus Outputs : outRadius - new world radius, possible adjusted mSegments - number of segments to use when drawing the circle. Return : ----------------------------------------------------------------------------*/ void pieScreenSizeOfCircleCompute(vector *position, real32 inRadius, real32 *outRadius, sdword *nSegments) { real32 screenX, screenY, screenRadius; real32 factor; *outRadius = inRadius; //figure out what size it is now selCircleComputeGeneral(&rndCameraMatrix, &rndProjectionMatrix, position, inRadius, &screenX, &screenY, &screenRadius); if (screenRadius > pieCircleSizeMax) { //if it is currently too large factor = pieCircleSizeMax / screenRadius; *outRadius *= factor; screenRadius *= factor; } if (screenRadius < pieCircleSizeMin) { //if currently too small factor = pieCircleSizeMin / screenRadius; *outRadius *= factor; screenRadius *= factor; } *nSegments = pieCircleSegmentsCompute(screenRadius); } /*----------------------------------------------------------------------------- Name : pieNeedSpecialAttackAndMoveColor Description : returns true if all ships in selection have an attack order and are going to perform an attack and move. Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ bool pieNeedSpecialAttackAndMoveColor() { sdword i,j; CommandToDo *command; j=0; for(i=0;iordertype.order != COMMAND_ATTACK) return FALSE; if(selSelected.ShipPtr[i]->staticinfo->shipclass == CLASS_Fighter || selSelected.ShipPtr[i]->staticinfo->shipclass == CLASS_Corvette || selSelected.ShipPtr[i]->staticinfo->cantMoveAndAttack) j++; } if(j==selSelected.numShips) return FALSE; //all strike craft...don't do special move attack! return TRUE; } /*----------------------------------------------------------------------------- Name : pieMovementCursorDraw Description : Default movement mechanism for rendering the dotted lines and destination cursors of the movement mechanism. Inputs : distance - size multiplier for no good reason? Outputs : Return : void ----------------------------------------------------------------------------*/ void pieMovementCursorDraw(real32 distance) { bool stipple; real32 scaledSize; sdword nSegments; stipple = glCapFastFeature(GL_LINE_STIPPLE); if (abs(piePointSpecZ) < pieDottedDistance) { if (stipple) { glEnable(GL_LINE_STIPPLE); glLineStipple(1, (GLushort)pieStipplePattern); } primLine3(&piePlanePoint, &selCentrePoint, moveLineColor);//draw line from centre to mouse point on x/y plane if (stipple) { glDisable(GL_LINE_STIPPLE); } } else { //if point off standard Z-plane if (stipple) { glEnable(GL_LINE_STIPPLE); glLineStipple(1, (GLushort)pieStipplePattern); } primLine3(&pieHeightPoint, &selCentrePoint, moveLineColor);//draw from centre of dish to height point if (stipple) { glDisable(GL_LINE_STIPPLE); } primLine3(&pieHeightPoint, &piePlanePoint, moveLineColor); //TW_MOVE_PIZZA_COLOR tweakable global variable (tweak.*) primLine3(&piePlanePoint, &selCentrePoint, moveLineColor);//draw line from centre to mouse point on x/y plane //destination circle pieScreenSizeOfCircleCompute(&pieHeightPoint, selAverageSize, &scaledSize, &nSegments); primCircleOutlineZ(&pieHeightPoint, scaledSize, nSegments, moveLineColor); } } /*----------------------------------------------------------------------------- Name : pieDestinationInGunRangeOfTargets Description : returns TRUE if all attacking ships will be within gun range of at least 1 of their targets at the new position Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ bool pieDestinationInGunRangeOfTargets() { sdword i,j; vector distvec; real32 distSqr; sdword breaker; CommandToDo *command; for(i=0;ishiptype == CloakGenerator || selSelected.ShipPtr[i]->shiptype == GravWellGenerator || selSelected.ShipPtr[i]->shiptype == DFGFrigate) continue; command = getShipAndItsCommand(&universe.mainCommandLayer,selSelected.ShipPtr[i]); dbgAssert(command != NULL); dbgAssert(command->ordertype.order == COMMAND_ATTACK); breaker = FALSE; for(j = 0; jattack->numTargets;j++) { vecSub(distvec,pieHeightPoint,command->attack->TargetPtr[j]->posinfo.position); distSqr = vecMagnitudeSquared(distvec); if(distSqr < selSelected.ShipPtr[i]->staticinfo->bulletRangeSquared[selSelected.ShipPtr[i]->tacticstype]) { //within range...break and check next ship breaker = TRUE; break; } } if(!breaker) return FALSE; //ship will not be within range } return TRUE; } /*----------------------------------------------------------------------------- Name : piePlaneDraw Description : Default draw callback for rendering the movement mechanism pizza dish. Inputs : distance - radius of the movement mechanism Outputs : Return : ----------------------------------------------------------------------------*/ void piePlaneDraw(real32 distance) { vector world0, world1, plane; //Ship *mothership; real32 scaledSize; sdword nSegments; color pieColor,spokeColor,headingColor; if(pieNeedSpecialAttackAndMoveColor()) { if(pieDestinationInGunRangeOfTargets()) { moveLineColor = TW_MOVE_ATTACK_POINT_LINE_COLOR_IN_RANGE; } else { moveLineColor = TW_MOVE_ATTACK_POINT_LINE_COLOR_NOT_IN_RANGE; } pieColor = TW_MOVE_ATTACK_PIE_COLOR; spokeColor = TW_MOVE_ATTACK_SPOKE_COLOR; headingColor = TW_MOVE_HEADING_COLOR; } else if(MP_HyperSpaceFlag) { pieColor = TW_MOVE_HYPERSPACE_COLOUR; moveLineColor = TW_MOVE_POINT_HYPERSPACE_LINE_COLOR; spokeColor = TW_MOVE_SPOKE_HYPERSPACE_COLOR; headingColor = TW_MOVE_HEADING_HYPERSPACE_COLOR; } else { pieColor = TW_MOVE_PIZZA_COLOR; moveLineColor = TW_MOVE_POINT_LINE_COLOR; spokeColor = TW_MOVE_SPOKE_COLOR; headingColor = TW_MOVE_HEADING_COLOR; } primCircleOutlineZ(&selCentrePoint, piePizzaDishRadius * distance, piePizzaSlices, pieColor);//TW_MOVE_PIZZA_COLOR tweakable global variable (tweak.*) //get the heading of the mothership for drawing the 'spokes' of the pie plate /* mothership = universe.curPlayerPtr->PlayerMothership; if (mothership != NULL) { matGetVectFromMatrixCol3(pieMotherShipHeading, mothership->rotinfo.coordsys); //handle an unlikely case: mothership pointing just about straight 'down' if (pieMotherShipHeading.z >= (1.0f - (real32)1e-6)) { pieMotherShipHeading.x = pieMotherShipHeading.z = 0.0f; pieMotherShipHeading.y = 1.0f; } else { pieMotherShipHeading.z = 0; vecNormalize(&pieMotherShipHeading); } } dbgAssert(pieMotherShipHeading.x != REALlyBig); */ pieMotherShipHeading.x = pieMotherShipHeading.z = 0.0f; pieMotherShipHeading.y = 1.0f; vecScalarMultiply(plane, pieMotherShipHeading, piePizzaDishRadius * distance); world0.x = selCentrePoint.x + plane.x; world0.y = selCentrePoint.y + plane.y; world0.z = selCentrePoint.z; primLine3(&selCentrePoint, &world0, spokeColor);//headingColor); world0.x = selCentrePoint.x - plane.x; world0.y = selCentrePoint.y - plane.y; primLine3(&selCentrePoint, &world0, spokeColor); world0.x = selCentrePoint.x + plane.y; world0.y = selCentrePoint.y - plane.x; world1.x = selCentrePoint.x - plane.y; world1.y = selCentrePoint.y + plane.x; world1.z = selCentrePoint.z; primLine3(&world1, &world0, spokeColor); //pieOriginDraw(&selCentrePoint, pieOriginSizeCentre * distance, TW_MOVE_ORIGIN_COLOR); //TW_MOVE_ORIGIN_COLOR tweakable global variable (tweak.*) //plane point circle pieScreenSizeOfCircleCompute(&piePlanePoint, selAverageSize, &scaledSize, &nSegments); primCircleOutlineZ(&piePlanePoint, scaledSize, nSegments, moveLineColor); } /*----------------------------------------------------------------------------- Name : pieMovePointClipToLimits Description : Clips a point to an elliptical cylindrical clipping volume. Inputs : sizeX, Y, Z - size of the elliptical clipping cylinder, in half-lengths pointA - where you are moving from pointB - where you are moving to, this is the point that will be clipped Outputs : modifies pointB if a valid clipping point found; set to pointA if not. In cases where there are two possible intersections (i.e. pointA and B are outside the volume) the one closer to pointB will be used. Return : TRUE if a valid intersection found, FALSE otherwise ----------------------------------------------------------------------------*/ #define SGN(x) ((x) < 0.0f ? -1.0f : 1.0f) #define ABS(x) ((x) < 0.0f ? -(x) : (x)) bool pieMovePointClipToLimits(real32 sizeX, real32 sizeY, real32 sizeZ, vector *pointA, vector *pointB) { real32 discriminant; real32 dx, dy, drSquared, rSquared, D, DSquared; real32 x, y, x1, y1, x2, y2; real32 ellipseAspect; real32 fsqrtResult; #if PIE_VISUALIZE_EXTENTS if (pieVisualizeExtents) { vector centre = {0, 0, pointB->z}; centre.z = sizeZ; primEllipseOutlineZ(¢re, sizeX, sizeY, 64, colFuscia); centre.z = -sizeZ; primEllipseOutlineZ(¢re, sizeX, sizeY, 64, colFuscia); centre.z = pointB->z; primEllipseOutlineZ(¢re, sizeX, sizeY, 64, colWhite); } #endif //simplify the problem by converting the problem to line-circle intersection //the circle will have a radius of the ellipses rx (sizeX) ellipseAspect = sizeY / sizeX; x1 = pointA->x; y1 = pointA->y / ellipseAspect; x2 = pointB->x; y2 = pointB->y / ellipseAspect; //base equations for line-circle intersection: dx = x2 - x1; dy = y2 - y1; drSquared = dx * dx + dy * dy; D = x1 * y2 - x2 * y1; DSquared = D * D; rSquared = sizeX * sizeX; discriminant = rSquared * drSquared - DSquared; if (discriminant < 0.0f) { //if no intersection pointB->x = pointA->x; pointB->y = pointA->y; return(FALSE); //no clipping because no intersection } fsqrtResult = fsqrt(rSquared * drSquared - DSquared); x1 = (D * dy + SGN(dy) * dx * fsqrtResult) / drSquared; x2 = (D * dy - SGN(dy) * dx * fsqrtResult) / drSquared; y1 = (-D * dx + ABS(dy) * fsqrtResult) / drSquared; y2 = (-D * dx - ABS(dy) * fsqrtResult) / drSquared; //convert the computed results back into elliptical coordinates y1 *= ellipseAspect; y2 *= ellipseAspect; //see if that point was clipped if (pointB->x >= min(x1, x2) && pointB->x <= max(x1, x2) && pointB->y >= min(y1, y2) && pointB->y <= max(y1, y2)) { //if between the intersection points ; //points are fine } else { //else use whatever intersection closer to specified destination dx = pointB->x - x1; dy = pointB->y - y1; x = ABS(dx * dx + dy * dy); //x1,y1 dx = pointB->x - x2; dy = pointB->y - y2; y = ABS(dx * dx + dy * dy); //x2,y2 if (x > y) { //use x2,y2 x1 = x2; //x1,y1 is always the closer intersection y1 = y2; } pointB->x = x1; pointB->y = y1; } if (pointB->z > sizeZ) { pointB->z = sizeZ; } if (pointB->z < -sizeZ) { pointB->z = -sizeZ; } return(TRUE); } #undef SGN #undef ABS /*----------------------------------------------------------------------------- Name : piePointSpecDraw Description : Draw the point specification overlays Inputs : void Outputs : .. Return : void ----------------------------------------------------------------------------*/ void piePointSpecDraw(void) { vector world0, world1; vector highPoint, lowPoint; GLdouble modelView[16], projection[16]; hmatrix modelViewF, projectionF; real32 distance, deltaX, deltaY; real32 oldPointSpecZ, slope, b; vector moveLengthVector; vector preUpdateCentre; vector updateDrift; real32 moveLength, maxMoveLength; real32 windowHeightMinusOne; real32 eyeMagnitude; //static int oldMouseX, oldMouseY; /* bool stipple; stipple = glCapFastFeature(GL_LINE_STIPPLE); */ mousePoll(); /* if (oldMouseX != mouseCursorX() || oldMouseY != mouseCursorY()) { cameraRayCast(&world0, mrCamera, mouseCursorX(), mouseCursorY(), MAIN_WindowWidth, MAIN_WindowHeight); dbgMessagef("\nVec = %.3f %.3f %.3f", world0.x, world0.y, world0.z); oldMouseX = mouseCursorX(); oldMouseY = mouseCursorY(); } */ if (pieDistReadFont == 0) { pieDistReadFont = frFontRegister(TW_DISTANCE_READOUT_FONT); } if (selSelected.numShips == 0) { piePointModeOnOff(); return; } //before recomputing the selection centre point, get the relationship between selection centre, plane point and heightpoint preUpdateCentre = selCentrePoint; selCentrePointCompute(); //get new centre of selection vecSub(updateDrift, selCentrePoint, preUpdateCentre); //compute drift of centre point for error correction if (piePointSpecMouseReset) { //if we should reset location of mouse glGetFloatv(GL_PROJECTION_MATRIX, //get the matrices in hmatrix format (float *)(&projectionF)); glGetFloatv(GL_MODELVIEW_MATRIX, (float *)(&modelViewF)); if (piePointSpecMode == PSM_XY) { vecAdd(world0, piePlanePoint, updateDrift); vecAdd(world1, pieHeightPoint, updateDrift); pieMousePosSet3D(&modelViewF, &projectionF, //set the position of the mouse &world0); if (pieSameOnScreenPoint(&modelViewF, &projectionF, &world0, &world1)) { //if they're still at the same spot on-screen pieHeightPoint = piePlanePoint; //kill any vertical component piePointSpecZ = 0.0f; } } else { vecAdd(world0, pieHeightPoint, updateDrift); pieMousePosSet3D(&modelViewF, &projectionF, //set the postion of the mouse &world0); } piePointSpecMouseReset = FALSE; //don't reset on next frame } windowHeightMinusOne = (real32)(MAIN_WindowHeight - 1); glGetDoublev(GL_PROJECTION_MATRIX, projection); glGetDoublev(GL_MODELVIEW_MATRIX, modelView); //get the matrices world0.x = mrCamera->eyeposition.x - selCentrePoint.x; world0.y = mrCamera->eyeposition.y - selCentrePoint.y; world0.z = mrCamera->eyeposition.z - selCentrePoint.z; distance = fmathSqrt(vecMagnitudeSquared(world0)); //prepare for drawing primModeClear2(); rndLightingEnable(FALSE); //draw the pizza dish rndTextureEnable(FALSE); //disable texturing //compute world coords of mouse location if (piePointSpecMode != PSM_Waiting) { //compute mouse location in world space pieWorldLocationCompute(&world0, &world1, &eyeMagnitude); if (piePointSpecMode == PSM_XY) { //find point on pizza dish plane which represents mouse //ensure positive slope of intersection line if (mrCamera->eyeposition.z >= selCentrePoint.z || world1.z == world0.z) { //if camera above selection plane if (world1.z >= world0.z) { world1.z = world0.z - PIE_PlanePointFudge * eyeMagnitude; } } else { //else camera below selection sphere if (world1.z <= world0.z) { world1.z = world0.z + PIE_PlanePointFudge * eyeMagnitude; } } vecLineIntersectWithXYPlane(&piePlanePoint, &world0, &world1, selCentrePoint.z); pieHeightPoint.x = piePlanePoint.x; pieHeightPoint.y = piePlanePoint.y; pieHeightPoint.z += updateDrift.z; } else { //else find point on vertical line passing through point on pizza dish (PSM_Z) if (pieOnScreen) { //ensure the vertical is at least partly on screen if ((real32)mouseCursorY() < (windowHeightMinusOne - pieScreen1.y)) { //if mouse off top of line mousePositionSet(mouseCursorX(), (sdword)(windowHeightMinusOne - pieScreen1.y)); } if ((real32)mouseCursorY() > (windowHeightMinusOne - pieScreen0.y)) { //if mouse off bottom of line mousePositionSet(mouseCursorX(), (sdword)(windowHeightMinusOne - pieScreen0.y)); } slope = (pieScreen0.x - pieScreen1.x) / (pieScreen1.y - pieScreen0.y); b = pieScreen1.x - slope * (windowHeightMinusOne - pieScreen1.y); mousePositionSet((sdword)(slope * mouseCursorY() + b), mouseCursorY()); //mouse position was adjusted; recompute mouse location in world space pieWorldLocationCompute(&world0, &world1, &eyeMagnitude); /* if (oldMouseX != mouseCursorX() || oldMouseY != mouseCursorY()) { dbgMessagef("\nslope = %.2f b = %.2f", slope, b); dbgMessagef("\nscreen0=(%.2f,%.2f) screen1=(%.2f,%.2f).", pieScreen0.x, pieScreen0.y, pieScreen1.x, pieScreen1.y); dbgMessagef("\nworld0=(%.2f,%.2f,%.2f) world1=(%.2f,%.2f,%.2f).", world0.x, world0.y, world0.z, world1.x, world1.y, world1.z); oldMouseX = mouseCursorX(); oldMouseY = mouseCursorY(); } */ } deltaX = mrCamera->eyeposition.x - (piePlanePoint.x + updateDrift.x);//find along which axis point is farther from camera deltaY = mrCamera->eyeposition.y - (piePlanePoint.y + updateDrift.y); if (deltaX > deltaY) { //ensure positive slope of intersection line if (mrCamera->eyeposition.x >= piePlanePoint.x + updateDrift.x || world1.x == world0.x) { //camera on positive side of test plane if (world1.x >= world0.x) { world1.x = world0.x - PIE_PlanePointFudge * eyeMagnitude; } } else { if (world1.x <= world0.x) { world1.x = world0.x + PIE_PlanePointFudge * eyeMagnitude; } } vecLineIntersectWithYZPlane(&pieHeightPoint, &world0, &world1, piePlanePoint.x + updateDrift.x); //dbgMessagef("\\"); } else { //ensure positive slope of intersection line if (mrCamera->eyeposition.y >= piePlanePoint.y + updateDrift.y || world1.y == world0.y) { //camera on positive side of test plane if (world1.y >= world0.y) { world1.y = world0.y - PIE_PlanePointFudge * eyeMagnitude; } } else { if (world1.y <= world0.y) { world1.y = world0.y + PIE_PlanePointFudge * eyeMagnitude; } } vecLineIntersectWithXZPlane(&pieHeightPoint, &world0, &world1, piePlanePoint.y + updateDrift.y); //dbgMessagef("/"); } /* if (oldMouseX != mouseCursorX() || oldMouseY != mouseCursorY()) { dbgMessagef("\ndeltaX = %.2f deltaY = %.2f", deltaX, deltaY); dbgMessagef("\nheightPoint=(%.2f,%.2f,%.2f).", pieHeightPoint.x, pieHeightPoint.y, pieHeightPoint.z); dbgMessagef("\nworld0=(%.2f,%.2f,%.2f) world1=(%.2f,%.2f,%.2f).", world0.x, world0.y, world0.z, world1.x, world1.y, world1.z); oldMouseX = mouseCursorX(); oldMouseY = mouseCursorY(); } */ //don't use this new value if the intersection test resulted in a bad number if (!_isnan(pieHeightPoint.z)) { //compute point along this line pieHeightPoint.x = piePlanePoint.x; //compute height point relative to pizza dish pieHeightPoint.y = piePlanePoint.y; oldPointSpecZ = piePointSpecZ; piePointSpecZ = pieHeightPoint.z - piePlanePoint.z;//get height off pizza dish } vecAddTo(piePlanePoint, updateDrift); //make sure the plane point does not drift //vecAddTo(pieHeightPoint, updateDrift); //make sure the height point does not drift } } else { //in waiting mode: keep destination point from drifting vecAddTo(piePlanePoint, updateDrift); //make sure the plane point does not drift vecAddTo(pieHeightPoint, updateDrift); //make sure the height point does not drift } //recompute the on-screen vertical vector pieHeightPoint.x = piePlanePoint.x; //compute height point relative to pizza dish point pieHeightPoint.y = piePlanePoint.y; highPoint.x = lowPoint.x = piePlanePoint.x; highPoint.y = lowPoint.y = piePlanePoint.y; highPoint.z = smUniverseSizeZ; lowPoint.z = -smUniverseSizeZ; pieOnScreen = clipLineToScreen(&lowPoint, &highPoint, (real32 *)(&rndCameraMatrix), (real32 *)(&rndProjectionMatrix), &pieScreen0, &pieScreen1); if (pieScreen0.y > pieScreen1.y) { //if upside-down swapReal32(pieScreen0.x, pieScreen1.x); //transpose the endpoints swapReal32(pieScreen0.y, pieScreen1.y); } //limit dest vector to render list extent if (!smSensorsActive) { maxMoveLength = pieMaxMoveHorizontal; moveLengthVector.x = pieHeightPoint.x - selCentrePoint.x; moveLengthVector.y = pieHeightPoint.y - selCentrePoint.y; moveLengthVector.z = 0.0f; moveLength = fmathSqrt(vecMagnitudeSquared(moveLengthVector)); if (moveLength > maxMoveLength) { //if distance on the plane too far vecMultiplyByScalar(moveLengthVector, maxMoveLength / moveLength); vecAdd(piePlanePoint, selCentrePoint, moveLengthVector); pieHeightPoint.x = piePlanePoint.x; //clip to the render list horizontally pieHeightPoint.y = piePlanePoint.y; } maxMoveLength = pieMaxMoveVertical; moveLength = abs(pieHeightPoint.z - piePlanePoint.z); if (moveLength > maxMoveLength) { //if moved too high pieHeightPoint.z = piePlanePoint.z + (pieHeightPoint.z - piePlanePoint.z) * maxMoveLength / moveLength; } } //now limit end point to the 'hard' limits of the universe. //After all, the universe has hard limits you know. pieMovePointClipToLimits(smUniverseSizeX, smUniverseSizeY, smUniverseSizeZ, &selCentrePoint, &pieHeightPoint); piePlanePoint.x = pieHeightPoint.x; piePlanePoint.y = pieHeightPoint.y; piePlanePoint.z = selCentrePoint.z; pieDistanceReadoutDraw(&pieHeightPoint, &selCentrePoint, TW_MOVE_ORIGIN_COLOR); //draw the pizza dish with spokes if (piePlaneDrawCallback != NULL) { piePlaneDrawCallback(distance); } //now draw lines and cross-hairs if (pieMovementCursorDrawCallback != NULL) { pieMovementCursorDrawCallback(distance); } //draw lines to pie plate if (pieLineDrawCallback != NULL) { pieLineDrawCallback(distance); } //fraw the horizon if (mrRenderMainScreen) { smCurrentWorldPlaneColor = colMultiplyClamped(smWorldPlaneColor, smMovementWorldPlaneDim); smHorizonLineDraw(mrCamera, &rndCameraMatrix, &rndProjectionMatrix, mrCamera->clipPlaneFar - mrCamera->farthestZoom); } rndLightingEnable(FALSE); primModeSet2(); #if PIE_MOVE_NEARTO //make the ship under cursor glow as a destination of the 'move near to' command if (selClosestTarget != NULL) { //if there is a ship under cursor selClosestTarget->flashtimer = taskTimeElapsed+FLASH_TIMER/2; //just glow for the next frame only } #endif //PIE_MOVE_NEARTO } /*----------------------------------------------------------------------------- Name : pieShipDied Description : cleans up main region stuff when a ship dies Inputs : Outputs : Return : Note : call only after ship has been removed from selSelected, selSelecting ----------------------------------------------------------------------------*/ void pieShipDied(struct Ship *ship) { if (pieLastClosestShip == ship) { pieLastClosestShip = NULL; } if ((selSelected.numShips == 0) && (piePointSpecMode != PSM_Idle)) { piePointModeOnOff(); } } /*----------------------------------------------------------------------------- Name : piePointModeToggle Description : Toggles between xy-mode and z-mode for point specification Inputs : bOn - if true, set Z mode, XY mode otherwise Outputs : Return : void ----------------------------------------------------------------------------*/ void piePointModeToggle(sdword bOn) { if (bOn && piePointSpecMode == PSM_XY) { if (!keyIsHit(CONTROLKEY)) { tutGameMessage("Game_MoveZ"); piePointSpecMode = PSM_Z; if (piePointSpecZ == 0.0f) { //if height point not yet specified pieHeightPoint = piePlanePoint; } } else { pieHeightPoint = piePlanePoint; piePointSpecZ = 0.0f; } } else if (!bOn && piePointSpecMode == PSM_Z) { tutGameMessage("Game_MoveXY"); piePointSpecMode = PSM_XY; } piePointSpecMouseReset = TRUE; //reset location of mouse cursor }