#include #include #include #include "object.h" #include "PHYSICS.H" #include "collide.h" #include "findintersection.h" #include "vecmat.h" #include "game.h" #include "terrain.h" #include "descent.h" #include "weapon.h" #include "polymodel.h" #include "fireball.h" #include "damage.h" #include "gameevent.h" #include "hlsoundlib.h" #include "soundload.h" #include "viseffect.h" #include "multi.h" #include "attach.h" #include "D3ForceFeedback.h" #include "player.h" #include "demofile.h" #include "vibeinterface.h" // Global variables for physics system ubyte Default_player_terrain_leveling = 0; ubyte Default_player_room_leveling = 0; // Maximum iterations thru the simulation loop NOTE: It is assumed that the player loops >= non-player loops #define MAX_PLAYER_SIM_LOOPS 9 #define MAX_NON_PLAYER_SIM_LOOPS 5 #define CEILING_POWERUP_DELTA 30.0f #define WIGGLE_FALLOFF_TIME 2.0 int Physics_normal_counter; int Physics_normal_looping_counter; int Physics_walking_counter; int Physics_walking_looping_counter; int Physics_vis_counter; #ifdef _DEBUG // This will allow us to debug physics in a better way. typedef struct sim_loop_info { vector start_pos; physics_info phys_info; // At beginnning of loop fvi_info hit_info; // Hit information returned by FVI } sim_loop_info; sim_loop_info sim_loop_record[MAX_PLAYER_SIM_LOOPS]; #endif #ifdef _DEBUG int Physics_player_verbose = 0; #endif #define PHYSICS_GROUND_TOLERANCE 0.0001f #define DAMP_ANG 1.0f // chrishack -- this is probably too tight and should be an int not a float #define MAX_OBJECT_VEL 100000.0f int Physics_cheat_flag = 0; extern char BounceCheat; int PhysicsLinkList[MAX_OBJECTS]; int Physics_NumLinked = 0; // Current strength of gravity float Gravity_strength = -32.2f; // Meters/second^2 void DoPhysLinkedFrame(object *obj) { object *parent = ObjGet(obj->mtype.obj_link_info.parent_handle); matrix new_orient; vector new_pos; if (parent && parent->type != OBJ_GHOST && parent->type != OBJ_DUMMY) { poly_model *pm; vector pnt; vector fvec; vector uvec; matrix m; int mn; // submodel number float normalized_time[MAX_SUBOBJECTS]; pm = &Poly_models[parent->rtype.pobj_info.model_num]; ASSERT(pm->used); SetNormalizedTimeObj(parent, normalized_time); SetModelAnglesAndPos(pm, normalized_time); pnt = obj->mtype.obj_link_info.pos; fvec = obj->mtype.obj_link_info.fvec; uvec = obj->mtype.obj_link_info.uvec; mn = obj->mtype.obj_link_info.sobj_index; if (mn < 0 || mn >= pm->n_models) { mprintf((0, "Caught physics link bug!\n")); SetObjectDeadFlag(obj); return; } // Instance up the tree for this gun while (mn != -1) { vector tpnt; vm_AnglesToMatrix(&m, pm->submodel[mn].angs.p, pm->submodel[mn].angs.h, pm->submodel[mn].angs.b); vm_TransposeMatrix(&m); tpnt = pnt * m; fvec = fvec * m; uvec = uvec * m; pnt = tpnt + pm->submodel[mn].offset + pm->submodel[mn].mod_pos; mn = pm->submodel[mn].parent; } // now instance for the entire object m = parent->orient; vm_TransposeMatrix(&m); new_pos = pnt * m; fvec = fvec * m; uvec = uvec * m; vm_VectorToMatrix(&new_orient, &fvec, &uvec, NULL); new_pos += parent->pos; ObjSetPos(obj, &new_pos, parent->roomnum, &new_orient, false); } else { SetObjectDeadFlag(obj); } } bool PhysCalcGround(vector *ground_point, vector *ground_normal, object *obj, int ground_num) { poly_model *pm; vector pnt; vector normal; matrix m; int mn; // submodel number float normalized_time[MAX_SUBOBJECTS]; bool f_good_gp = true; if (obj->render_type != RT_POLYOBJ && obj->type != OBJ_PLAYER) { // mprintf ((0,"Object type %d is not a polyobj!\n",obj->type)); if (ground_point) *ground_point = obj->pos; if (ground_normal) *ground_normal = obj->orient.fvec; return false; } pm = &Poly_models[obj->rtype.pobj_info.model_num]; if (pm->n_ground == 0) { mprintf((0, "WARNING: Object with no weapons is firing.\n", ground_num)); if (ground_point) *ground_point = obj->pos; if (ground_normal) *ground_normal = obj->orient.fvec; return false; } SetNormalizedTimeObj(obj, normalized_time); SetModelAnglesAndPos(pm, normalized_time); if (ground_num < 0 || ground_num >= pm->n_ground) { mprintf((0, "WARNING: Bashing ground num %d to 0.\n", ground_num)); ground_num = 0; f_good_gp = false; } pnt = pm->ground_slots[ground_num].pnt; normal = pm->ground_slots[ground_num].norm; mn = pm->ground_slots[ground_num].parent; // Instance up the tree for this ground while (mn != -1) { vector tpnt; vm_AnglesToMatrix(&m, pm->submodel[mn].angs.p, pm->submodel[mn].angs.h, pm->submodel[mn].angs.b); vm_TransposeMatrix(&m); tpnt = pnt * m; normal = normal * m; pnt = tpnt + pm->submodel[mn].offset + pm->submodel[mn].mod_pos; mn = pm->submodel[mn].parent; } m = obj->orient; vm_TransposeMatrix(&m); if (ground_point) *ground_point = pnt * m; if (ground_normal) *ground_normal = normal * m; if (ground_point) *ground_point += obj->pos; return f_good_gp; } // Applies rotational thrust over at delta time void PhysicsApplyConstRotForce(object &objp, const vector &rotforce, vector &rotvel, float deltaTime) { const double drag = objp.mtype.phys_info.rotdrag; const double mass = objp.mtype.phys_info.mass; if (mass < std::numeric_limits::epsilon() || drag < std::numeric_limits::epsilon()) { // Invalid mass/drag rotvel += rotforce * deltaTime; return; } // Standard angular motion with a linear air drag (drag is proportional to angular velocity) const double oneOverDrag = 1.0 / drag; const double rotForceOverDrag[3] = {double(rotforce.x) * oneOverDrag, double(rotforce.y) * oneOverDrag, double(rotforce.z) * oneOverDrag}; const double dragOverMass = drag / mass; const double expDoMDt = exp(-dragOverMass * double(deltaTime)); double newRotVel[3]; newRotVel[0] = (double(rotvel.x) - rotForceOverDrag[0]) * expDoMDt + rotForceOverDrag[0]; newRotVel[1] = (double(rotvel.y) - rotForceOverDrag[1]) * expDoMDt + rotForceOverDrag[1]; newRotVel[2] = (double(rotvel.z) - rotForceOverDrag[2]) * expDoMDt + rotForceOverDrag[2]; // set the new rotational velocity rotvel.x = static_cast(newRotVel[0]); rotvel.y = static_cast(newRotVel[1]); rotvel.z = static_cast(newRotVel[2]); } // Applies a linear force over time. --Like gravity or thrust void PhysicsApplyConstantForce(const object &objp, vector &newPos, vector &newVel, vector &movementVec, const vector &force, float deltaTime) { const vector &pos = objp.pos; const vector &vel = objp.mtype.phys_info.velocity; const double drag = static_cast(objp.mtype.phys_info.drag); const double mass = static_cast(objp.mtype.phys_info.mass); if (mass < std::numeric_limits::epsilon() || drag < std::numeric_limits::epsilon()) { // No Mass/Drag movementVec = (vel * deltaTime) + (force * (0.5f * deltaTime * deltaTime)); newPos = pos + movementVec; newVel = vel + force * deltaTime; return; } // Standard motion with a linear air drag (drag is proportional to velocity) const double dt = static_cast(deltaTime); const double oneOverDrag = 1.0 / drag; const double massOverDrag = mass / drag; const double forceOverDrag[3] = {force.x * oneOverDrag, force.y * oneOverDrag, force.z * oneOverDrag}; const double objVel[3] = {static_cast(vel.x), static_cast(vel.y), static_cast(vel.z)}; const double expDoMDt = exp((-1.0 / massOverDrag) * dt); newPos.x = static_cast(static_cast(pos.x) + forceOverDrag[0] * dt + massOverDrag * (objVel[0] - forceOverDrag[0]) * (1.0 - expDoMDt)); newPos.y = static_cast(static_cast(pos.y) + forceOverDrag[1] * dt + massOverDrag * (objVel[1] - forceOverDrag[1]) * (1.0 - expDoMDt)); newPos.z = static_cast(static_cast(pos.z) + forceOverDrag[2] * dt + massOverDrag * (objVel[2] - forceOverDrag[2]) * (1.0 - expDoMDt)); movementVec = newPos - pos; newVel.x = static_cast((objVel[0] - forceOverDrag[0]) * expDoMDt + forceOverDrag[0]); newVel.y = static_cast((objVel[1] - forceOverDrag[1]) * expDoMDt + forceOverDrag[1]); newVel.z = static_cast((objVel[2] - forceOverDrag[2]) * expDoMDt + forceOverDrag[2]); } // Banks an object as it turns (we counteract this and then reapply it when we // actually do the turn -- cool) void set_object_turnroll(object *obj, vector *rotvel, angle *turnroll) { float desired_bank; angle desired_bank_angle; desired_bank = -(rotvel->y) * obj->mtype.phys_info.turnroll_ratio; // If desired bank > 32000, we will rotate the ship in weird ways. Should limit turn-roll to 32000 if this // ever occurs. BTW This is where the limiter should be placed. if (fabs(desired_bank) > 32000.0) { if (desired_bank < 0.0) desired_bank = -32000.0f; else desired_bank = 32000.0f; } if (desired_bank > -1.0) // accounts for -.9999 this is rounded to 0 in a float to int conversion desired_bank_angle = desired_bank; else desired_bank_angle = 65535 + desired_bank; if (*turnroll != desired_bank_angle) { int delta_ang; int max_roll; max_roll = obj->mtype.phys_info.max_turnroll_rate * Frametime; if (*turnroll > 32000) delta_ang = desired_bank - (*turnroll - 65535); else delta_ang = desired_bank - *turnroll; if (labs(delta_ang) > max_roll) if (delta_ang > 0) *turnroll += max_roll; else *turnroll -= max_roll; else *turnroll = desired_bank_angle; } else *turnroll = desired_bank_angle; } // Maximum number of objects that we already hit. #define MAX_IGNORE_OBJS 100 // When leveling non-player objects - how many angles per sec. do they correct with. #define MAX_LEVEL_ANGLES_PER_SEC 30000.0f // Base scaler for pitch scaling of autoleveling #define MAX_AUTOLEVEL_TILT_ANGLE 11000 // Multiplier for newbie autoleveling scaling of tilt angle #define NEWBIE_AUTOLEVEL_TILT_ANGLE_SCALAR 1.25f // How long a newbie sits there before the pitch leveling kicks in #define NEWBIE_PITCH_LEVEL_TIME 1.5f #define BANK_AUTOLEVEL_SPEED_SCALAR 2.0 // Rotational simulator the updates orient, rotvel, and turnroll // (using these original values + frametime + rotthrust) bool PhysicsDoSimRot(object *obj, float frame_time, matrix *orient, vector *rotthrust, vector *rotvel, angle *turnroll) { angvec tangles; matrix rotmat; physics_info *pi; bool f_leveling = false; float max_tilt_angle; bool f_newbie_leveling = false; if (frame_time <= 0.0) return false; // chrishack pi = &obj->mtype.phys_info; if (obj == Player_object && obj->control_type != CT_AI) { max_tilt_angle = MAX_AUTOLEVEL_TILT_ANGLE; if (OBJECT_OUTSIDE(obj)) { if (Default_player_terrain_leveling) { f_leveling = true; if (Default_player_terrain_leveling > 1) { max_tilt_angle *= NEWBIE_AUTOLEVEL_TILT_ANGLE_SCALAR; f_newbie_leveling = true; } } } else { if (Default_player_room_leveling) { f_leveling = true; if (Default_player_room_leveling > 1) { max_tilt_angle *= NEWBIE_AUTOLEVEL_TILT_ANGLE_SCALAR; f_newbie_leveling = true; } } } } else { if (pi->flags & PF_LEVELING) f_leveling = true; } // Fixed rate rotaters if (obj->mtype.phys_info.flags & PF_FIXED_ROT_VELOCITY) { tangles.p = rotvel->x * frame_time; tangles.h = rotvel->y * frame_time; tangles.b = rotvel->z * frame_time; vm_AnglesToMatrix(&rotmat, tangles.p, tangles.h, tangles.b); *orient = *orient * rotmat; // ObjSetOrient below vm_Orthogonalize(orient); // Rest done after call return true; } // if(!(f_leveling) && fabs(rotthrust->x) < .000001 && fabs(rotthrust->y) < .000001 && fabs(rotthrust->z) < //.000001) // { // if(!(fabs(rotvel->x) > .000001 || fabs(rotvel->y) > .000001 || fabs(rotvel->z) > .000001 || *turnroll != // 0)) // { // return false; // } // } // now rotate object // unrotate object for bank caused by turn if (*turnroll != 0) { tangles.p = tangles.h = 0; tangles.b = -(*turnroll); vm_AnglesToMatrix(&rotmat, tangles.p, tangles.h, tangles.b); // Apply rotation matrix to the orientation matrix *orient = *orient * rotmat; // ObjSetOrient is below } // Auto-leveling if ((f_leveling) && (obj->type != OBJ_PLAYER)) { if (fabs(rotthrust->z) < 100.0f) { if ((fabs(rotvel->z) < 1500.0f) /*&& (pi->turnroll <= 10 || pi->turnroll >= 65535 - 10)*/) { angvec ang; vector fvec; int bound; // f_leveling = true; // pi->turnroll = 0; // extract angles from a matrix vm_ExtractAnglesFromMatrix(&ang, orient); if ((pi->flags & PF_TURNROLL) && *turnroll) { bound = *turnroll; if (bound > 32768) { bound = 65535 - bound; } } else { bound = 10; } if (ang.b > bound && ang.b < 65535 - bound) // About 1 degree { float scale; // Scale on pitch if (ang.p < 32768) { scale = abs(16834 - (int)ang.p) / 16384.0f; } else { scale = abs(49152 - (int)ang.p) / 16384.0f; } // mprintf((0, "scale %f, ", scale)); if (ang.b < 32768) { float al_rate = scale * MAX_LEVEL_ANGLES_PER_SEC * (1.0f - (abs(16834 - (int)ang.b) / 16384.0f)); if (al_rate < scale * 5000.0f) { al_rate = scale * 5000.0f; } al_rate *= frame_time; if (al_rate > ang.b) al_rate = ang.b; // mprintf((0, "L al_rate %f\n", al_rate)); ang.b -= al_rate; } else { float al_rate = scale * MAX_LEVEL_ANGLES_PER_SEC * (1.0f - (abs(16384 - ((int)ang.b - 32768)) / 16384.0f)); if (al_rate < scale * 5000.0f) { al_rate = scale * 5000.0f; } al_rate *= frame_time; if (al_rate > 65535 - ang.b) al_rate = 65535 - ang.b; // mprintf((0, "G al_rate %f\n", al_rate)); ang.b += al_rate; } fvec = orient->fvec; vm_AnglesToMatrix(orient, ang.p, ang.h, ang.b); } } } } else if ((f_leveling) && (obj->type == OBJ_PLAYER)) { bool f_pitch_leveled = false; bool f_player_fired_recently = false; int i; for (i = 0; i < 21; i++) { if (obj->dynamic_wb[i].last_fire_time + NEWBIE_PITCH_LEVEL_TIME > Gametime) { f_player_fired_recently = true; f_newbie_leveling = false; break; } } if (!f_player_fired_recently && f_newbie_leveling && (Players[obj->id].last_thrust_time + NEWBIE_PITCH_LEVEL_TIME < Gametime)) { if (1) { angvec ang; int bound; // f_leveling = true; // pi->turnroll = 0; // extract angles from a matrix vm_ExtractAnglesFromMatrix(&ang, orient); bound = 750; // About 1 degree -- front or back if ((ang.p > bound) && (ang.p < 65535 - bound) && // Forward abs(ang.p - 32768) > bound) // Backward { float scale; f_pitch_leveled = true; if (obj->orient.uvec.y < 0.0f) { ang.p += 16384; } scale = 1.05f - fabs(obj->orient.uvec.y); // scale *= scale; if (scale > 1.0) scale = 1.0; if (ang.p < 16834) { // float temp_scale = (1.04f - ((16834 - ang.p)/16834.0f)); // scale = temp_scale * temp_scale; rotthrust->x -= scale * obj->mtype.phys_info.full_rotthrust; // mprintf((0, "1 scale %f\n", scale)); } else if (ang.p < 32768) { // float temp_scale = (1.04f - ((ang.p - 16834)/16834.0f)); // scale = temp_scale * temp_scale; rotthrust->x += scale * obj->mtype.phys_info.full_rotthrust; // mprintf((0, "2 scale %f\n", scale)); } else if (ang.p < 49152) { // float temp_scale = (1.04f - ((49152 - ang.p)/16834.0f)); // scale = temp_scale * temp_scale; rotthrust->x -= scale * obj->mtype.phys_info.full_rotthrust; // mprintf((0, "3 scale %f\n", scale)); } else { // float temp_scale = (1.04f - ((ang.p - 49152)/16834.0f)); // scale = temp_scale * temp_scale; rotthrust->x += scale * obj->mtype.phys_info.full_rotthrust; // mprintf((0, "4 scale %f\n", scale)); } } } } if (!f_pitch_leveled && fabs(rotthrust->z) < 100.0f) { if (1) ///*&& (pi->turnroll <= 10 || pi->turnroll >= 65535 - 10)*/) { angvec ang; int bound; // f_leveling = true; // pi->turnroll = 0; // extract angles from a matrix vm_ExtractAnglesFromMatrix(&ang, orient); if ((ang.p < 32768 && (abs((int)ang.p - (int)16834) > max_tilt_angle)) || (ang.p >= 32768 && (abs((int)ang.p - (int)49152) > max_tilt_angle))) { if ((pi->flags & PF_TURNROLL) && *turnroll) { bound = *turnroll; if (bound > 32768) { bound = 65535 - bound; } } else { bound = 10; } if (ang.b > bound && ang.b < 65535 - bound) // About 1 degree { float scale; // vector level_rotthrust = Zero_vector; // Scale on pitch if (ang.p < 32768) { scale = (abs(16834 - (int)ang.p) - max_tilt_angle) / (16384.0f - max_tilt_angle); } else { scale = (abs(49152 - (int)ang.p) - max_tilt_angle) / (16384.0f - max_tilt_angle); } // mprintf((0, "scale %f, ", scale)); if (ang.b < 32768) { float temp_scale; // if(temp_scale * temp_scale > .05) if (ang.b > 28672) { temp_scale = (1.04f - ((28672 - 16834) / 16384.0f)); } else { temp_scale = (1.04f - (abs(16834 - (int)ang.b) / 16384.0f)); } temp_scale *= temp_scale; scale *= temp_scale; if (f_player_fired_recently) scale *= .25f; rotthrust->z -= scale * BANK_AUTOLEVEL_SPEED_SCALAR * obj->mtype.phys_info.full_rotthrust; } else { float temp_scale; if (ang.b < 36864) { temp_scale = (1.04f - ((49152 - 36864) / 16384.0f)); } else { temp_scale = (1.04f - (abs(49152 - (int)ang.b) / 16384.0f)); } temp_scale *= temp_scale; scale *= temp_scale; if (f_player_fired_recently) scale *= .25f; rotthrust->z += scale * BANK_AUTOLEVEL_SPEED_SCALAR * obj->mtype.phys_info.full_rotthrust; } // PhysicsApplyConstRotForce(*obj, level_rotthrust, *rotvel, // frame_time); } } } } } if (pi->rotdrag > 0.0f) { if (!(pi->flags & PF_USES_THRUST)) { *rotthrust = Zero_vector; } PhysicsApplyConstRotForce(*obj, *rotthrust, *rotvel, frame_time); } // Apply rotation to the "un-rollbanked" object tangles.p = rotvel->x * frame_time; tangles.h = rotvel->y * frame_time; tangles.b = rotvel->z * frame_time; vm_AnglesToMatrix(&rotmat, tangles.p, tangles.h, tangles.b); *orient = *orient * rotmat; // ObjSetOrient is below // Determine the new turnroll from this amount of turning if (pi->flags & PF_TURNROLL) { set_object_turnroll(obj, rotvel, turnroll); } // re-rotate object for bank caused by turn if (*turnroll != 0) { tangles.p = tangles.h = 0; tangles.b = *turnroll; vm_AnglesToMatrix(&rotmat, tangles.p, tangles.h, tangles.b); *orient = *orient * rotmat; // ObjSetOrient is below } // mprintf((0, " a %f, %f, %f,\n%f, %f, %f,\n%f %f %f\n\n", XYZ(&obj->orient.fvec), XYZ(&obj->orient.rvec), // XYZ(&obj->orient.uvec))); // Make sure the new orientation is valid vm_Orthogonalize(orient); return true; } void PhysicsDoSimLinear(const object &obj, const vector &pos, const vector &force, vector &velocity, vector &movementVec, vector &movementPos, float simTime, int count) { // Determine velocity if (obj.mtype.phys_info.flags & PF_FIXED_VELOCITY) { movementVec = velocity * simTime; movementPos = pos + movementVec; return; } // Help so that we do not loss velocity when sliding (still framerate dependent -- fix with force stuff) // if (!(count == 0 || (count > 0 && (~obj->mtype.phys_info.flags & (PF_BOUNCE | PF_GRAVITY))))) // return; vector forceToUse = force; if (!ROOMNUM_OUTSIDE(obj.roomnum) && !(obj.mtype.phys_info.flags & PF_LOCK_MASK) && Rooms[obj.roomnum].wind != Zero_vector && count == 0 && obj.mtype.phys_info.drag > 0.0f && !(obj.type == OBJ_POWERUP && Level_powerups_ignore_wind)) { // Factor in outside wind const vector &wind = Rooms[obj.roomnum].wind; vector deltaForce = wind * obj.mtype.phys_info.drag * 16.0f; forceToUse += deltaForce; } PhysicsApplyConstantForce(obj, movementPos, velocity, movementVec, forceToUse, simTime); #ifdef _DEBUG if (Physics_player_verbose && obj.type == OBJ_PLAYER) { mprintf((0, "Player Velocity = %f(%f)\n", vm_GetMagnitude(&velocity), vm_GetMagnitude(&movementVec) / simTime)); } #endif } // ----------------------------------------------------------------------------------------------------------- // Simulate a physics object for this frame void do_physics_sim(object *obj) { // Since we might not call FVI, set this here Fvi_num_recorded_faces = 0; int n_ignore_objs = 0; // The number of ignored objects int ignore_obj_list[MAX_IGNORE_OBJS + 1]; // List of ignored objects int fate; // Collision type for response code vector movement_vec; // Movement in this frame vector movement_pos; // End point of the movement int count = 0; // Simulation loop counter int sim_loop_limit = (obj->type == OBJ_PLAYER) ? MAX_PLAYER_SIM_LOOPS : MAX_NON_PLAYER_SIM_LOOPS; int objnum = OBJNUM(obj); // Simulated object's index fvi_info hit_info; // Hit information fvi_query fq; // Movement query float sim_time_remaining = Frametime; // Amount of simulation time remaining (current iteration) float old_sim_time_remaining = Frametime; // Amount of simulation time remaining (previous iteration) vector init_pos = obj->pos; // Initial position int init_room = obj->roomnum; // Initial room vector start_pos; // Values at the start of current simulation loop vector start_vel; matrix start_orient; vector start_rotvel; angle start_turnroll; int start_room; float moved_time; // How long objected moved before hit something physics_info *pi = &obj->mtype.phys_info; // Simulated object's physics information int bounced = 0; // Did the object bounce? vector total_force; // Constant force acting on an object bool f_continue_sim; // Should we run another simulation loop bool f_start_fvi_record = true; // Records the rooms that are passed thru bool f_turn_gravity_on = false; // Assert conditions ASSERT(obj->type != OBJ_NONE); ASSERT(obj->movement_type == MT_PHYSICS); ASSERT(!(obj->mtype.phys_info.flags & PF_USES_THRUST) || obj->mtype.phys_info.drag != 0.0); DebugBlockPrint("P "); ASSERT(_finite(obj->mtype.phys_info.velocity.x) != 0); // Caller wants to go to infinity! -- Not FVI's fault. ASSERT(_finite(obj->mtype.phys_info.velocity.y) != 0); // Caller wants to go to infinity! -- Not FVI's fault. ASSERT(_finite(obj->mtype.phys_info.velocity.z) != 0); // Caller wants to go to infinity! -- Not FVI's fault. #ifdef _DEBUG if (!Game_do_flying_sim) { return; } #endif // Make powerups above the ceiling fall with gravity if (obj->type == OBJ_POWERUP && ROOMNUM_OUTSIDE(obj->roomnum) && obj->pos.y > (CEILING_HEIGHT - obj->size - CEILING_POWERUP_DELTA) && !(obj->mtype.phys_info.flags & PF_GRAVITY)) { obj->mtype.phys_info.flags |= PF_GRAVITY; obj->flags |= OF_TEMP_GRAVITY; } else if (obj->flags & OF_TEMP_GRAVITY) { obj->mtype.phys_info.flags &= ~PF_GRAVITY; } // Instant bail cases if ((obj->flags & (OF_DEAD | OF_ATTACHED)) || (Frametime <= 0.0) || (obj->type == OBJ_DUMMY)) { return; } if (obj->type == OBJ_PLAYER && (obj->mtype.phys_info.thrust != Zero_vector || obj->mtype.phys_info.rotthrust != Zero_vector)) { Players[obj->id].last_thrust_time = Gametime; } // mprintf((0, "%d Over terrain = %d\n", obj - Objects, obj->flags & OF_OVER_TERRAIN)); // If the object wiggles if ((obj->mtype.phys_info.flags & PF_WIGGLE) && (Demo_flags != DF_PLAYBACK)) { float swiggle; vector w_pos; if (vm_GetMagnitude(&obj->mtype.phys_info.thrust) < .1) { obj->mtype.phys_info.last_still_time -= Frametime / WIGGLE_FALLOFF_TIME; } else { obj->mtype.phys_info.last_still_time += Frametime / WIGGLE_FALLOFF_TIME; } if (obj->mtype.phys_info.last_still_time < 0.0) obj->mtype.phys_info.last_still_time = 0.0; else if (obj->mtype.phys_info.last_still_time > 1.0) obj->mtype.phys_info.last_still_time = 1.0; // Ship always keeps 10% of its wiggle // if(obj->mtype.phys_info.last_still_time < 1.0) { float scaler = 1.0f - obj->mtype.phys_info.last_still_time; if (scaler < .1f) scaler = .1f; swiggle = obj->mtype.phys_info.wiggle_amplitude * (scaler) * (FixSin((angle)((int)(Gametime * obj->mtype.phys_info.wiggles_per_sec * 65535) % 65535)) - FixSin((angle)((int)((Gametime - Frametime) * obj->mtype.phys_info.wiggles_per_sec * 65535) % 65535))); w_pos = obj->pos + obj->orient.uvec * (swiggle); fq.p0 = &obj->pos; fq.startroom = obj->roomnum; fq.p1 = &w_pos; fq.rad = obj->size; fq.thisobjnum = objnum; fq.ignore_obj_list = NULL; fq.flags = FQ_CHECK_OBJS | FQ_RECORD | FQ_IGNORE_POWERUPS; fq.flags |= FQ_NEW_RECORD_LIST; fq.flags |= FQ_CHECK_CEILING; // Need for weapons to go thru transparent areas of a texture if (obj->type == OBJ_WEAPON) fq.flags |= FQ_TRANSPOINT; if (obj->flags & OF_NO_OBJECT_COLLISIONS) fq.flags &= ~FQ_CHECK_OBJS; fate = fvi_FindIntersection(&fq, &hit_info); // Only wiggle on a non-hit case if (fate == HIT_NONE) { ASSERT(hit_info.hit_room != -1); f_start_fvi_record = false; // update object's position and segment number ObjSetPos(obj, &hit_info.hit_pnt, hit_info.hit_room, NULL, false); } } } // If the object is effected by gravity (normal, lighter than air, and anti-gravity) if (obj->mtype.phys_info.flags & PF_GRAVITY) { total_force.x = total_force.z = 0.0; total_force.y = Gravity_strength * obj->mtype.phys_info.mass; } else if (obj->mtype.phys_info.flags & PF_REVERSE_GRAVITY) { total_force.x = total_force.z = 0.0; total_force.y = -Gravity_strength * obj->mtype.phys_info.mass; } else { total_force.x = total_force.y = total_force.z = 0.0; } obj->flags &= (~OF_STOPPED_THIS_FRAME); // Do rotation velocity/accel stuff bool f_rotated = false; if (!(fabs(pi->velocity.x) > .000001 || fabs(pi->velocity.y) > .000001 || fabs(pi->velocity.z) > .000001 || fabs(pi->thrust.x) > .000001 || fabs(pi->thrust.y) > .000001 || fabs(pi->thrust.z) > .000001 || fabs(pi->rotvel.x) > .000001 || fabs(pi->rotvel.y) > .000001 || fabs(pi->rotvel.z) > .000001 || fabs(pi->rotthrust.x) > .000001 || fabs(pi->rotthrust.y) > .000001 || fabs(pi->rotthrust.z) > .000001 || (obj->mtype.phys_info.flags & PF_GRAVITY) || ((!ROOMNUM_OUTSIDE(obj->roomnum)) && Rooms[obj->roomnum].wind != Zero_vector) || (obj->mtype.phys_info.flags & PF_WIGGLE) || ((obj->ai_info != NULL) && ((obj->ai_info->flags & AIF_REPORT_NEW_ORIENT) != 0)))) { if ((obj->flags & OF_MOVED_THIS_FRAME)) { obj->flags &= (~OF_MOVED_THIS_FRAME); obj->flags |= OF_STOPPED_THIS_FRAME; } return; } if (obj->ai_info) obj->ai_info->flags &= ~AIF_REPORT_NEW_ORIENT; obj->flags |= OF_MOVED_THIS_FRAME; // This assumes that the thrust does not change within a frame. If it does, account for it // there... like missiles bouncing off a wall and changing heading. --chrishack if (obj->mtype.phys_info.drag != 0.0) { if (obj->mtype.phys_info.flags & PF_USES_THRUST) { total_force += obj->mtype.phys_info.thrust; } } // mprintf((0, "PHYSICS: Current obj: %d, %9.2fx %9.2fy %9.2fz\n", OBJNUM(obj), XYZ(&obj->pos))); // mprintf((2,1,0,"x %9.2f\ny %9.2f\nz %9.2f", XYZ(&obj->mtype.phys_info.velocity))); // mprintf((0, "PHYSICS: Current velocity (%f, %f, %f)\n", XYZ(&obj->mtype.phys_info.velocity))); Physics_normal_counter++; // Simulate movement until we are done (i.e. Frametime has passed or object is done moving) do { // Values at start of sim loop start_pos = obj->pos; start_vel = obj->mtype.phys_info.velocity; start_orient = obj->orient; start_rotvel = obj->mtype.phys_info.rotvel; start_turnroll = obj->mtype.phys_info.turnroll; start_room = obj->roomnum; matrix end_orient = start_orient; vector end_rotvel = start_rotvel; angle end_turnroll = start_turnroll; vector end_vel = start_vel; #ifdef _DEBUG // This records the sim. sim_loop_record[count].phys_info = obj->mtype.phys_info; #endif // Initailly assume that this is the last sim cycle f_continue_sim = false; // Determines forces if (count > 0) { // If the object is effected by gravity (normal, lighter than air, and anti-gravity) if (obj->mtype.phys_info.flags & PF_GRAVITY) { total_force.x = total_force.z = 0.0; total_force.y = Gravity_strength * obj->mtype.phys_info.mass; } else if (obj->mtype.phys_info.flags & PF_REVERSE_GRAVITY) { total_force.x = total_force.z = 0.0; total_force.y = -Gravity_strength * obj->mtype.phys_info.mass; } else if (!(obj->mtype.phys_info.flags & PF_BOUNCE)) { // Player ship } else { total_force.x = total_force.y = total_force.z = 0.0; } } // Compute rotational information // Chrishack -- replace OBJ_CLUTTER with a BBOX_HIT_TYPE bool f_set_orient; if ((f_rotated == false && count == 0) || obj->type == OBJ_CLUTTER) { f_rotated = true; f_set_orient = true; PhysicsDoSimRot(obj, sim_time_remaining, &end_orient, &obj->mtype.phys_info.rotthrust, &end_rotvel, &end_turnroll); /* vector init_pos = obj->pos + obj->wall_sphere_offset;*/ ObjSetOrient(obj, &end_orient); /* vector new_pos = obj->pos + obj->wall_sphere_offset; if(obj->wall_sphere_offset != Zero_vector && obj->type == OBJ_ROBOT && !(obj->mtype.phys_info.flags & PF_POINT_COLLIDE_WALLS)) { fq.p0 = &init_pos; fq.startroom = obj->roomnum; fq.p1 = &new_pos; fq.rad = Poly_models[obj->rtype.pobj_info.model_num].wall_size; fq.thisobjnum = objnum; fq.ignore_obj_list = NULL; fq.flags = 0; fate = fvi_FindIntersection(&fq,&hit_info); // Only wiggle on a non-hit case if(fate != HIT_NONE) { end_orient = start_orient; ObjSetOrient(obj, &start_orient); } }*/ } else { f_set_orient = false; } // Updates the thrust vector to the orientation for homing weapons if ((obj->type == OBJ_WEAPON) && (pi->flags & PF_USES_THRUST) && (pi->flags & PF_HOMING)) { pi->thrust = end_orient.fvec * pi->full_thrust; movement_vec = obj->mtype.phys_info.velocity * Frametime; movement_pos = obj->pos + movement_vec; } PhysicsDoSimLinear(*obj, obj->pos, total_force, end_vel, movement_vec, movement_pos, sim_time_remaining, count); // Only do velocity until we've reached our destination position // This is useful for multiplayer if (obj->mtype.phys_info.flags & PF_DESTINATION_POS) { vector sub1 = obj->pos - obj->mtype.phys_info.dest_pos; vector sub2 = movement_pos - obj->mtype.phys_info.dest_pos; if (vm_DotProduct(&sub1, &sub2) <= 0) { obj->mtype.phys_info.flags &= ~PF_DESTINATION_POS; vm_MakeZero(&obj->mtype.phys_info.velocity); vm_MakeZero(&movement_vec); movement_pos = obj->pos; goto end_of_sim; } if ((vm_VectorDistanceQuick(&obj->mtype.phys_info.dest_pos, &obj->pos)) < 1) { vm_MakeZero(&obj->mtype.phys_info.velocity); obj->mtype.phys_info.flags &= ~PF_DESTINATION_POS; goto end_of_sim; } } // We are done, as there is no collision response with PF_NO_COLLIDE if (obj->mtype.phys_info.flags & PF_NO_COLLIDE) { obj->mtype.phys_info.rotvel = end_rotvel; obj->mtype.phys_info.turnroll = end_turnroll; obj->mtype.phys_info.velocity = end_vel; // ObjSetOrient(obj,&end_orient); ObjSetPos(obj, &movement_pos, obj->roomnum, NULL, false); ObjSetAABB(obj); obj->last_pos = init_pos; goto end_of_sim; } // If we are stationary, we are done :) -- breaks on high framerates (Gravity) // if(vm_GetMagnitude(&movement_vec) < 0.00001) // if(vm_GetMagnitude(&start_vel) < 0.00001) // if(vm_GetMagnitude(&end_vel) < 0.00001) // if(vm_GetMagnitude(&obj->mtype.phys_info.thrust) < 0.00001) // if(vm_GetMagnitude(&obj->mtype.phys_info.rotthrust) < .0001) // if(vm_GetMagnitude(&start_rotvel) < .0001) // if(vm_GetMagnitude(&end_rotvel) < .0001) // { // mprintf((0, "At rest\n")); // goto end_of_sim; // } // mprintf((0, "PHYSICS: Try to move to (%f, %f, %f)\n", movement_pos.x, movement_pos.y, movement_pos.z)); Physics_normal_looping_counter++; // Cap ignore list -- (Objects we already hit this frame) ignore_obj_list[n_ignore_objs] = -1; vector temp_vel = obj->mtype.phys_info.velocity; obj->mtype.phys_info.velocity = (movement_pos - start_pos) / sim_time_remaining; fq.p0 = &obj->pos; fq.startroom = obj->roomnum; fq.p1 = &movement_pos; fq.rad = obj->size; fq.thisobjnum = objnum; fq.ignore_obj_list = ignore_obj_list; fq.flags = FQ_CHECK_OBJS | FQ_RECORD; if (f_start_fvi_record) fq.flags |= FQ_NEW_RECORD_LIST; fq.o_orient = &start_orient; fq.o_rotvel = &start_rotvel; fq.o_rotthrust = &obj->mtype.phys_info.rotthrust; fq.o_turnroll = &start_turnroll; fq.o_velocity = &start_vel; fq.o_thrust = &total_force; f_start_fvi_record = false; // Need for weapons to go thru transparent areas of a texture if (obj->type == OBJ_WEAPON) fq.flags |= FQ_TRANSPOINT; // Added a ceiling for players and powerups if (obj->type == OBJ_POWERUP || (obj->type == OBJ_PLAYER && obj->control_type != CT_AI) || obj->type == OBJ_OBSERVER || (obj == Players[Player_object->id].guided_obj) || (obj->flags & OF_FORCE_CEILING_CHECK)) fq.flags |= FQ_CHECK_CEILING; if ((obj->type == OBJ_WEAPON) && (obj->ctype.laser_info.hit_status != WPC_NOT_USED)) fq.flags |= FQ_IGNORE_WALLS; if (obj->flags & OF_NO_OBJECT_COLLISIONS) fq.flags &= ~FQ_CHECK_OBJS; if (IS_GENERIC(obj->type) && (Object_info[obj->id].flags & OIF_IGNORE_FORCEFIELDS_AND_GLASS)) fq.flags |= FQ_IGNORE_RENDER_THROUGH_PORTALS; // Initially assume that we fully move hit_info.hit_turnroll = end_turnroll; hit_info.hit_orient = end_orient; hit_info.hit_rotvel = end_rotvel; hit_info.hit_velocity = end_vel; hit_info.hit_time = sim_time_remaining; if (obj->type == OBJ_PLAYER) { fq.flags |= FQ_MULTI_POINT; } fate = fvi_FindIntersection(&fq, &hit_info); if (fate != HIT_NONE) { if (obj->type == OBJ_PLAYER && hit_info.num_hits > 1) { vector n = Zero_vector; int i; for (i = 0; i < hit_info.num_hits; i++) { n += hit_info.hit_wallnorm[i]; } hit_info.hit_wallnorm[0] = n; } vm_NormalizeVector(&hit_info.hit_wallnorm[0]); } /* if(obj == Player_object && fate != HIT_NONE) { mprintf((0, "Hit type %d, obj %d\n", fate, hit_info.hit_object[0])); if(fate == HIT_OBJECT || fate == HIT_SPHERE_2_POLY_OBJECT) { mprintf((0, "Hit object type %d, id %d\n", Objects[hit_info.hit_object[0]].type, Objects[hit_info.hit_object[0]].id)); } } */ obj->mtype.phys_info.velocity = temp_vel; // Accounts for precomputed hit rays if ((obj->type == OBJ_WEAPON) && (obj->ctype.laser_info.hit_status == WPC_HIT_WALL)) { vector to_hit_pnt, to_fvi_pos; if (fate == HIT_NONE) { to_hit_pnt = obj->ctype.laser_info.hit_pnt - obj->pos; to_fvi_pos = obj->ctype.laser_info.hit_pnt - hit_info.hit_pnt; } else { to_hit_pnt = obj->ctype.laser_info.hit_wall_pnt - obj->pos; to_fvi_pos = obj->ctype.laser_info.hit_wall_pnt - hit_info.hit_face_pnt[0]; } if ((to_hit_pnt * to_fvi_pos) <= 0.0f) { fate = hit_info.hit_type[0] = HIT_WALL; hit_info.hit_pnt = obj->ctype.laser_info.hit_pnt; hit_info.hit_face_pnt[0] = obj->ctype.laser_info.hit_wall_pnt; hit_info.hit_room = obj->ctype.laser_info.hit_room; hit_info.hit_face_room[0] = obj->ctype.laser_info.hit_pnt_room; hit_info.hit_wallnorm[0] = obj->ctype.laser_info.hit_wall_normal; hit_info.hit_face[0] = obj->ctype.laser_info.hit_face; obj->ctype.laser_info.hit_status = WPC_NOT_USED; } } // chrishack -- move all FVI ASSERT to here! ASSERT(fate != HIT_WALL || (fate == HIT_WALL && hit_info.hit_face[0] > -1)); ASSERT(hit_info.hit_pnt.x > -100000000.0); ASSERT(hit_info.hit_pnt.y > -100000000.0); ASSERT(hit_info.hit_pnt.z > -100000000.0); ASSERT(hit_info.hit_pnt.x < 100000000.0); ASSERT(hit_info.hit_pnt.y < 100000000.0); ASSERT(hit_info.hit_pnt.z < 100000000.0); ASSERT(!(fate != HIT_OUT_OF_TERRAIN_BOUNDS && hit_info.hit_room == -1)); #ifdef _DEBUG sim_loop_record[count].start_pos = obj->pos; sim_loop_record[count].hit_info = hit_info; if (fate != HIT_NONE) { if (Physics_player_verbose) { objnum = ObjCreate(OBJ_DEBUG_LINE, 1, hit_info.hit_room, &hit_info.hit_face_pnt[0], NULL); if (objnum >= 0) { // DAJ -1FIX Objects[objnum].rtype.line_info.end_pos = hit_info.hit_face_pnt[0] + hit_info.hit_wallnorm[0]; Objects[objnum].size = vm_VectorDistance(&Player_object->pos, &hit_info.hit_pnt); ObjSetAABB(&Objects[objnum]); Objects[objnum].flags |= OF_USES_LIFELEFT; Objects[objnum].lifeleft = 10.0f; Objects[objnum].lifetime = 10.0f; } if (obj == Player_object) { mprintf((0, "Fate = %d for player - ", fate)); if (fate == HIT_OBJECT || fate == HIT_SPHERE_2_POLY_OBJECT) { if (Objects[hit_info.hit_object[0]].flags & OF_BIG_OBJECT) { mprintf((0, "Big object\n")); } else { mprintf((0, "Small object\n")); } } else { mprintf((0, "non-object\n")); } } } } #endif if (f_set_orient) { obj->mtype.phys_info.turnroll = hit_info.hit_turnroll; obj->mtype.phys_info.rotvel = hit_info.hit_rotvel; // ObjSetOrient(obj, &hit_info.hit_orient); f_set_orient = false; } obj->mtype.phys_info.velocity = hit_info.hit_velocity; // Instant bails for collision response if (obj->type == OBJ_PLAYER && ((fate == HIT_OUT_OF_TERRAIN_BOUNDS) || (ROOMNUM_OUTSIDE(hit_info.hit_room) && GetTerrainCellFromPos(&hit_info.hit_pnt) == -1))) { ObjSetPos(obj, &init_pos, init_room, NULL, false); obj->last_pos = init_pos; return; } else if (fate == HIT_OUT_OF_TERRAIN_BOUNDS) { SetObjectDeadFlag(obj); obj->last_pos = init_pos; return; } // This is so the collide code knows that last collision position if there are multiple collisions obj->last_pos = obj->pos; // update object's position and segment number ObjSetPos(obj, &hit_info.hit_pnt, hit_info.hit_room, NULL, false); // Calulate amount of sim time left if (fate == HIT_NONE) { moved_time = old_sim_time_remaining = sim_time_remaining; sim_time_remaining = 0.0; f_continue_sim = false; } else { vm_NormalizeVector(&hit_info.hit_wallnorm[0]); vector moved_vec_n; float attempted_dist, actual_dist; // Save results of this simulation old_sim_time_remaining = sim_time_remaining; moved_vec_n = obj->pos - start_pos; // chrishack -- use this copy if (moved_vec_n != Zero_vector) { actual_dist = vm_NormalizeVector(&moved_vec_n); } else { actual_dist = 0.0f; } // chrishack -- potentially bad -- outside to inside stuff // We where sitting in a wall -- invalid starting point // moved backwards if (fate == HIT_WALL && moved_vec_n * movement_vec < -0.000001 && actual_dist != 0.0) { // mprintf((0, "Obj %d Flew backwards!\n", OBJNUM(obj))); // mprintf((0, "PHYSICS NOTE: (%f, %f, %f) to (%f, %f, %f)\n", XYZ(&start_pos), // XYZ(&obj->pos))); don't change position or sim_time_remaining ObjSetPos(obj, &start_pos, start_room, NULL, false); moved_time = 0.0; } else { // Compute more results of this simulation attempted_dist = vm_GetMagnitude(&movement_vec); sim_time_remaining = sim_time_remaining * ((attempted_dist - actual_dist) / attempted_dist); moved_time = old_sim_time_remaining - sim_time_remaining; // chrishack -- we have no time left (plus some) This can happen if FVI corrects a near tangental // movement by adding the radius - thus, we move a little too far. if (sim_time_remaining < 0.0) { sim_time_remaining = 0.0; moved_time = old_sim_time_remaining; } // chrishack -- negative simulation time pasted for this sim frame if (sim_time_remaining > old_sim_time_remaining) { // mprintf((0,"PHYSICS WARNING: Bogus sim_time_remaining = %15.13f, old = //%15.13f\nAttempted d = %15.13f, actual = %15.13f\n",sim_time_remaining,old_sim_time_remaining, // attempted_dist, actual_dist)); // Int3(); sim_time_remaining = old_sim_time_remaining; moved_time = 0.0; f_continue_sim = false; } } // // Get the collision speed as a linear interp. // if(old_sim_time_remaining > 0.0f) // { // Get the collision speed as a linear interp. if (fate == HIT_OBJECT || fate == HIT_SPHERE_2_POLY_OBJECT || fate == HIT_TERRAIN || fate == HIT_WALL) { if (moved_time > 0.0001f) { obj->mtype.phys_info.velocity = (hit_info.hit_pnt - start_pos) / moved_time; } else { // mprintf((0, "Moved time is ZERO\n")); -- Add this back -- chrishack ASSERT(old_sim_time_remaining > 0.0f); obj->mtype.phys_info.velocity = start_vel; } } else { if (old_sim_time_remaining > 0.0) { obj->mtype.phys_info.velocity = obj->mtype.phys_info.velocity * (moved_time / old_sim_time_remaining) + start_vel * (sim_time_remaining / old_sim_time_remaining); } } } bounced = 0; // Collision response code switch (fate) { case HIT_NONE: if (obj->type == OBJ_WEAPON && (obj->mtype.phys_info.flags & (PF_GRAVITY | PF_WIND))) { if (obj->mtype.phys_info.velocity != Zero_vector) vm_VectorToMatrix(&obj->orient, &obj->mtype.phys_info.velocity, &obj->orient.uvec, NULL); } f_continue_sim = false; break; case HIT_WALL: // Finally unified, still needs some work, but it certainly better like this :) case HIT_TERRAIN: { bool f_volatile_lava; // either volatile or lava bool f_forcefield; float ff_bounce_factor = 2.0f; // Check if volatile int tmap; if (!ROOMNUM_OUTSIDE(hit_info.hit_face_room[0])) // Make sure we've hit a wall, and not terrain { tmap = Rooms[hit_info.hit_face_room[0]].faces[hit_info.hit_face[0]].tmap; } else { tmap = Terrain_tex_seg[Terrain_seg[CELLNUM(hit_info.hit_face_room[0])].texseg_index].tex_index; } f_volatile_lava = (GameTextures[tmap].flags & (TF_VOLATILE + TF_LAVA)) != 0; f_forcefield = (GameTextures[tmap].flags & TF_FORCEFIELD) != 0; if (f_forcefield) { int i; for (i = 0; i < MAX_FORCE_FIELD_BOUNCE_TEXTURES; i++) { if (tmap == force_field_bounce_texture[i]) { ff_bounce_factor = force_field_bounce_multiplier[i]; break; } } } // if(obj->type != OBJ_CLUTTER) { vector moved_v; float hit_speed, wall_part; float hit_dot = 1.0; float luke_test; if (obj->type == OBJ_PLAYER) { luke_test = vm_GetMagnitude(&obj->mtype.phys_info.velocity); } // Find hit speed moved_v = obj->pos - start_pos; // chrishack -- We already computed this!!!!!!! wall_part = moved_v * hit_info.hit_wallnorm[0]; if (obj->mtype.phys_info.hit_die_dot > 0.0) { vector m_normal = start_pos - obj->pos; vm_NormalizeVector(&m_normal); hit_dot = m_normal * hit_info.hit_wallnorm[0]; } hit_speed = -(wall_part / moved_time); // Process wall hit bool hit_wall = collide_object_with_wall(obj, hit_speed, hit_info.hit_face_room[0], hit_info.hit_face[0], &hit_info.hit_face_pnt[0], &hit_info.hit_wallnorm[0], hit_dot); if (!(obj->flags & OF_DEAD)) { // Check for not really hitting the wall (because it was glass & broke) if (!hit_wall) { f_continue_sim = true; break; } if (obj->type == OBJ_WEAPON && ((obj->mtype.phys_info.flags & PF_BOUNCE) || f_forcefield)) obj->mtype.phys_info.flags &= (~PF_NO_COLLIDE_PARENT); // Slide object along wall // We're constrained by wall, so subtract wall part from the velocity vector wall_part = hit_info.hit_wallnorm[0] * obj->mtype.phys_info.velocity; if (!f_forcefield && !f_volatile_lava && (obj->mtype.phys_info.num_bounces <= 0) && (obj->mtype.phys_info.flags & PF_STICK)) { obj->movement_type = MT_NONE; if (!ROOMNUM_OUTSIDE(hit_info.hit_face_room[0])) { if (GameTextures[Rooms[hit_info.hit_face_room[0]].faces[hit_info.hit_face[0]].tmap].flags & (TF_VOLATILE | TF_LAVA)) { SetObjectDeadFlag(obj); } } else { if (GameTextures[Terrain_tex_seg[Terrain_seg[CELLNUM(hit_info.hit_room)].texseg_index].tex_index].flags & (TF_VOLATILE | TF_LAVA)) { SetObjectDeadFlag(obj); } } if (!ROOMNUM_OUTSIDE(hit_info.hit_face_room[0]) && (Rooms[hit_info.hit_face_room[0]].faces[hit_info.hit_face[0]].portal_num >= 0)) { obj->flags |= OF_STUCK_ON_PORTAL; obj->mtype.phys_info.stuck_room = hit_info.hit_face_room[0]; obj->mtype.phys_info.stuck_portal = Rooms[hit_info.hit_face_room[0]].faces[hit_info.hit_face[0]].portal_num; } vm_MakeZero(&obj->mtype.phys_info.velocity); obj->last_pos = init_pos; goto end_of_sim; } else if (f_volatile_lava || f_forcefield || (obj->mtype.phys_info.flags & PF_BOUNCE)) { wall_part *= 2.0; // Subtract out wall part twice to achieve bounce bounced = 1; // this object bounced if (f_forcefield) { if (obj->type == OBJ_PLAYER) wall_part *= ff_bounce_factor; // player bounce twice as much -- chrishack try without this too. } // New bounceness code if (!f_forcefield && (obj->mtype.phys_info.flags & PF_BOUNCE) && (obj->mtype.phys_info.num_bounces != PHYSICS_UNLIMITED_BOUNCE)) { if (obj->mtype.phys_info.num_bounces == 0) { if (obj->type != OBJ_PLAYER && (obj->flags & OF_DYING)) { ASSERT((obj->control_type == CT_DYING) || (obj->control_type == CT_DYING_AND_AI)); DestroyObject(obj, 50.0, obj->ctype.dying_info.death_flags); } else { if (obj->type != OBJ_PLAYER) SetObjectDeadFlag(obj); else mprintf((0, "Got a ship that was set for bounce!!! BAD!!!\n")); } } obj->mtype.phys_info.num_bounces--; } { float v_mag = vm_GetMagnitude(&obj->mtype.phys_info.velocity); if (obj->type == OBJ_CLUTTER && hit_info.hit_wallnorm[0].y > .4 && (obj->mtype.phys_info.velocity * hit_info.hit_wallnorm[0] > -2.0f)) { if (obj->mtype.phys_info.flags & PF_GRAVITY) { f_turn_gravity_on = true; obj->mtype.phys_info.flags &= (~(PF_GRAVITY | PF_BOUNCE)); obj->mtype.phys_info.drag *= 300.0f; } if (v_mag < 1.8f) { obj->mtype.phys_info.velocity = Zero_vector; obj->mtype.phys_info.rotvel = Zero_vector; goto skip_sim; } else { goto slide_sim; } } if (!f_forcefield && !(f_volatile_lava && obj->type == OBJ_PLAYER)) bump_obj_against_fixed(obj, &hit_info.hit_face_pnt[0], &hit_info.hit_wallnorm[0]); else obj->mtype.phys_info.velocity += hit_info.hit_wallnorm[0] * (wall_part * -1.0f); } // if(!f_forcefield && !(f_volatile && obj->type == OBJ_PLAYER)) // { // if(obj->mtype.phys_info.coeff_restitution != 1.0f) // obj->mtype.phys_info.velocity -= //(obj->mtype.phys_info.velocity * (1.0f //- obj->mtype.phys_info.coeff_restitution)); // } // if(!f_forcefield && !(f_volatile && obj->type == OBJ_PLAYER)) // bump_obj_against_fixed(obj, &hit_info.hit_face_pnt[0], //&hit_info.hit_wallnorm[0]); // // if(hit_info.hit_wallnorm[0].y > .4 && //(vm_GetMagnitude(&obj->mtype.phys_info.velocity) < .001f //|| obj->mtype.phys_info.velocity * hit_info.hit_wallnorm[0] <= 0.0f)) // { // mprintf((0, "At Rest!\n")); // obj->movement_type = MT_NONE; // obj->mtype.phys_info.velocity = Zero_vector; // f_continue_sim = false; // } // mprintf((0, "(%f, %f, %f) after bounce\n", XYZ(&obj->mtype.phys_info.velocity))); // mprintf((0, "p (%f, %f, %f) after bounce\n", XYZ(&obj->pos))); } else { slide_sim: float wall_force; wall_force = total_force * hit_info.hit_wallnorm[0]; if (obj->type != OBJ_CLUTTER) total_force += hit_info.hit_wallnorm[0] * (wall_force * -1.001); // 1.001 so that we are not quite tangential else total_force += hit_info.hit_wallnorm[0] * (wall_force * -1.0001); // 1.001 so that we are not quite tangential // Update velocity from wall hit. if (obj->type != OBJ_CLUTTER) obj->mtype.phys_info.velocity += hit_info.hit_wallnorm[0] * (wall_part * -1.001); // 1.001 so that we are not quite tangential else obj->mtype.phys_info.velocity += hit_info.hit_wallnorm[0] * (wall_part * -1.0001); // 1.001 so that we are not quite tangential if ((obj->type == OBJ_ROBOT || obj->type == OBJ_PLAYER || (obj->type == OBJ_BUILDING && obj->ai_info)) && sim_time_remaining == old_sim_time_remaining) { obj->mtype.phys_info.velocity += hit_info.hit_wallnorm[0]; } if (obj->type == OBJ_PLAYER) { float real_vel; real_vel = vm_NormalizeVector(&obj->mtype.phys_info.velocity); obj->mtype.phys_info.velocity *= ((real_vel + luke_test) / 2); // obj->mtype.phys_info.velocity *= (luke_test); } else if (obj->type == OBJ_CLUTTER && !(obj->mtype.phys_info.flags & PF_LOCK_MASK)) { // Do rolling rotvel hack obj->mtype.phys_info.rotvel = Zero_vector; if (obj->mtype.phys_info.velocity != Zero_vector) { vector axis; float speed = vm_GetMagnitude(&obj->mtype.phys_info.velocity); vm_CrossProduct(&axis, &obj->mtype.phys_info.velocity, &hit_info.hit_wallnorm[0]); vm_NormalizeVector(&axis); axis = axis * obj->orient; float scale = speed * 65535.0f / (2.0f * PI * obj->size); obj->mtype.phys_info.rotvel = axis * -scale; } } } skip_sim: // mprintf((0, "PHYSICS: Wall normal (%f, %f, %f)\n", XYZ(&hit_info.hit_wallnorm[0]))); // mprintf((0, "PHYSICS: Bounded velocity (%f, %f, %f)\n", XYZ(&obj->mtype.phys_info.velocity))); // This only happens if the new velocity is set from hitting a forcefield. if (!(fabs(obj->mtype.phys_info.velocity.x) < MAX_OBJECT_VEL && fabs(obj->mtype.phys_info.velocity.y) < MAX_OBJECT_VEL && fabs(obj->mtype.phys_info.velocity.z) < MAX_OBJECT_VEL)) { float mag = vm_NormalizeVector(&obj->mtype.phys_info.velocity); mprintf((0, "PHYSICS: Bashing vel for Obj %d of type %d with %f velocity", objnum, obj->type, mag)); obj->mtype.phys_info.velocity *= MAX_OBJECT_VEL * 0.1f; } // Weapons should face their new heading. This is so missiles are pointing in the correct direct. if (obj->type == OBJ_WEAPON && (bounced || (obj->mtype.phys_info.flags & (PF_GRAVITY | PF_WIND)))) if (obj->mtype.phys_info.velocity != Zero_vector) vm_VectorToMatrix(&obj->orient, &obj->mtype.phys_info.velocity, &obj->orient.uvec, NULL); } } // else //{ /* vector r1 = hit_info.hit_face_pnt[0] - obj->pos; vector w1; vector n1; float temp1; float j; matrix o_t1 = obj->orient; vm_TransposeMatrix(&o_t1); vector cmp1 = obj->mtype.phys_info.rotvel * o_t1; ConvertEulerToAxisAmount(&cmp1, &n1, &temp1); n1 *= temp1; if(temp1 != 0.0f) { vm_CrossProduct(&w1, &n1, &r1); } else { w1 = Zero_vector; } vector p1 = obj->mtype.phys_info.velocity + w1; //*((2.0f*(float)PI)/65535.0f); if(p1.y < 0.0) { // if(p1.y == 0.0) // mprintf((0, "AT REST: p1 (%f, %f, %f)\n", XYZ(&p1))); // else // mprintf((0, "SHIT: p1 (%f, %f, %f)\n", XYZ(&p1))); // // p1.y = -.0001f; // } float v_rel; float m1 = obj->mtype.phys_info.mass; v_rel = hit_info.hit_wallnorm[0] * (p1); float e = .1f; vector c1; vector cc1; float cv1; float i1 = (m1/12.0)*(2.0f * pow(obj->size*2.0, 2)); vm_CrossProduct(&c1, &r1, &hit_info.hit_wallnorm[0]); c1 = c1/i1; vm_CrossProduct(&cc1, &c1, &r1); cv1 = (hit_info.hit_wallnorm[0])*c1; j = (-(1.0f + e))*v_rel; j /= (1/m1 + cv1); obj->mtype.phys_info.velocity += ((j*(hit_info.hit_wallnorm[0]))/m1); vector jcn = j * (hit_info.hit_wallnorm[0]); vm_CrossProduct(&c1, &r1, &jcn); n1 = (c1)/i1; temp1 = vm_NormalizeVector(&n1); vector txx1; ConvertAxisAmountToEuler(&n1, &temp1, &txx1); obj->mtype.phys_info.rotvel += (txx1*obj->orient); } else { obj->mtype.phys_info.velocity.y = .001f; obj->mtype.phys_info.rotvel = Zero_vector; }*/ // } f_continue_sim = true; } break; case HIT_CEILING: { vector moved_v; float wall_part; float luke_test; if (obj->type == OBJ_PLAYER) { luke_test = vm_GetMagnitude(&obj->mtype.phys_info.velocity); } // Find hit speed moved_v = obj->pos - start_pos; // chrishack -- We already computed this!!!!!!! wall_part = moved_v * hit_info.hit_wallnorm[0]; // We're constrained by wall, so subtract wall part from the velocity vector wall_part = hit_info.hit_wallnorm[0] * obj->mtype.phys_info.velocity; if (obj->mtype.phys_info.flags & PF_BOUNCE) { wall_part *= 2.0; // Subtract out wall part twice to achieve bounce obj->mtype.phys_info.velocity += hit_info.hit_wallnorm[0] * (wall_part * -1.0f); if (obj->mtype.phys_info.coeff_restitution != 1.0f) obj->mtype.phys_info.velocity -= (obj->mtype.phys_info.velocity * (1.0f - obj->mtype.phys_info.coeff_restitution)); } else { float wall_force; wall_force = total_force * hit_info.hit_wallnorm[0]; total_force += hit_info.hit_wallnorm[0] * (wall_force * -1.001); // 1.001 so that we are not quite tangential // Update velocity from wall hit. obj->mtype.phys_info.velocity += hit_info.hit_wallnorm[0] * (wall_part * -1.001); // 1.001 so that we are not quite tangential if (obj->type == OBJ_PLAYER) { float real_vel; real_vel = vm_NormalizeVector(&obj->mtype.phys_info.velocity); obj->mtype.phys_info.velocity *= ((real_vel + luke_test) / 2.0f); // obj->mtype.phys_info.velocity *= (luke_test); } } // This only happens if the new velocity is set from hitting a forcefield. ASSERT(fabs(obj->mtype.phys_info.velocity.x) < MAX_OBJECT_VEL && fabs(obj->mtype.phys_info.velocity.y) < MAX_OBJECT_VEL && fabs(obj->mtype.phys_info.velocity.z) < MAX_OBJECT_VEL); // Weapons should face their new heading. This is so missiles are pointing in the correct direct. if (obj->type == OBJ_WEAPON && (bounced || (obj->mtype.phys_info.flags & (PF_GRAVITY | PF_WIND)))) vm_VectorToMatrix(&obj->orient, &obj->mtype.phys_info.velocity, &obj->orient.uvec, NULL); f_continue_sim = true; } break; case HIT_OBJECT: case HIT_SPHERE_2_POLY_OBJECT: { vector old_force; // -- chrishack // Mark the hit object so that on the following sim frames, in this game frame, ignore this object. ASSERT(hit_info.hit_object[0] != -1); // chrishack move this ASSERT fvi stuff above // chrishack -- we should have this point from FVI!!!!!!!!!!! // Calculcate the hit point between the two objects. // old_vel = obj->mtype.phys_info.velocity; old_force = obj->mtype.phys_info.thrust; obj->mtype.phys_info.thrust = total_force; // hit_info.hit_wallnorm[0] *= -1.0f; // mprintf((0, "hit dist %f\n", hit_info.hit_dist)); collide_two_objects(obj, &Objects[hit_info.hit_object[0]], &hit_info.hit_face_pnt[0], &hit_info.hit_wallnorm[0], &hit_info); if (obj->movement_type == MT_OBJ_LINKED) goto end_of_sim; total_force = obj->mtype.phys_info.thrust; obj->mtype.phys_info.thrust = old_force; if ((obj->type != OBJ_POWERUP && Objects[hit_info.hit_object[0]].type != OBJ_POWERUP) && (obj->type == OBJ_CLUTTER || obj->type == OBJ_ROBOT || obj->type == OBJ_PLAYER || (obj->type == OBJ_BUILDING && obj->ai_info)) && sim_time_remaining == old_sim_time_remaining && (Objects[hit_info.hit_object[0]].movement_type == MT_NONE || (Objects[hit_info.hit_object[0]].mtype.phys_info.velocity == Zero_vector && (Objects[hit_info.hit_object[0]].mtype.phys_info.flags & PF_LOCK_MASK)))) { obj->mtype.phys_info.velocity += hit_info.hit_wallnorm[0]; } // mprintf((0, "OBJ %d t %d hit %d\n", OBJNUM(obj), obj->type, hit_info.hit_object[0])); // Let object continue its movement if collide_two_objects does not mark it as dead // if ( !(obj->flags&OF_DEAD) ) // { // // // chrishack -- check this later -- what is the old_vel stuff? // if ((obj->mtype.phys_info.flags & PF_PERSISTENT) || // (old_vel == obj->mtype.phys_info.velocity)) // { // ignore_obj_list[n_ignore_objs++] = hit_info.hit_object[0]; // } // } // Let object continue its movement if collide_two_objects does not mark it as dead if (!(obj->flags & OF_DEAD)) { if ((obj->mtype.phys_info.flags & PF_PERSISTENT) || (Objects[hit_info.hit_object[0]].mtype.phys_info.flags & PF_PERSISTENT)) { if (n_ignore_objs < MAX_IGNORE_OBJS) { ignore_obj_list[n_ignore_objs++] = hit_info.hit_object[0]; count--; if (count < -1) count = -1; } if (obj->type == OBJ_WEAPON && (obj->mtype.phys_info.flags & PF_PERSISTENT)) obj->ctype.laser_info.last_hit_handle = Objects[hit_info.hit_object[0]].handle; if (Objects[hit_info.hit_object[0]].type == OBJ_WEAPON && (Objects[hit_info.hit_object[0]].mtype.phys_info.flags & PF_PERSISTENT)) Objects[hit_info.hit_object[0]].ctype.laser_info.last_hit_handle = obj->handle; } else if (Objects[hit_info.hit_object[0]].type == OBJ_POWERUP || obj->type == OBJ_POWERUP || Objects[hit_info.hit_object[0]].type == OBJ_MARKER || obj->type == OBJ_MARKER) { if (n_ignore_objs < MAX_IGNORE_OBJS) ignore_obj_list[n_ignore_objs++] = hit_info.hit_object[0]; count--; if (count < -1) count = -1; } f_continue_sim = true; // if((obj->type == OBJ_CLUTTER)&&(Objects[hit_info.hit_object[0]].type == // OBJ_CLUTTER)) // { // f_continue_sim = false; // } } else { f_continue_sim = false; } } break; case HIT_BAD_P0: mprintf((0, "PHYSICS ERROR: Bad p0 in physics!!!\n")); Int3(); // Unexpected collision type: start point not in specified segment. break; default: mprintf((0, "PHYSICS ERROR: Unknown and unhandled hit type returned from FVI\n")); Int3(); break; } // Increment the simulation loop counter count++; } while ((f_continue_sim) && (!(obj->flags & OF_DEAD)) && (sim_time_remaining > 0.0f) && (count < sim_loop_limit)); // Special stuff should we do if we hit the simulation loop limit // If retry count is getting large, then we might be trying to do something bad. // NOTE: These numbers limit the max collisions an object can have in a single frame if (count >= sim_loop_limit) { if (obj->type == OBJ_PLAYER) { mprintf((0, "PHYSICS NOTE: Too many collisions for player!\n")); obj->mtype.phys_info.velocity = Zero_vector; } else { // mprintf((0, "PHYSICS NOTE: Too many collisions for non-player object %d (%d to %d)!\n", // objnum, init_room, obj->roomnum)); obj->flags |= OF_DEAD; // obj->mtype.phys_info.velocity /= 2.0f; // obj->mtype.phys_info.rotvel /= 2.0f; } // ObjSetPos(obj,&init_pos, init_room); // obj->last_pos = init_pos; } end_of_sim: if (f_turn_gravity_on) { obj->mtype.phys_info.flags |= (PF_GRAVITY | PF_BOUNCE); obj->mtype.phys_info.drag /= 300.0f; } AttachUpdateSubObjects(obj); ASSERT(_finite(obj->mtype.phys_info.velocity.x) != 0); // Caller wants to go to infinity! -- Not FVI's fault. ASSERT(_finite(obj->mtype.phys_info.velocity.y) != 0); // Caller wants to go to infinity! -- Not FVI's fault. ASSERT(_finite(obj->mtype.phys_info.velocity.z) != 0); // Caller wants to go to infinity! -- Not FVI's fault. obj->last_pos = init_pos; return; } int PhysCastWalkRay(object *obj, vector *p0, vector *p1, vector *hitpnt, int *start_roomnum = NULL, int *end_roomnum = NULL, vector *hit_normal = NULL) { int fate; fvi_info hit_info; fvi_query fq; fq.p0 = p0; fq.p1 = p1; if (start_roomnum) fq.startroom = *start_roomnum; else fq.startroom = obj->roomnum; fq.rad = 0.0f; fq.flags = FQ_CHECK_OBJS | FQ_IGNORE_POWERUPS | FQ_IGNORE_WEAPONS | FQ_IGNORE_MOVING_OBJECTS | FQ_IGNORE_NON_LIGHTMAP_OBJECTS; if (!end_roomnum) fq.flags |= FQ_NO_RELINK; fq.thisobjnum = OBJNUM(obj); fq.ignore_obj_list = NULL; fate = fvi_FindIntersection(&fq, &hit_info); if (end_roomnum) *end_roomnum = hit_info.hit_room; if (hit_normal) *hit_normal = hit_info.hit_wallnorm[0]; if (fate == HIT_WALL) { if (GameTextures[Rooms[hit_info.hit_face_room[0]].faces[hit_info.hit_face[0]].tmap].flags & (TF_VOLATILE | TF_FORCEFIELD | TF_LAVA)) { return HIT_NONE; } } *hitpnt = hit_info.hit_pnt; return fate; } // void PhysComputeGroundPosOrient(object *obj, vector *pos, matrix *orient) //{ // vector pnt[3]; // vector tpnt = obj->pos; // matrix tmatrix = obj->orient; // poly_model *pm=&Poly_models[obj->rtype.pobj_info.model_num]; // int num_gps = (pm->n_ground == 5)?3:1; // int i; // // obj->pos = *pos; // obj->orient = *orient; // // for(i = 0; i < num_gps; i++) // { // PhysCalcGround(&pnt[i], NULL, obj, i); // } // // obj->pos = tpnt; // obj->orient = tmatrix; // // if(num_gps == 3 && ROOMNUM_OUTSIDE(obj->roomnum)) // { // float diff[3]; // vector gp[3]; // // for(i = 0; i < 3; i++) // { // gp[i] = pnt[i]; // gp[i].y = GetTerrainGroundPoint(&gp[i]); // } // // for(i = 0; i < 3; i++) // { // diff[i] = gp[i].y - pnt[i].y; // } // // float biggest_diff = diff[0]; // for(i = 1; i < 3; i++) // { // if(fabs(diff[i]) > fabs(biggest_diff)) // { // biggest_diff = diff[i]; // } // } // // vector uvec; // vm_GetPerp(&uvec, &gp[0], &gp[1], &gp[2]); // vm_NormalizeVector(&uvec); // // float dot = orient->fvec * uvec; // vector fvec = orient->fvec; // fvec -= (uvec * dot); // vm_NormalizeVector(&fvec); // // vm_VectorToMatrix(orient, &fvec, &uvec, NULL); // pos->y += biggest_diff; // } // else // { // Alignd to Y-axis // float diff = GetTerrainGroundPoint(&pnt[0]) - pnt[0].y; // // pos->y += diff; // // angvec a; // vm_ExtractAnglesFromMatrix(&a, orient); // vm_AnglesToMatrix(orient, 0.0f, a.h, 0.0f); // } //} void PhysCalPntOnCPntPlane(object *obj, vector *s_pnt, vector *d_pnt, float *dist) { vector tpnt = *s_pnt - obj->pos; *dist = obj->orient.uvec * tpnt; *d_pnt = *s_pnt + ((-(*dist)) * obj->orient.uvec); } bool PhysComputeWalkerPosOrient(object *obj, vector *pos, matrix *orient) { vector pnt[3]; vector tpnt = obj->pos; matrix tmatrix = obj->orient; poly_model *pm = &Poly_models[obj->rtype.pobj_info.model_num]; int num_gps = (pm->n_ground == 5) ? 3 : 1; int i; bool f_ok = true; obj->pos = *pos; obj->orient = *orient; for (i = 0; i < num_gps; i++) { PhysCalcGround(&pnt[i], NULL, obj, i); } obj->pos = tpnt; obj->orient = tmatrix; if (num_gps == 3) { vector pp[3]; float pdist[3]; int proom[3]; vector hp[3]; for (i = 0; i < 3; i++) PhysCalPntOnCPntPlane(obj, &pnt[i], &pp[i], &pdist[i]); // Moves the points to valid locations in the mine and determines the points' room numbers for (i = 0; i < 3; i++) { vector x; vector norm; int fate = PhysCastWalkRay(obj, &obj->pos, &pp[i], &x, NULL, &proom[i], &norm); if (fate != HIT_NONE) { if (norm * obj->orient.uvec < 0.4717f) { return false; } pp[i] = x; } } int num_ave = 0; vector ave_diff = Zero_vector; for (i = 0; i < 3; i++) { int fate; vector foot_pnt = pp[i] + obj->orient.uvec * -(obj->size * 1.5f); vector norm; fate = PhysCastWalkRay(obj, &pp[i], &foot_pnt, &hp[i], &proom[i], NULL, &norm); if (fate != HIT_NONE) { if (norm * obj->orient.uvec < 0.4717f) { return false; } ave_diff += hp[i] - pnt[i]; num_ave++; } else { f_ok = false; } } if (num_ave > 0) { ave_diff /= num_ave; } else ave_diff = Zero_vector; vector uvec; vm_GetPerp(&uvec, &hp[0], &hp[1], &hp[2]); vm_NormalizeVector(&uvec); // if(uvec.y < 0.0) // uvec *= -1.0f; float dot = orient->fvec * uvec; vector fvec = orient->fvec; fvec -= (uvec * dot); vm_NormalizeVector(&fvec); vm_VectorToMatrix(orient, &fvec, &uvec, NULL); *pos += ave_diff; } else { // Alignd to Y-axis int fate; vector hp; vector foot_pnt = obj->pos; foot_pnt.y -= obj->size * 3.0f; fate = PhysCastWalkRay(obj, &obj->pos, &foot_pnt, &hp); if (fate == HIT_NONE) { mprintf((0, "Walking object %d should be falling\n", OBJNUM(obj))); // SetObjectDeadFlag(obj); f_ok = false; } else { float diff = hp.y - pnt[0].y; pos->y += diff; angvec a; vm_ExtractAnglesFromMatrix(&a, orient); vm_AnglesToMatrix(orient, 0.0f, a.h, 0.0f); } } return f_ok; } bool IsNodeValid(int cell, float min_vert_dot) { if (TerrainNormals[MAX_TERRAIN_LOD - 1][cell].normal1.y >= min_vert_dot && TerrainNormals[MAX_TERRAIN_LOD - 1][cell].normal2.y >= min_vert_dot) return true; else return false; } bool PhysValidateGroundPath(object *obj, vector *s, int sroom, vector *e, int eroom, float rad, float min_vert_dot) { if (ROOMNUM_OUTSIDE(sroom)) { int x1, x2, y1, y2, x, y, delta_y, delta_x, change_x, change_y, length, cur_node, error_term, i; int new_x, new_y; int counter; int delta_ter_check = rad / TERRAIN_SIZE + 1; // Determine the start end end nodes x1 = CELLNUM(sroom) % TERRAIN_WIDTH; y1 = CELLNUM(sroom) / TERRAIN_WIDTH; x2 = CELLNUM(eroom) % TERRAIN_WIDTH; y2 = CELLNUM(eroom) / TERRAIN_WIDTH; x = x1; y = y1; // How many nodes did I move? delta_x = x2 - x1; delta_y = y2 - y1; // Check the current node if (!IsNodeValid(CELLNUM(sroom), min_vert_dot)) { return false; } if (delta_x == 0 && delta_y == 0) return true; // check the end node cur_node = y1 * TERRAIN_DEPTH + x2; if (!IsNodeValid(cur_node, min_vert_dot)) { return false; } // Do a Breshenham line algorithm if (delta_x < 0) { change_x = -1; delta_x = -delta_x; } else { change_x = 1; } if (delta_y < 0) { change_y = -1; delta_y = -delta_y; } else { change_y = 1; } error_term = 0; i = 1; if (delta_x < delta_y) { length = delta_y + 1; while (i < length) { y += change_y; error_term += delta_x; if (error_term >= delta_y) { x += change_x; error_term -= delta_y; } if (y >= TERRAIN_DEPTH || y < 0 || x < 0 || x >= TERRAIN_WIDTH) { return false; } for (counter = -delta_ter_check; counter <= delta_ter_check; counter++) { new_x = x + counter; if (new_x < 0) { counter = -x; new_x = 0; } if (new_x >= TERRAIN_WIDTH) break; // Check the current node for collisions -- chrishack -- This can be made iterative cur_node = y * TERRAIN_DEPTH + new_x; if (!IsNodeValid(cur_node, min_vert_dot)) { return false; } } i++; } } else { length = delta_x + 1; while (i < length) { x += change_x; error_term += delta_y; if (error_term >= delta_x) { y += change_y; error_term -= delta_x; } if (y >= TERRAIN_DEPTH || y < 0 || x < 0 || x >= TERRAIN_WIDTH) { return false; } for (counter = -delta_ter_check; counter <= delta_ter_check; counter++) { new_y = y + counter; if (new_y < 0) { counter = -y; new_y = 0; } if (new_y >= TERRAIN_DEPTH) break; // Check the current node for collisions -- chrishack -- This can be made iterative cur_node = new_y * TERRAIN_DEPTH + x; if (!IsNodeValid(cur_node, min_vert_dot)) { return false; } } i++; } } return true; } else { return true; } } // ----------------------------------------------------------------------------------------------------------- // Simulate a physics object for this frame void do_walking_sim(object *obj) { // Since we might not call FVI, set this here Fvi_num_recorded_faces = 0; int n_ignore_objs = 0; // The number of ignored objects int ignore_obj_list[MAX_IGNORE_OBJS + 1]; // List of ignored objects int fate; // Collision type for response code vector movement_vec; // Movement in this frame vector movement_pos; // End point of the movement int count = 0; // Simulation loop counter int sim_loop_limit = (obj->type == OBJ_PLAYER) ? MAX_PLAYER_SIM_LOOPS : MAX_NON_PLAYER_SIM_LOOPS; int objnum = OBJNUM(obj); // Simulated object's index fvi_info hit_info; // Hit information fvi_query fq; // Movement query float sim_time_remaining = Frametime; // Amount of simulation time remaining (current iteration) float old_sim_time_remaining = Frametime; // Amount of simulation time remaining (previous iteration) vector init_pos = obj->pos; // Initial position int init_room = obj->roomnum; // Initial room vector start_pos = obj->pos; // Values at the start of current simulation loop vector start_vel = obj->mtype.phys_info.velocity; int start_room = obj->roomnum; float moved_time; // How long objected moved before hit something physics_info *pi = &obj->mtype.phys_info; // Simulated object's physics information int bounced = 0; // Did the object bounce? vector total_force = Zero_vector; // Constant force acting on an object bool f_continue_sim; // Should we run another simulation loop bool f_start_fvi_record = true; // Records the rooms that are passed thru poly_model *pm = &Poly_models[obj->rtype.pobj_info.model_num]; // Assert conditions ASSERT(obj->type != OBJ_NONE); ASSERT(obj->movement_type == MT_WALKING); ASSERT(!(obj->mtype.phys_info.flags & PF_USES_THRUST) || obj->mtype.phys_info.drag != 0.0); ASSERT(_finite(obj->mtype.phys_info.velocity.x) != 0); // Caller wants to go to infinity! -- Not FVI's fault. ASSERT(_finite(obj->mtype.phys_info.velocity.y) != 0); // Caller wants to go to infinity! -- Not FVI's fault. ASSERT(_finite(obj->mtype.phys_info.velocity.z) != 0); // Caller wants to go to infinity! -- Not FVI's fault. DebugBlockPrint("PW"); #ifdef _DEBUG if (!Game_do_walking_sim) { return; } #endif // Instant bail cases if ((obj->flags & (OF_DEAD | OF_ATTACHED)) || (Frametime <= 0.0) || (obj->type == OBJ_DUMMY)) { return; } obj->flags &= (~OF_STOPPED_THIS_FRAME); // Do rotation velocity/accel stuff bool f_rotated = false; if (!(fabs(pi->velocity.x) > .000001 || fabs(pi->velocity.y) > .000001 || fabs(pi->velocity.z) > .000001 || fabs(pi->thrust.x) > .000001 || fabs(pi->thrust.y) > .000001 || fabs(pi->thrust.z) > .000001 || fabs(pi->rotvel.x) > .000001 || fabs(pi->rotvel.y) > .000001 || fabs(pi->rotvel.z) > .000001 || fabs(pi->rotthrust.x) > .000001 || fabs(pi->rotthrust.y) > .000001 || fabs(pi->rotthrust.z) > .000001 || (obj->mtype.phys_info.flags & PF_GRAVITY) || ((!ROOMNUM_OUTSIDE(obj->roomnum)) && Rooms[obj->roomnum].wind != Zero_vector) || (obj->mtype.phys_info.flags & PF_WIGGLE) || ((obj->ai_info != NULL) && ((obj->ai_info->flags & AIF_REPORT_NEW_ORIENT) != 0)))) { if ((obj->flags & OF_MOVED_THIS_FRAME)) { obj->flags &= (~OF_MOVED_THIS_FRAME); obj->flags |= OF_STOPPED_THIS_FRAME; } return; } if (obj->ai_info) { if (obj->ai_info->flags & AIF_REPORT_NEW_ORIENT) { if (pm->n_ground == 5) { vector pos = obj->pos; matrix orient = obj->orient; if (PhysComputeWalkerPosOrient(obj, &pos, &orient) && ((!obj->ai_info) || (orient.uvec * obj->orient.uvec > .7101 && orient.fvec * obj->orient.fvec > .7101 && orient.rvec * obj->orient.rvec > .7101))) ObjSetPos(obj, &pos, obj->roomnum, &orient, false); else ObjSetOrient(obj, &obj->ai_info->saved_orient); } obj->ai_info->flags &= ~AIF_REPORT_NEW_ORIENT; } } obj->flags |= OF_MOVED_THIS_FRAME; // This assumes that the thrust does not change within a frame. If it does, account for it // there... like missiles bouncing off a wall and changing heading. --chrishack if (obj->mtype.phys_info.drag != 0.0) { if (obj->mtype.phys_info.flags & PF_USES_THRUST) { total_force = obj->mtype.phys_info.thrust; } } // mprintf((0, "PHYSICS: Current obj: %d, %9.2fx %9.2fy %9.2fz\n", OBJNUM(obj), XYZ(&obj->pos))); // mprintf((2,1,0,"x %9.2f\ny %9.2f\nz %9.2f", XYZ(&obj->mtype.phys_info.velocity))); // mprintf((0, "PHYSICS: Current velocity (%f, %f, %f)\n", XYZ(&obj->mtype.phys_info.velocity))); Physics_normal_counter++; // Simulate movement until we are done (i.e. Frametime has passed or object is done moving) do { obj->mtype.phys_info.rotvel = Zero_vector; #ifdef _DEBUG // This records the sim. sim_loop_record[count].phys_info = obj->mtype.phys_info; #endif // Remove transient portions of the velocity vector t = (obj->mtype.phys_info.velocity * obj->orient.uvec) * obj->orient.uvec; obj->mtype.phys_info.velocity -= t; // Initailly assume that this is the last sim cycle f_continue_sim = false; // Determines forces if (count > 0) { total_force.x = total_force.y = total_force.z = 0.0; } { movement_vec = pi->velocity * Frametime; movement_pos = obj->pos + movement_vec; vector footstep = movement_pos; int new_room; if (ROOMNUM_OUTSIDE(obj->roomnum)) { new_room = GetTerrainRoomFromPos(&footstep); } else { new_room = obj->roomnum; } bool f_step; if (PhysValidateGroundPath(obj, &obj->pos, obj->roomnum, &footstep, new_room, obj->size, 0.6f)) { f_step = true; // Determine endpos matrix norient; norient = obj->orient; if (PhysComputeWalkerPosOrient(obj, &footstep, &norient)) { if (norient.uvec * obj->orient.uvec > .7101 && norient.fvec * obj->orient.fvec > .7101 && norient.rvec * obj->orient.rvec > .7101) { ObjSetOrient(obj, &norient); movement_vec = footstep - obj->pos; movement_pos = footstep; fq.p0 = &obj->pos; fq.startroom = obj->roomnum; fq.p1 = &footstep; fq.rad = obj->size; fq.thisobjnum = objnum; fq.ignore_obj_list = NULL; fq.flags = FQ_CHECK_OBJS | FQ_IGNORE_WEAPONS | FQ_IGNORE_POWERUPS | FQ_IGNORE_TERRAIN; if (obj->mtype.phys_info.flags & PF_POINT_COLLIDE_WALLS) { fq.rad = 0.0f; } else if (!ROOMNUM_OUTSIDE(obj->roomnum)) { poly_model *pm = &Poly_models[obj->rtype.pobj_info.model_num]; int num_gps = (pm->n_ground == 5) ? 3 : 1; if (num_gps == 3) fq.rad *= 0.2f; else fq.rad *= 0.45f; } if (obj->flags & OF_NO_OBJECT_COLLISIONS) fq.flags &= ~FQ_CHECK_OBJS; fate = fvi_FindIntersection(&fq, &hit_info); norient = obj->orient; if (PhysComputeWalkerPosOrient(obj, &hit_info.hit_pnt, &norient)) { ObjSetPos(obj, &hit_info.hit_pnt, hit_info.hit_room, &norient, false); } else { f_step = false; } } else { f_step = false; movement_vec = Zero_vector; } } else { f_step = false; movement_vec = Zero_vector; } } else { f_step = false; } if (!f_step) { pi->velocity = Zero_vector; break; } // If we are stationary, we are done :) if (fabs(movement_vec.x) < 0.0000001 && fabs(movement_vec.y) < 0.0000001 && fabs(movement_vec.z) < 0.0000001) break; } Physics_walking_looping_counter++; // Instant bails for collision response if (obj->type == OBJ_PLAYER && ((fate == HIT_OUT_OF_TERRAIN_BOUNDS) || (ROOMNUM_OUTSIDE(hit_info.hit_room) && GetTerrainCellFromPos(&hit_info.hit_pnt) == -1))) { ObjSetPos(obj, &init_pos, init_room, NULL, false); obj->last_pos = init_pos; goto end_of_sim; } else if (fate == HIT_OUT_OF_TERRAIN_BOUNDS) { mprintf((0, "PHYSICS NOTE: Walker exited terrain\n")); SetObjectDeadFlag(obj); obj->last_pos = init_pos; goto end_of_sim; } // Calulate amount of sim time left if (fate == HIT_NONE) { moved_time = old_sim_time_remaining = sim_time_remaining; sim_time_remaining = 0.0; f_continue_sim = false; } else { vector moved_vec_n; float attempted_dist, actual_dist; // Save results of this simulation old_sim_time_remaining = sim_time_remaining; moved_vec_n = obj->pos - start_pos; // chrishack -- use this copy if (moved_vec_n != Zero_vector) { actual_dist = vm_NormalizeVector(&moved_vec_n); } else { actual_dist = 0.0f; } // chrishack -- potentially bad -- outside to inside stuff // We where sitting in a wall -- invalid starting point // moved backwards if (fate == HIT_WALL && moved_vec_n * movement_vec < -0.000001 && actual_dist != 0.0) { mprintf((0, "Obj %d Walked backwards!\n", OBJNUM(obj))); // mprintf((0, "PHYSICS NOTE: (%f, %f, %f) to (%f, %f, %f)\n", XYZ(&start_pos), // XYZ(&obj->pos))); don't change position or sim_time_remaining ObjSetPos(obj, &start_pos, start_room, NULL, false); moved_time = 0.0; } else { // Compute more results of this simulation attempted_dist = vm_GetMagnitude(&movement_vec); sim_time_remaining = sim_time_remaining * ((attempted_dist - actual_dist) / attempted_dist); moved_time = old_sim_time_remaining - sim_time_remaining; // chrishack -- we have no time left (plus some) This can happen if FVI corrects a near tangental // movement by adding the radius - thus, we move a little too far. if (sim_time_remaining < 0.0) { sim_time_remaining = 0.0; moved_time = old_sim_time_remaining; } // chrishack -- negative simulation time pasted for this sim frame if (sim_time_remaining > old_sim_time_remaining) { mprintf((0, "PHYSICS WARNING: Bogus sim_time_remaining = %15.13f, old = %15.13f\nAttempted d = %15.13f, actual " "= %15.13f\n", sim_time_remaining, old_sim_time_remaining, attempted_dist, actual_dist)); // Int3(); sim_time_remaining = old_sim_time_remaining; moved_time = 0.0; f_continue_sim = false; } } } // Get the collision speed as a linear interp. if (old_sim_time_remaining > 0.0) { obj->mtype.phys_info.velocity = obj->mtype.phys_info.velocity * (moved_time / old_sim_time_remaining) + start_vel * (sim_time_remaining / old_sim_time_remaining); } // Collision response code switch (fate) { case HIT_NONE: f_continue_sim = false; break; case HIT_WALL: // Finally unified, still needs some work, but it certainly better like this :) case HIT_TERRAIN: { // if(obj->type != OBJ_CLUTTER) { vector moved_v; float hit_speed, wall_part; float hit_dot = 1.0; float luke_test; if (obj->type == OBJ_PLAYER || OBJ_CLUTTER) { luke_test = vm_GetMagnitude(&obj->mtype.phys_info.velocity); } // Find hit speed moved_v = obj->pos - start_pos; // chrishack -- We already computed this!!!!!!! wall_part = moved_v * hit_info.hit_wallnorm[0]; if (obj->mtype.phys_info.hit_die_dot > 0.0) { vector m_normal = start_pos - obj->pos; vm_NormalizeVector(&m_normal); hit_dot = m_normal * hit_info.hit_wallnorm[0]; } hit_speed = -(wall_part / moved_time); // We hit a wall (weapons cannot scrap) if ((wall_part != 0.0 && moved_time > 0.0 && (hit_speed > 0.0)) || obj->type == OBJ_WEAPON) collide_object_with_wall(obj, hit_speed, hit_info.hit_face_room[0], hit_info.hit_face[0], &hit_info.hit_face_pnt[0], &hit_info.hit_wallnorm[0], hit_dot); else // We hit a wall and are moving parallel to it collide_object_with_wall(obj, hit_speed, hit_info.hit_face_room[0], hit_info.hit_face[0], &hit_info.hit_face_pnt[0], &hit_info.hit_wallnorm[0], hit_dot); // scrape_object_on_wall(obj, hit_info.hit_face_room, hit_info.hit_face, //&hit_info.hit_face_pnt[0], &hit_info.hit_wallnorm[0] ); if (!(obj->flags & OF_DEAD)) { bool f_forcefield = false; // bounce off a forcefield if (obj->type == OBJ_WEAPON && (obj->mtype.phys_info.flags & PF_BOUNCE)) obj->mtype.phys_info.flags &= (~PF_NO_COLLIDE_PARENT); // Slide object along wall // We're constrained by wall, so subtract wall part from the velocity vector wall_part = hit_info.hit_wallnorm[0] * obj->mtype.phys_info.velocity; if ((obj->mtype.phys_info.num_bounces <= 0) && (obj->mtype.phys_info.flags & PF_STICK)) { obj->movement_type = MT_NONE; vm_MakeZero(&obj->mtype.phys_info.velocity); obj->last_pos = init_pos; goto end_of_sim; } else if (f_forcefield || (obj->mtype.phys_info.flags & PF_BOUNCE)) { wall_part *= 2.0; // Subtract out wall part twice to achieve bounce if (f_forcefield) { if (obj->type == OBJ_PLAYER) wall_part *= 2; // player bounce twice as much -- chrishack try without this too. } bounced = 1; // this object bounced // mprintf((0, "(%f, %f, %f) before bounce\n", // XYZ(&obj->mtype.phys_info.velocity))); obj->mtype.phys_info.velocity += hit_info.hit_wallnorm[0] * (wall_part * -1.0f); if (obj->mtype.phys_info.coeff_restitution != 1.0f) obj->mtype.phys_info.velocity -= (obj->mtype.phys_info.velocity * (1.0f - obj->mtype.phys_info.coeff_restitution)); // mprintf((0, "(%f, %f, %f) after bounce\n", XYZ(&obj->mtype.phys_info.velocity))); // mprintf((0, "p (%f, %f, %f) after bounce\n", XYZ(&obj->pos))); } else { float wall_force; wall_force = total_force * hit_info.hit_wallnorm[0]; total_force += hit_info.hit_wallnorm[0] * (wall_force * -1.001); // 1.001 so that we are not quite tangential // Update velocity from wall hit. obj->mtype.phys_info.velocity += hit_info.hit_wallnorm[0] * (wall_part * -1.001); // 1.001 so that we are not quite tangential if ((obj->type == OBJ_ROBOT || obj->type == OBJ_PLAYER || OBJ_CLUTTER || (obj->type == OBJ_BUILDING && obj->ai_info)) && sim_time_remaining == old_sim_time_remaining) { obj->mtype.phys_info.velocity += hit_info.hit_wallnorm[0]; } if (obj->type == OBJ_PLAYER || OBJ_CLUTTER) { float real_vel; real_vel = vm_NormalizeVector(&obj->mtype.phys_info.velocity); obj->mtype.phys_info.velocity *= ((real_vel + luke_test) / 2); // obj->mtype.phys_info.velocity *= (luke_test); } } // mprintf((0, "PHYSICS: Wall normal (%f, %f, %f)\n", XYZ(&hit_info.hit_wallnorm[0]))); // mprintf((0, "PHYSICS: Bounded velocity (%f, %f, %f)\n", XYZ(&obj->mtype.phys_info.velocity))); // This only happens if the new velocity is set from hitting a forcefield. if (!(fabs(obj->mtype.phys_info.velocity.x) < MAX_OBJECT_VEL && fabs(obj->mtype.phys_info.velocity.y) < MAX_OBJECT_VEL && fabs(obj->mtype.phys_info.velocity.z) < MAX_OBJECT_VEL)) { float mag = vm_NormalizeVector(&obj->mtype.phys_info.velocity); mprintf((0, "PHYSICS: Bashing vel for Obj %d of type %d with %f velocity", objnum, obj->type, mag)); obj->mtype.phys_info.velocity *= MAX_OBJECT_VEL * 0.1f; } // Weapons should face their new heading. This is so missiles are pointing in the correct direct. if (obj->type == OBJ_WEAPON && (bounced || (obj->mtype.phys_info.flags & (PF_GRAVITY | PF_WIND)))) if (obj->mtype.phys_info.velocity != Zero_vector) vm_VectorToMatrix(&obj->orient, &obj->mtype.phys_info.velocity, &obj->orient.uvec, NULL); } } // else //{ /* vector r1 = hit_info.hit_face_pnt[0] - obj->pos; vector w1; vector n1; float temp1; float j; matrix o_t1 = obj->orient; vm_TransposeMatrix(&o_t1); vector cmp1 = obj->mtype.phys_info.rotvel * o_t1; ConvertEulerToAxisAmount(&cmp1, &n1, &temp1); n1 *= temp1; if(temp1 != 0.0f) { vm_CrossProduct(&w1, &n1, &r1); } else { w1 = Zero_vector; } vector p1 = obj->mtype.phys_info.velocity + w1; //*((2.0f*(float)PI)/65535.0f); if(p1.y < 0.0) { // if(p1.y == 0.0) // mprintf((0, "AT REST: p1 (%f, %f, %f)\n", XYZ(&p1))); // else // mprintf((0, "SHIT: p1 (%f, %f, %f)\n", XYZ(&p1))); // // p1.y = -.0001f; // } float v_rel; float m1 = obj->mtype.phys_info.mass; v_rel = hit_info.hit_wallnorm[0] * (p1); float e = .1f; vector c1; vector cc1; float cv1; float i1 = (m1/12.0)*(2.0f * pow(obj->size*2.0, 2)); vm_CrossProduct(&c1, &r1, &hit_info.hit_wallnorm[0]); c1 = c1/i1; vm_CrossProduct(&cc1, &c1, &r1); cv1 = (hit_info.hit_wallnorm[0])*c1; j = (-(1.0f + e))*v_rel; j /= (1/m1 + cv1); obj->mtype.phys_info.velocity += ((j*(hit_info.hit_wallnorm[0]))/m1); vector jcn = j * (hit_info.hit_wallnorm[0]); vm_CrossProduct(&c1, &r1, &jcn); n1 = (c1)/i1; temp1 = vm_NormalizeVector(&n1); vector txx1; ConvertAxisAmountToEuler(&n1, &temp1, &txx1); obj->mtype.phys_info.rotvel += (txx1*obj->orient); } else { obj->mtype.phys_info.velocity.y = .001f; obj->mtype.phys_info.rotvel = Zero_vector; }*/ // } f_continue_sim = true; } break; case HIT_OBJECT: case HIT_SPHERE_2_POLY_OBJECT: { vector old_force; // -- chrishack // Mark the hit object so that on the following sim frames, in this game frame, ignore this object. ASSERT(hit_info.hit_object[0] != -1); // chrishack move this ASSERT fvi stuff above // chrishack -- we should have this point from FVI!!!!!!!!!!! // Calculcate the hit point between the two objects. // old_vel = obj->mtype.phys_info.velocity; old_force = obj->mtype.phys_info.thrust; obj->mtype.phys_info.thrust = total_force; // hit_info.hit_wallnorm[0] *= -1.0f; collide_two_objects(obj, &Objects[hit_info.hit_object[0]], &hit_info.hit_face_pnt[0], &hit_info.hit_wallnorm[0], &hit_info); if (obj->movement_type == MT_OBJ_LINKED) goto end_of_sim; total_force = obj->mtype.phys_info.thrust; obj->mtype.phys_info.thrust = old_force; if ((obj->type == OBJ_ROBOT || obj->type == OBJ_PLAYER || OBJ_CLUTTER || (obj->type == OBJ_BUILDING && obj->ai_info)) && sim_time_remaining == old_sim_time_remaining && Objects[hit_info.hit_object[0]].movement_type == MT_NONE) { obj->mtype.phys_info.velocity += hit_info.hit_wallnorm[0]; } // mprintf((0, "OBJ %d t %d hit %d\n", OBJNUM(obj), obj->type, hit_info.hit_object[0])); // Let object continue its movement if collide_two_objects does not mark it as dead // if ( !(obj->flags&OF_DEAD) ) // { // // // chrishack -- check this later -- what is the old_vel stuff? // if ((obj->mtype.phys_info.flags & PF_PERSISTENT) || // (old_vel == obj->mtype.phys_info.velocity)) // { // ignore_obj_list[n_ignore_objs++] = hit_info.hit_object[0]; // } // } // Let object continue its movement if collide_two_objects does not mark it as dead if (!(obj->flags & OF_DEAD)) { if ((obj->mtype.phys_info.flags & PF_PERSISTENT) || (Objects[hit_info.hit_object[0]].mtype.phys_info.flags & PF_PERSISTENT)) { if (n_ignore_objs < MAX_IGNORE_OBJS) ignore_obj_list[n_ignore_objs++] = hit_info.hit_object[0]; } else if (Objects[hit_info.hit_object[0]].type == OBJ_POWERUP || obj->type == OBJ_POWERUP || Objects[hit_info.hit_object[0]].type == OBJ_MARKER || obj->type == OBJ_MARKER) { if (n_ignore_objs < MAX_IGNORE_OBJS) ignore_obj_list[n_ignore_objs++] = hit_info.hit_object[0]; count--; if (count < -1) count = -1; } f_continue_sim = true; } else { f_continue_sim = false; } } break; case HIT_BAD_P0: mprintf((0, "PHYSICS ERROR: Bad p0 in physics!!!\n")); Int3(); // Unexpected collision type: start point not in specified segment. break; default: mprintf((0, "PHYSICS ERROR: Unknown and unhandled hit type returned from FVI\n")); Int3(); break; } // Increment the simulation loop counter count++; } while ((f_continue_sim) && (!(obj->flags & OF_DEAD)) && (sim_time_remaining > 0.0f) && (count < sim_loop_limit)); // Special stuff should we do if we hit the simulation loop limit // If retry count is getting large, then we might be trying to do something bad. // NOTE: These numbers limit the max collisions an object can have in a single frame if (count >= sim_loop_limit) { if (obj->type == OBJ_PLAYER) { mprintf((0, "PHYSICS NOTE: Too many collisions for player!\n")); } else { // mprintf((0, "PHYSICS NOTE: Too many collisions for non-player object %d (%d to %d)!\n", // objnum, init_room, obj->roomnum)); obj->flags |= OF_DEAD; // obj->mtype.phys_info.velocity /= 2.0f; // obj->mtype.phys_info.rotvel /= 2.0f; } // ObjSetPos(obj,&init_pos, init_room); // obj->last_pos = init_pos; } end_of_sim: AttachUpdateSubObjects(obj); ASSERT(_finite(obj->mtype.phys_info.velocity.x) != 0); // Caller wants to go to infinity! -- Not FVI's fault. ASSERT(_finite(obj->mtype.phys_info.velocity.y) != 0); // Caller wants to go to infinity! -- Not FVI's fault. ASSERT(_finite(obj->mtype.phys_info.velocity.z) != 0); // Caller wants to go to infinity! -- Not FVI's fault. obj->last_pos = init_pos; return; } // ----------------------------------------------------------------------------------------------------------- // Simulate a physics object for this frame void do_vis_physics_sim(vis_effect *vis) { if (Frametime <= 0.0f) return; vector total_force; vector old_pos = vis->pos; #ifdef _DEBUG if (!Game_do_vis_sim) { return; } #endif DebugBlockPrint("V "); if (vis->flags & VF_DEAD) { DebugBlockPrint("DV"); return; } if (fabsf(vis->velocity.x) < 0.000001f && fabsf(vis->velocity.y) < 0.000001f && fabsf(vis->velocity.z) < 0.000001f && !(vis->phys_flags & PF_GRAVITY)) { DebugBlockPrint("DV"); return; } // Determine velocity if (vis->phys_flags & PF_FIXED_VELOCITY) { vis->pos += vis->velocity * Frametime; } else { if (vis->mass > 0.0f && vis->drag > 0.0f) { // If the object is effected by gravity (normal, lighter than air, and anti-gravity) if (vis->phys_flags & PF_GRAVITY) { total_force.x = total_force.z = 0.0f; total_force.y = Gravity_strength * vis->mass; } else if (vis->phys_flags & PF_REVERSE_GRAVITY) { total_force.x = total_force.z = 0.0f; total_force.y = -Gravity_strength * vis->mass; } else { total_force.x = total_force.y = total_force.z = 0.0f; } // Note: The math here is done in 64bit floats because we will run into precision problems with 32bit const double forceOverDrag[3] = {total_force.x / vis->drag, total_force.y / vis->drag, total_force.z / vis->drag}; const double frameTime = Frametime; const double massOverDrag = vis->mass / vis->drag; const double dragOverMass = vis->drag / vis->mass; const double expDoMFt = exp(-dragOverMass * frameTime); double visNewPos[3]; visNewPos[0] = double(vis->pos.x) + forceOverDrag[0] * frameTime + massOverDrag * (double(vis->velocity.x) - forceOverDrag[0]) * (1.0 - expDoMFt); visNewPos[1] = double(vis->pos.y) + forceOverDrag[1] * frameTime + massOverDrag * (double(vis->velocity.y) - forceOverDrag[1]) * (1.0 - expDoMFt); visNewPos[2] = double(vis->pos.z) + forceOverDrag[2] * frameTime + massOverDrag * (double(vis->velocity.z) - forceOverDrag[2]) * (1.0 - expDoMFt); double visNewVel[3]; visNewVel[0] = (double(vis->velocity.x) - forceOverDrag[0]) * expDoMFt + forceOverDrag[0]; visNewVel[1] = (double(vis->velocity.y) - forceOverDrag[1]) * expDoMFt + forceOverDrag[1]; visNewVel[2] = (double(vis->velocity.z) - forceOverDrag[2]) * expDoMFt + forceOverDrag[2]; vis->pos.x = float(visNewPos[0]); vis->pos.y = float(visNewPos[1]); vis->pos.z = float(visNewPos[2]); vis->velocity.x = float(visNewVel[0]); vis->velocity.y = float(visNewVel[1]); vis->velocity.z = float(visNewVel[2]); } else { vector delta_velocity = {0.0f, 0.0f, 0.0f}; if (vis->phys_flags & PF_GRAVITY) { delta_velocity.y += Gravity_strength * Frametime; } else if (vis->phys_flags & PF_REVERSE_GRAVITY) { delta_velocity.y -= Gravity_strength * Frametime; } vis->pos += (vis->velocity * Frametime) + (0.5f * delta_velocity * Frametime); vis->velocity += delta_velocity; } } if (vis->phys_flags & PF_NO_COLLIDE) { DebugBlockPrint("DV"); return; } Physics_vis_counter++; // Do FVI call to check for death & update room fvi_query fq; fvi_info hit_info; int fate; if (ROOMNUM_OUTSIDE(vis->roomnum)) { ASSERT(CELLNUM(vis->roomnum) <= TERRAIN_WIDTH * TERRAIN_DEPTH); ASSERT(CELLNUM(vis->roomnum) >= 0); } else { ASSERT(vis->roomnum >= 0 && vis->roomnum <= Highest_room_index && Rooms[vis->roomnum].used); } fq.p0 = &old_pos; fq.startroom = vis->roomnum; fq.p1 = &vis->pos; fq.rad = 0.0f; fq.thisobjnum = -1; fq.ignore_obj_list = NULL; fq.flags = FQ_CHECK_OBJS | FQ_ONLY_DOOR_OBJ | FQ_IGNORE_WALLS; fate = fvi_FindIntersection(&fq, &hit_info); if (fate == HIT_NONE && hit_info.hit_room != -1) { if (hit_info.hit_room != vis->roomnum) { VisEffectRelink(vis - VisEffects, hit_info.hit_room); } if (ROOMNUM_OUTSIDE(hit_info.hit_room)) { ASSERT(CELLNUM(hit_info.hit_room) <= TERRAIN_WIDTH * TERRAIN_DEPTH); ASSERT(CELLNUM(hit_info.hit_room) >= 0); } else { ASSERT(hit_info.hit_room >= 0 && hit_info.hit_room <= Highest_room_index && Rooms[hit_info.hit_room].used); } } else { VisEffectDelete(vis - VisEffects); } DebugBlockPrint("DV"); } void phys_apply_force(object *obj, vector *force_vec, short weapon_index) { if (obj->mtype.phys_info.mass == 0.0) return; if (obj->movement_type != MT_PHYSICS && obj->movement_type != MT_WALKING) return; if (obj->mtype.phys_info.flags & PF_LOCK_MASK) // Not done! return; if ((Game_mode & GM_MULTI) && ((obj->type == OBJ_PLAYER && obj->id != Player_num) || ((obj->type != OBJ_PLAYER && obj->type != OBJ_POWERUP) && Netgame.local_role != LR_SERVER))) return; // Do Force Feedback for the hit if (obj->id == Player_num) { // we need to convert collision_normal from world space to local space vector local_norm; float scale = 0, magnitude = 0; // compute how much force to do magnitude = vm_GetMagnitude(force_vec); if (magnitude) { scale = magnitude / 2500.0f; if (scale < 0.0f) scale = 0.0f; if (scale > 1.0f) scale = 1.0f; vm_MatrixMulVector(&local_norm, force_vec, &obj->orient); local_norm *= -1.0f; ForceEffectsPlay(FORCE_TEST_FORCE, &scale, &local_norm); } if (weapon_index != -1) { mprintf((0, "Force from weapon\n")); } // mprintf((0,"Force: Magnitude = %f Scale = %1.3f\n",magnitude,scale)); VIBE_DoForce(force_vec); } //------------------------------ // Add in acceleration due to force obj->mtype.phys_info.velocity += (*force_vec / obj->mtype.phys_info.mass); } /* // ---------------------------------------------------------------- // Do *dest = *delta unless: // *delta is pretty small // and they are of different signs. void physics_set_rotvel_and_saturate(float *dest, float delta) { if ((delta ^ *dest) < 0) { if (abs(delta) < F1_0/8) { // mprintf((0, "D")); *dest = delta/4; } else // mprintf((0, "d")); *dest = delta; } else { // mprintf((0, "!")); *dest = delta; } } // ------------------------------------------------------------------------------------------------------ // Note: This is the old ai_turn_towards_vector code. // phys_apply_rot used to call ai_turn_towards_vector until I fixed it, which broke phys_apply_rot. void physics_turn_towards_vector(vector *goal_vector, object *obj, float rate) { vms_angvec dest_angles, cur_angles; float delta_p, delta_h; vector *rotvel_ptr = &obj->mtype.phys_info.rotvel; // Make this object turn towards the goal_vector. Changes orientation, doesn't change direction of movement. // If no one moves, will be facing goal_vector in 1 second. // Detect null vector. if ((goal_vector->x == 0) && (goal_vector->y == 0) && (goal_vector->z == 0)) return; // Make morph objects turn more slowly. if (obj->control_type == CT_MORPH) rate *= 2; vm_extract_angles_vector(&dest_angles, goal_vector); vm_extract_angles_vector(&cur_angles, &obj->orient.fvec); delta_p = (dest_angles.p - cur_angles.p); delta_h = (dest_angles.h - cur_angles.h); if (delta_p > F1_0/2) delta_p = dest_angles.p - cur_angles.p - F1_0; if (delta_p < -F1_0/2) delta_p = dest_angles.p - cur_angles.p + F1_0; if (delta_h > F1_0/2) delta_h = dest_angles.h - cur_angles.h - F1_0; if (delta_h < -F1_0/2) delta_h = dest_angles.h - cur_angles.h + F1_0; delta_p = fixdiv(delta_p, rate); delta_h = fixdiv(delta_h, rate); if (abs(delta_p) < F1_0/16) delta_p *= 4; if (abs(delta_h) < F1_0/16) delta_h *= 4; physics_set_rotvel_and_saturate(&rotvel_ptr->x, delta_p); physics_set_rotvel_and_saturate(&rotvel_ptr->y, delta_h); rotvel_ptr->z = 0; } */ // ----------------------------------------------------------------------------- // Applies an instantaneous whack on an object, resulting in an instantaneous // change in orientation. void phys_apply_rot(object *obj, vector *force_vec) { /* float rate, vecmag; if (obj->movement_type != MT_PHYSICS) return; vecmag = vm_VectorMagnitude(force_vec); if (vecmag < F1_0/256) rate = 4*F1_0; else if (vecmag < obj->mtype.phys_info.mass >> 14) rate = 4*F1_0; else { rate = fixdiv(obj->mtype.phys_info.mass, vecmag); if (obj->type == OBJ_ROBOT) { if (rate < F1_0/4) rate = F1_0/4; // Changed by mk, 10/24/95, claw guys should not slow down when attacking! if (!Robot_info[obj->id].thief && !Robot_info[obj->id].attack_type) { if (obj->ctype.ai_info.SKIP_AI_COUNT * Frametime < 3*F1_0/4) { float tval = fixdiv(F1_0, 8*Frametime); int addval; addval = f2i(tval); if ( (rand() * 2) < (tval & 0xffff)) addval++; obj->ctype.ai_info.SKIP_AI_COUNT += addval; // -- mk: too much stuff making hard to see my debug messages...mprintf((0, "Frametime = %7.3f, addval = %i\n", f2fl(Frametime), addval)); } } } else { if (rate < F1_0/2) rate = F1_0/2; } } // Turn amount inversely proportional to mass. Third parameter is seconds to do 360 turn. physics_turn_towards_vector(force_vec, obj, rate); */ } /* //this routine will set the thrust for an object to a value that will //(hopefully) maintain the object's current velocity void set_thrust_from_velocity(object *obj) { Int3(); // float k; // // ASSERT(obj->movement_type == MT_PHYSICS); // // k = fixmuldiv(obj->mtype.phys_info.mass,obj->mtype.phys_info.drag,(f1_0-obj->mtype.phys_info.drag)); // // vm_vec_copy_scale(&obj->mtype.phys_info.thrust,&obj->mtype.phys_info.velocity,k); // } */ // Checks if an object is on the ground (we might do more in this function at a latter date) --chrishack int check_obj_on_ground(object *obj, fvi_info *hit_info_ptr) { vector ground; fvi_query fq; ASSERT(obj->size - 2 * PHYSICS_GROUND_TOLERANCE > 0.0f); ground = obj->pos; // This should account for an object that touches more than one face. (To find the real ground). ground.y -= (obj->size + 3 * PHYSICS_GROUND_TOLERANCE); fq.p0 = &obj->pos; fq.startroom = obj->roomnum; fq.p1 = &ground; fq.rad = obj->size - 2 * PHYSICS_GROUND_TOLERANCE; fq.thisobjnum = OBJNUM(obj); fq.ignore_obj_list = NULL; fq.flags = FQ_CHECK_OBJS; if (obj->flags & OF_NO_OBJECT_COLLISIONS) fq.flags &= ~FQ_CHECK_OBJS; return fvi_FindIntersection(&fq, hit_info_ptr); }