diff --git a/lib/gamelib/gtime.c b/lib/gamelib/gtime.c index c25d0d05e..059b7b62e 100644 --- a/lib/gamelib/gtime.c +++ b/lib/gamelib/gtime.c @@ -104,7 +104,7 @@ UDWORD getModularScaledRealTime(UDWORD timePeriod, UDWORD requiredRange) } /* Call this each loop to update the game timer */ -bool logicalUpdates = false; +bool logicalUpdates = true; void gameTimeUpdate() { uint32_t currTime = SDL_GetTicks(); @@ -134,18 +134,17 @@ void gameTimeUpdate() } // Calculate the time for this frame - deltaGameTime = scaledCurrTime - gameTime; deltaGraphicsTime = scaledCurrTime - graphicsTime; // Adjust deltas. if (sane) { - if (deltaGameTime > GAME_UNITS_PER_TICK) + if (scaledCurrTime >= gameTime) { - if (deltaGameTime >= GAME_UNITS_PER_TICK*2) + if (scaledCurrTime > gameTime + GAME_UNITS_PER_TICK) { // Game isn't updating fast enough... - uint32_t slideBack = deltaGameTime - GAME_UNITS_PER_TICK*2; + uint32_t slideBack = deltaGameTime - GAME_UNITS_PER_TICK; baseTime += slideBack / modifier; // adjust the addition to base time deltaGraphicsTime -= slideBack; } @@ -159,12 +158,15 @@ void gameTimeUpdate() } else { + deltaGameTime = scaledCurrTime - gameTime; + // Limit the frame time if (deltaGraphicsTime > GTIME_MAXFRAME) { uint32_t slideBack = deltaGraphicsTime - GTIME_MAXFRAME; baseTime += slideBack / modifier; // adjust the addition to base time deltaGraphicsTime -= slideBack; + deltaGameTime -= slideBack; // If !sane, then deltaGameTime == deltaGraphicsTime. } } diff --git a/lib/gamelib/gtime.h b/lib/gamelib/gtime.h index 0d5d8abca..4406aeb78 100644 --- a/lib/gamelib/gtime.h +++ b/lib/gamelib/gtime.h @@ -44,7 +44,7 @@ /// Changes in GAME_UNITS_PER_TICK increments. extern UDWORD gameTime; /// The current time in the graphical display of the game world. -/// Should be close to gameTime, up to GAME_UNITS_PER_TICK ahead. +/// Should be close to gameTime, up to GAME_UNITS_PER_TICK behind. extern UDWORD graphicsTime; /** The current time in the game world - never stops. * FIXME Then isn't it the real time, not the game time? Rename from gameTime2 to realTime? diff --git a/src/basedef.h b/src/basedef.h index c19d28bd8..619bd385c 100644 --- a/src/basedef.h +++ b/src/basedef.h @@ -60,6 +60,7 @@ typedef struct _tilePos For explanation of yaw/pitch/roll look for "flight dynamics" in your encyclopedia. */ +/// Along with NEXTOBJ, the elements of the base class SIMPLE_OBJECT. FIXME If converting to C++, this can be a normal base class and a lot less ugly. #define BASE_ELEMENTS1(pointerType) \ OBJECT_TYPE type; /**< The type of object */ \ UDWORD id; /**< ID number of the object */ \ @@ -67,7 +68,9 @@ typedef struct _tilePos float direction; /**< Object's yaw +ve rotation around up-axis */ \ SWORD pitch; /**< Object's pitch +ve rotation around right-axis (nose up/down) */ \ UBYTE player; /**< Which player the object belongs to */ \ - SWORD roll /**< Object's roll +ve rotation around forward-axis (left wing up/down) */ + SWORD roll; /**< Object's roll +ve rotation around forward-axis (left wing up/down) */ \ + SWORD prevRoll; /**< Object's roll +ve rotation around forward-axis (left wing up/down), previous tick */ \ + uint32_t time; /**< Game time of given space-time position. */ #define BASE_ELEMENTS2(pointerType) \ SCREEN_DISP_DATA sDisplay; /**< screen coordinate details */ \ @@ -91,10 +94,12 @@ typedef struct _tilePos TILEPOS *watchedTiles; /**< Variable size array of watched tiles, NULL for features */ \ UDWORD armour[NUM_HIT_SIDES][WC_NUM_WEAPON_CLASSES] +/// Along with BASE_ELEMENTS1, the elements of the base class BASE_OBJECT. FIXME If converting to C++, this can be a normal base class and a lot less ugly. #define NEXTOBJ(pointerType) \ pointerType *psNext; /**< Pointer to the next object in the object list */ \ pointerType *psNextFunc /**< Pointer to the next object in the function list */ +/// The elements of the base class SIMPLE_ELEMENTS. #define SIMPLE_ELEMENTS(pointerType) \ BASE_ELEMENTS1(pointerType); \ NEXTOBJ(pointerType) @@ -105,7 +110,7 @@ typedef struct _tilePos typedef struct BASE_OBJECT { - BASE_ELEMENTS( struct BASE_OBJECT ); + BASE_ELEMENTS( struct BASE_OBJECT ); ///< FIXME Should be converted to C++, and be written as ": public SIMPLE_OBJECT", avoiding a whole lot of casts. } WZ_DECL_MAY_ALIAS BASE_OBJECT; typedef struct SIMPLE_OBJECT @@ -113,6 +118,26 @@ typedef struct SIMPLE_OBJECT SIMPLE_ELEMENTS( struct SIMPLE_OBJECT ); } SIMPLE_OBJECT; +/// Space-time coordinate. +typedef struct SPACETIME +{ + uint32_t time; ///< Game time + + Vector3uw pos; ///< Position of the object + int16_t pitch; ///< Object's pitch +ve rotation around right-axis (nose up/down) + float direction; ///< Object's yaw +ve rotation around up-axis + int16_t roll; ///< Object's roll +ve rotation around forward-axis (left wing up/down) +} SPACETIME; + +static inline SPACETIME constructSpacetime(Vector3uw pos, float direction, int16_t pitch, int16_t roll, uint32_t time) +{ + SPACETIME ret = {time, pos, pitch, direction, roll}; + return ret; +} + +#define GET_SPACETIME(psObj) constructSpacetime(psObj->pos, psObj->direction, psObj->pitch, psObj->roll, psObj->time) +#define SET_SPACETIME(psObj, st) do { psObj->pos = st.pos; psObj->direction = st.direction; psObj->pitch = st.pitch; psObj->roll = st.roll; psObj->time = st.time; } while(0) + static inline bool isDead(const BASE_OBJECT* psObj) { // See objmem.c for comments on the NOT_CURRENT_LIST hack diff --git a/src/baseobject.c b/src/baseobject.c index 7e50af721..683ef060b 100644 --- a/src/baseobject.c +++ b/src/baseobject.c @@ -25,6 +25,51 @@ #include "projectile.h" #include "structure.h" +static inline float interpolateFloat(float v1, float v2, uint32_t t1, uint32_t t2, uint32_t t) +{ + int32_t numer = t - t1, denom = t2 - t1; + return v1 + (v2 - v1) * numer/denom; +} + +Vector3uw interpolatePos(Vector3uw p1, Vector3uw p2, uint32_t t1, uint32_t t2, uint32_t t) +{ + Vector3uw ret = { interpolateInt(p1.x, p2.x, t1, t2, t), + interpolateInt(p1.y, p2.y, t1, t2, t), + interpolateInt(p1.z, p2.z, t1, t2, t) + }; + return ret; +} + +float interpolateDirection(float v1, float v2, uint32_t t1, uint32_t t2, uint32_t t) +{ + if (v1 > v2 + 180) + { + v2 += 360; + } + else if(v2 > v1 + 180) + { + v1 += 360; + } + return interpolateFloat(v1, v2, t1, t2, t); +} + +int16_t interpolateCyclicInt16(int16_t v1, int16_t v2, uint32_t t1, uint32_t t2, uint32_t t) +{ + int i1 = (uint16_t)v1; // i1: [0; 0xFFFF] v2: [-0x8000; 0x7FFF] + int i2 = v2 + ((v2 + 0x8000 - i1) & 0x10000); // i2: [i1 - 0x8000; i1 + 0x7FFF] + return interpolateInt(i1, i2, t1, t2, t); +} + +SPACETIME interpolateSpacetime(SPACETIME st1, SPACETIME st2, uint32_t t) +{ + return constructSpacetime(interpolatePos(st1.pos, st2.pos, st1.time, st2.time, t), + interpolateDirection(st1.direction, st2.direction, st1.time, st2.time, t), + interpolateCyclicInt16(st1.pitch, st2.pitch, st1.time, st2.time, t), + interpolateCyclicInt16(st1.roll, st2.roll, st1.time, st2.time, t), + t + ); +} + void checkObject(const BASE_OBJECT* psObject, const char * const location_description, const char * function, const int recurse) { if (recurse < 0) diff --git a/src/baseobject.h b/src/baseobject.h index 7bacb161f..9e4c02091 100644 --- a/src/baseobject.h +++ b/src/baseobject.h @@ -33,6 +33,21 @@ extern "C" static const unsigned int max_check_object_recursion = 4; +static inline unsigned interpolateInt(int32_t v1, int32_t v2, uint32_t t1, uint32_t t2, uint32_t t) +{ + int32_t numer = t - t1, denom = t2 - t1; + return v1 + (v2 - v1) * numer/denom; +} + +/// Get interpolated object position at time curTime. +Vector3uw interpolatePos(Vector3uw p1, Vector3uw p2, uint32_t t1, uint32_t t2, uint32_t t); +/// Get interpolated object direction at time curTime. +float interpolateDirection(float v1, float v2, uint32_t t1, uint32_t t2, uint32_t t); +/// Get interpolated object pitch at time curTime. +int16_t interpolateCyclicInt16(int16_t v1, int16_t v2, uint32_t t1, uint32_t t2, uint32_t t); +/// Get interpolated object roll at time curTime. +SPACETIME interpolateSpacetime(SPACETIME st1, SPACETIME st2, uint32_t t); + void checkObject(const BASE_OBJECT* psObject, const char * const location_description, const char * function, const int recurse); /* assert if object is bad */ diff --git a/src/display3d.c b/src/display3d.c index 3f73f60a0..119360c56 100644 --- a/src/display3d.c +++ b/src/display3d.c @@ -1052,13 +1052,19 @@ static void display3DProjectiles( void ) { switch(psObj->state) { + case PROJ_IMPACT: + if (graphicsTime > psObj->time) + { + break; // Projectile has impacted. + } + // Projectile not quite impacted, so don't break. case PROJ_INFLIGHTDIRECT: case PROJ_INFLIGHTINDIRECT: // if source or destination is visible if(gfxVisible(psObj)) { - /* don't display first frame of trajectory (projectile on firing object) */ - if ( graphicsTime != psObj->born ) + // Only display once projectile is supposed to have been spawned. + if (graphicsTime >= psObj->prevSpacetime.time) { /* Draw a bullet at psObj->pos.x for X coord psObj->pos.y for Z coord @@ -1083,9 +1089,6 @@ static void display3DProjectiles( void ) } break; - case PROJ_IMPACT: - break; - case PROJ_POSTIMPACT: break; @@ -1103,6 +1106,7 @@ void renderProjectile(PROJECTILE *psCurr) Vector3i dv; iIMDShape *pIMD; SDWORD rx, rz; + SPACETIME st; psStats = psCurr->psWStats; /* Reject flame or command since they have interim drawn fx */ @@ -1116,22 +1120,23 @@ void renderProjectile(PROJECTILE *psCurr) return; } + st = interpolateSpacetime(psCurr->prevSpacetime, GET_SPACETIME(psCurr), graphicsTime); //the weapon stats holds the reference to which graphic to use /*Need to draw the graphic depending on what the projectile is doing - hitting target, missing target, in flight etc - JUST DO IN FLIGHT FOR NOW! */ pIMD = psStats->pInFlightGraphic; - if (clipXY(psCurr->pos.x,psCurr->pos.y)) + if (clipXY(st.pos.x, st.pos.y)) { /* Get bullet's x coord */ - dv.x = (psCurr->pos.x - player.p.x) - terrainMidX*TILE_UNITS; + dv.x = (st.pos.x - player.p.x) - terrainMidX*TILE_UNITS; /* Get it's y coord (z coord in the 3d world */ - dv.z = terrainMidY*TILE_UNITS - (psCurr->pos.y - player.p.z); + dv.z = terrainMidY*TILE_UNITS - (st.pos.y - player.p.z); /* What's the present height of the bullet? */ - dv.y = psCurr->pos.z; + dv.y = st.pos.z; /* Set up the matrix */ iV_MatrixBegin(); @@ -1145,11 +1150,11 @@ void renderProjectile(PROJECTILE *psCurr) iV_TRANSLATE(rx,0,-rz); /* Rotate it to the direction it's facing */ - imdRot2.y = DEG( (int)psCurr->direction ); + imdRot2.y = DEG(st.direction); iV_MatrixRotateY(-imdRot2.y); /* pitch it */ - imdRot2.x = DEG(psCurr->pitch); + imdRot2.x = DEG(st.pitch); iV_MatrixRotateX(imdRot2.x); if (psStats->weaponSubClass == WSC_ROCKET || psStats->weaponSubClass == WSC_MISSILE diff --git a/src/projectile.c b/src/projectile.c index f7fe81abd..f925cdbfc 100644 --- a/src/projectile.c +++ b/src/projectile.c @@ -582,6 +582,9 @@ BOOL proj_SendProjectile(WEAPON *psWeap, BASE_OBJECT *psAttacker, int player, Ve counterBatteryFire(psAttacker, psTarget); } + psProj->time = gameTime; + psProj->prevSpacetime.time = gameTime; + CHECK_PROJECTILE(psProj); return true; @@ -683,16 +686,22 @@ static void proj_InFlightFunc(PROJECTILE *psProj, bool bIndirect) // Projectile is missile: bool bMissile = false; WEAPON_STATS *psStats; - Vector3uw prevPos, nextPos; + Vector3uw nextPos; unsigned int targetDistance, currentDistance; - int32_t closestCollision = 1<<30; BASE_OBJECT *psTempObj, *closestCollisionObject = NULL; - Vector3uw closestCollisionPos; + SPACETIME closestCollisionSpacetime; CHECK_PROJECTILE(psProj); + if (psProj->prevSpacetime.time == gameTime) + { + return; // Not in flight yet. + } + timeSoFar = gameTime - psProj->born; + psProj->time = gameTime; + psStats = psProj->psWStats; ASSERT(psStats != NULL, "proj_InFlightDirectFunc: Invalid weapon stats pointer"); @@ -814,7 +823,6 @@ static void proj_InFlightFunc(PROJECTILE *psProj, bool bIndirect) } /* Update position */ - prevPos = psProj->pos; psProj->pos = nextPos; if (bIndirect) @@ -823,6 +831,8 @@ static void proj_InFlightFunc(PROJECTILE *psProj, bool bIndirect) psProj->pitch = rad2degf(atan2f(psProj->vZ - (timeSoFar * ACC_GRAVITY / GAME_TICKS_PER_SEC), psProj->vXY)); } + closestCollisionSpacetime.time = 0xFFFFFFFF; + /* Check nearby objects for possible collisions */ gridStartIterate(psProj->pos.x, psProj->pos.y, PROJ_NEIGHBOUR_RANGE); for (psTempObj = gridIterate(); psTempObj != NULL; psTempObj = gridIterate()) @@ -877,7 +887,7 @@ static void proj_InFlightFunc(PROJECTILE *psProj, bool bIndirect) // FIXME HACK Needed since we got those ugly Vector3uw floating around in BASE_OBJECT... Vector3i posProj = {psProj->pos.x, psProj->pos.y, nextPosZ}, // HACK psProj->pos.z may have been set to 0, since psProj->pos.z can't be negative. So can't use Vector3uw_To3i. - prevPosProj = Vector3uw_To3i(prevPos), + prevPosProj = Vector3uw_To3i(psProj->prevSpacetime.pos), posTemp = Vector3uw_To3i(psTempObj->pos); Vector3i diff = Vector3i_Sub(posProj, posTemp); @@ -887,18 +897,16 @@ static void proj_InFlightFunc(PROJECTILE *psProj, bool bIndirect) unsigned int targetRadius = establishTargetRadius(psTempObj); int32_t collision = collisionXYZ(prevDiff, diff, targetRadius, targetHeight); + uint32_t collisionTime = psProj->prevSpacetime.time + (psProj->time - psProj->prevSpacetime.time)*collision/1024; - if (collision >= 0 && collision < closestCollision) + if (collision >= 0 && collisionTime < closestCollisionSpacetime.time) { // We hit! - //exactHitPos = prevPosProj + (posProj - prevPosProj)*collision/1024; - Vector3i exactHitPos = Vector3i_Add(prevPosProj, Vector3i_Div(Vector3i_Mult(Vector3i_Sub(posProj, prevPosProj), collision), 1024)); - exactHitPos.z = MAX(0, exactHitPos.z); // Clamp before casting to unsigned. - closestCollisionPos = Vector3i_To3uw(exactHitPos); - - closestCollision = collision; + closestCollisionSpacetime = interpolateSpacetime(psProj->prevSpacetime, GET_SPACETIME(psProj), collisionTime); closestCollisionObject = psTempObj; + closestCollisionSpacetime.pos.z = MAX(0, interpolateInt(psProj->prevSpacetime.pos.z, nextPosZ, psProj->prevSpacetime.time, psProj->time, collisionTime)); + // Keep testing for more collisions, in case there was a closer target. } } @@ -907,7 +915,11 @@ static void proj_InFlightFunc(PROJECTILE *psProj, bool bIndirect) if (closestCollisionObject != NULL) { // We hit! - psProj->pos = closestCollisionPos; + SET_SPACETIME(psProj, closestCollisionSpacetime); + if(psProj->time == psProj->prevSpacetime.time) + { + --psProj->prevSpacetime.time; + } setProjectileDestination(psProj, closestCollisionObject); // We hit something. /* Buildings cannot be penetrated and we need a penetrating weapon */ @@ -1042,11 +1054,11 @@ static void proj_ImpactFunc( PROJECTILE *psObj ) // may want to add both a fire effect and the las sat effect if (psStats->weaponSubClass == WSC_LAS_SAT) { - position.x = psObj->tarX; - position.z = psObj->tarY; + position.x = psObj->pos.x; + position.z = psObj->pos.y; // z = y [sic] intentional position.y = map_Height(position.x, position.z); addEffect(&position, EFFECT_SAT_LASER, SAT_LASER_STANDARD, false, NULL, 0); - if (clipXY(psObj->tarX, psObj->tarY)) + if (clipXY(psObj->pos.x, psObj->pos.y)) { shakeStart(); } @@ -1407,6 +1419,8 @@ static void proj_Update(PROJECTILE *psObj) { CHECK_PROJECTILE(psObj); + psObj->prevSpacetime = GET_SPACETIME(psObj); + /* See if any of the stored objects have died * since the projectile was created */ diff --git a/src/projectiledef.h b/src/projectiledef.h index 903c5cb11..3b30b1423 100644 --- a/src/projectiledef.h +++ b/src/projectiledef.h @@ -55,6 +55,7 @@ typedef struct PROJECTILE SDWORD vXY, vZ; ///< axis velocities UDWORD srcHeight; ///< Height of origin SDWORD altChange; ///< Change in altitude + SPACETIME prevSpacetime; ///< Location of projectile in previous tick. UDWORD born; UDWORD died; UDWORD expectedDamageCaused; ///< Expected damage that this projectile will cause to the target.