Subversion Repositories Games.Descent

Rev

Blame | Last modification | View Log | Download | RSS feed

  1. /*
  2.  * Portions of this file are copyright Rebirth contributors and licensed as
  3.  * described in COPYING.txt.
  4.  * Portions of this file are copyright Parallax Software and licensed
  5.  * according to the Parallax license below.
  6.  * See COPYING.txt for license details.
  7.  
  8. THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
  9. SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
  10. END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
  11. ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
  12. IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
  13. SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
  14. FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
  15. CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
  16. AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
  17. COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
  18. */
  19.  
  20. /*
  21.  *
  22.  * Code for flying through the mines
  23.  *
  24.  */
  25.  
  26.  
  27. #include <stdio.h>
  28. #include <stdlib.h>
  29.  
  30. #include "joy.h"
  31. #include "dxxerror.h"
  32.  
  33. #include "inferno.h"
  34. #include "segment.h"
  35. #include "object.h"
  36. #include "physics.h"
  37. #include "robot.h"
  38. #include "key.h"
  39. #include "game.h"
  40. #include "collide.h"
  41. #include "fvi.h"
  42. #include "newdemo.h"
  43. #include "gameseg.h"
  44. #include "timer.h"
  45. #include "ai.h"
  46. #include "wall.h"
  47. #include "laser.h"
  48. #if defined(DXX_BUILD_DESCENT_II)
  49. #include "bm.h"
  50. #include "player.h"
  51. #define MAX_OBJECT_VEL  i2f(100)
  52. #endif
  53.  
  54. #include "dxxsconf.h"
  55. #include "compiler-range_for.h"
  56.  
  57. //Global variables for physics system
  58.  
  59. #define ROLL_RATE       0x2000
  60. #define DAMP_ANG        0x400 //min angle to bank
  61. #define TURNROLL_SCALE  (0x4ec4/2)
  62.  
  63. //check point against each side of segment. return bitmask, where bit
  64. //set means behind that side
  65.  
  66. //make sure matrix is orthogonal
  67. void check_and_fix_matrix(vms_matrix &m)
  68. {
  69.         m = vm_vector_2_matrix(m.fvec,&m.uvec,nullptr);
  70. }
  71.  
  72.  
  73. static void do_physics_align_object(object_base &obj)
  74. {
  75.         vms_vector desired_upvec;
  76.         fixang delta_ang,roll_ang;
  77.         fix largest_d = INT32_MIN;
  78.         const shared_side *best_side = nullptr;
  79.         // bank player according to segment orientation
  80.  
  81.         //find side of segment that player is most alligned with
  82.  
  83.         range_for (auto &i, vcsegptr(obj.segnum)->shared_segment::sides)
  84.         {
  85.                 const auto d = vm_vec_dot(i.normals[0], obj.orient.uvec);
  86.  
  87.                 if (largest_d < d)
  88.                 {
  89.                         largest_d = d;
  90.                         best_side = &i;
  91.                 }
  92.         }
  93.  
  94.         // new player leveling code: use normal of side closest to our up vec
  95.         if (!best_side)
  96.                 return;
  97.         if (!get_side_is_quad(*best_side))
  98.         {
  99.                 desired_upvec = vm_vec_avg(best_side->normals[0], best_side->normals[1]);
  100.                 vm_vec_normalize(desired_upvec);
  101.         }
  102.         else
  103.                 desired_upvec = best_side->normals[0];
  104.  
  105.         if (labs(vm_vec_dot(desired_upvec, obj.orient.fvec)) < f1_0 / 2)
  106.         {
  107.                 vms_angvec tangles;
  108.                
  109.                 const auto temp_matrix = vm_vector_2_matrix(obj.orient.fvec, &desired_upvec, nullptr);
  110.  
  111.                 delta_ang = vm_vec_delta_ang(obj.orient.uvec, temp_matrix.uvec, obj.orient.fvec);
  112.  
  113.                 delta_ang += obj.mtype.phys_info.turnroll;
  114.  
  115.                 if (abs(delta_ang) > DAMP_ANG) {
  116.                         roll_ang = fixmul(FrameTime,ROLL_RATE);
  117.  
  118.                         if (abs(delta_ang) < roll_ang) roll_ang = delta_ang;
  119.                         else if (delta_ang<0) roll_ang = -roll_ang;
  120.  
  121.                         tangles.p = tangles.h = 0;  tangles.b = roll_ang;
  122.                         const auto &&rotmat = vm_angles_2_matrix(tangles);
  123.                         obj.orient = vm_matrix_x_matrix(obj.orient, rotmat);
  124.                 }
  125.         }
  126.  
  127. }
  128.  
  129. static void set_object_turnroll(object_base &obj)
  130. {
  131.         fixang desired_bank;
  132.  
  133.         desired_bank = -fixmul(obj.mtype.phys_info.rotvel.y, TURNROLL_SCALE);
  134.  
  135.         if (obj.mtype.phys_info.turnroll != desired_bank)
  136.         {
  137.                 fixang delta_ang,max_roll;
  138.  
  139.                 max_roll = fixmul(ROLL_RATE,FrameTime);
  140.  
  141.                 delta_ang = desired_bank - obj.mtype.phys_info.turnroll;
  142.  
  143.                 if (labs(delta_ang) < max_roll)
  144.                         max_roll = delta_ang;
  145.                 else
  146.                         if (delta_ang < 0)
  147.                                 max_roll = -max_roll;
  148.  
  149.                 obj.mtype.phys_info.turnroll += max_roll;
  150.         }
  151.  
  152. }
  153.  
  154.  
  155. #define MAX_IGNORE_OBJS 100
  156.  
  157. #ifndef NDEBUG
  158. int     Total_retries=0, Total_sims=0;
  159. int     Dont_move_ai_objects=0;
  160. #endif
  161.  
  162. #define FT (f1_0/64)
  163.  
  164. //      -----------------------------------------------------------------------------------------------------------
  165. // add rotational velocity & acceleration
  166. namespace dsx {
  167. static void do_physics_sim_rot(object_base &obj)
  168. {
  169.         vms_angvec      tangles;
  170.         //fix           rotdrag_scale;
  171.         physics_info *pi;
  172.  
  173.         Assert(FrameTime > 0);  //Get MATT if hit this!
  174.  
  175.         pi = &obj.mtype.phys_info;
  176.  
  177.         if (!(pi->rotvel.x || pi->rotvel.y || pi->rotvel.z || pi->rotthrust.x || pi->rotthrust.y || pi->rotthrust.z))
  178.                 return;
  179.  
  180.         if (obj.mtype.phys_info.drag)
  181.         {
  182.                 int count;
  183.                 fix drag,r,k;
  184.  
  185.                 count = FrameTime / FT;
  186.                 r = FrameTime % FT;
  187.                 k = fixdiv(r,FT);
  188.  
  189.                 drag = (obj.mtype.phys_info.drag * 5) / 2;
  190.  
  191.                 if (obj.mtype.phys_info.flags & PF_USES_THRUST)
  192.                 {
  193.                         const auto accel = vm_vec_copy_scale(obj.mtype.phys_info.rotthrust, fixdiv(f1_0, obj.mtype.phys_info.mass));
  194.                         while (count--) {
  195.                                 vm_vec_add2(obj.mtype.phys_info.rotvel, accel);
  196.                                 vm_vec_scale(obj.mtype.phys_info.rotvel, f1_0 - drag);
  197.                         }
  198.  
  199.                         //do linear scale on remaining bit of time
  200.  
  201.                         vm_vec_scale_add2(obj.mtype.phys_info.rotvel, accel, k);
  202.                         vm_vec_scale(obj.mtype.phys_info.rotvel, f1_0 - fixmul(k, drag));
  203.                 }
  204.                 else
  205. #if defined(DXX_BUILD_DESCENT_II)
  206.                         if (! (obj.mtype.phys_info.flags & PF_FREE_SPINNING))
  207. #endif
  208.                 {
  209.                         fix total_drag=f1_0;
  210.  
  211.                         while (count--)
  212.                                 total_drag = fixmul(total_drag,f1_0-drag);
  213.  
  214.                         //do linear scale on remaining bit of time
  215.  
  216.                         total_drag = fixmul(total_drag,f1_0-fixmul(k,drag));
  217.  
  218.                         vm_vec_scale(obj.mtype.phys_info.rotvel, total_drag);
  219.                 }
  220.  
  221.         }
  222.  
  223.         //now rotate object
  224.  
  225.         //unrotate object for bank caused by turn
  226.         if (obj.mtype.phys_info.turnroll)
  227.         {
  228.                 tangles.p = tangles.h = 0;
  229.                 tangles.b = -obj.mtype.phys_info.turnroll;
  230.                 const auto &&rotmat = vm_angles_2_matrix(tangles);
  231.                 obj.orient = vm_matrix_x_matrix(obj.orient, rotmat);
  232.         }
  233.  
  234.         const auto frametime = FrameTime;
  235.         tangles.p = fixmul(obj.mtype.phys_info.rotvel.x, frametime);
  236.         tangles.h = fixmul(obj.mtype.phys_info.rotvel.y, frametime);
  237.         tangles.b = fixmul(obj.mtype.phys_info.rotvel.z, frametime);
  238.  
  239.         obj.orient = vm_matrix_x_matrix(obj.orient, vm_angles_2_matrix(tangles));
  240.  
  241.         if (obj.mtype.phys_info.flags & PF_TURNROLL)
  242.                 set_object_turnroll(obj);
  243.  
  244.         //re-rotate object for bank caused by turn
  245.         if (obj.mtype.phys_info.turnroll)
  246.         {
  247.                 tangles.p = tangles.h = 0;
  248.                 tangles.b = obj.mtype.phys_info.turnroll;
  249.                 obj.orient = vm_matrix_x_matrix(obj.orient, vm_angles_2_matrix(tangles));
  250.         }
  251.  
  252.         check_and_fix_matrix(obj.orient);
  253. }
  254. }
  255.  
  256. // On joining edges fvi tends to get inaccurate as hell. Approach is to check if the object interects with the wall and if so, move away from it.
  257. static void fix_illegal_wall_intersection(const vmobjptridx_t obj)
  258. {
  259.         auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
  260.         auto &Objects = LevelUniqueObjectState.Objects;
  261.         auto &Vertices = LevelSharedVertexState.get_vertices();
  262.         auto &vmobjptr = Objects.vmptr;
  263.         if (!(obj->type == OBJ_PLAYER || obj->type == OBJ_ROBOT))
  264.                 return;
  265.  
  266.         auto &vcvertptr = Vertices.vcptr;
  267.         const auto &&hresult = sphere_intersects_wall(vcvertptr, obj->pos, vcsegptridx(obj->segnum), obj->size);
  268.         if (hresult.seg)
  269.         {
  270.                 vm_vec_scale_add2(obj->pos, hresult.seg->sides[hresult.side].normals[0], FrameTime*10);
  271.                 update_object_seg(vmobjptr, LevelSharedSegmentState, LevelUniqueSegmentState, obj);
  272.         }
  273. }
  274.  
  275. namespace {
  276.  
  277. class ignore_objects_array_t
  278. {
  279.         using array_t = std::array<vcobjidx_t, MAX_IGNORE_OBJS>;
  280.         array_t::iterator e;
  281.         union {
  282.                 array_t a;
  283.         };
  284. public:
  285.         /* The iterator should be initialized in a
  286.          * member-initialization-list.  However, clang complains that the
  287.          * union is uninitialized during the member-initialization-list, but
  288.          * accepts the still-uninitialized union member once the constructor
  289.          * body starts.  Assign the iterator in the body to silence this
  290.          * useless clang warning.
  291.          *
  292.          * Known bad:
  293.          *      clang-5
  294.          *      clang-7
  295.          */
  296.         ignore_objects_array_t()
  297.         {
  298.                 e = a.begin();
  299.         }
  300.         bool push_back(const vcobjidx_t o)
  301.         {
  302.                 if (unlikely(e == a.end()))
  303.                         return false;
  304.                 std::uninitialized_fill_n(e, 1, o);
  305.                 ++e;
  306.                 return true;
  307.         }
  308.         operator std::pair<const vcobjidx_t *, const vcobjidx_t *>() const
  309.         {
  310.                 return {a.begin(), e};
  311.         }
  312. };
  313.  
  314. }
  315.  
  316. //      -----------------------------------------------------------------------------------------------------------
  317. //Simulate a physics object for this frame
  318. namespace dsx {
  319. window_event_result do_physics_sim(const vmobjptridx_t obj, const vms_vector &obj_previous_position, phys_visited_seglist *const phys_segs)
  320. {
  321.         auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
  322.         auto &Objects = LevelUniqueObjectState.Objects;
  323.         auto &Vertices = LevelSharedVertexState.get_vertices();
  324.         auto &vcobjptr = Objects.vcptr;
  325.         auto &vmobjptr = Objects.vmptr;
  326.         ignore_objects_array_t ignore_obj_list;
  327.         int try_again;
  328.         int fate=0;
  329.         vms_vector ipos;                //position after this frame
  330.         segnum_t WallHitSeg;
  331.         int WallHitSide;
  332.         fvi_info hit_info;
  333.         fvi_query fq;
  334.         vms_vector save_pos;
  335.         fix drag;
  336.         fix sim_time;
  337.         vms_vector start_pos;
  338.         int obj_stopped=0;
  339.         fix moved_time;                 //how long objected moved before hit something
  340.         physics_info *pi;
  341.         auto orig_segnum = obj->segnum;
  342.         int bounced=0;
  343.         bool Player_ScrapeFrame=false;
  344.         auto result = window_event_result::handled;
  345. #if defined(DXX_BUILD_DESCENT_II)
  346.         auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
  347. #endif
  348.  
  349.         Assert(obj->movement_type == MT_PHYSICS);
  350.  
  351. #ifndef NDEBUG
  352.         if (Dont_move_ai_objects)
  353.                 if (obj->control_type == CT_AI)
  354.                         return window_event_result::ignored;
  355. #endif
  356.  
  357.         pi = &obj->mtype.phys_info;
  358.  
  359.         do_physics_sim_rot(obj);
  360.  
  361.         if (!(pi->velocity.x || pi->velocity.y || pi->velocity.z || pi->thrust.x || pi->thrust.y || pi->thrust.z))
  362.                 return window_event_result::ignored;
  363.  
  364.         sim_time = FrameTime;
  365.  
  366.         start_pos = obj->pos;
  367.  
  368.                 //if uses thrust, cannot have zero drag
  369.         Assert(!(obj->mtype.phys_info.flags&PF_USES_THRUST) || obj->mtype.phys_info.drag!=0);
  370.  
  371.         //do thrust & drag
  372.         if ((drag = obj->mtype.phys_info.drag) != 0) {
  373.  
  374.                 int count;
  375.                 fix r,k,have_accel;
  376.  
  377.                 count = FrameTime / FT;
  378.                 r = FrameTime % FT;
  379.                 k = fixdiv(r,FT);
  380.  
  381.                 if (obj->mtype.phys_info.flags & PF_USES_THRUST) {
  382.  
  383.                         const auto accel = vm_vec_copy_scale(obj->mtype.phys_info.thrust,fixdiv(f1_0,obj->mtype.phys_info.mass));
  384.                         have_accel = (accel.x || accel.y || accel.z);
  385.  
  386.                         while (count--) {
  387.                                 if (have_accel)
  388.                                         vm_vec_add2(obj->mtype.phys_info.velocity,accel);
  389.  
  390.                                 vm_vec_scale(obj->mtype.phys_info.velocity,f1_0-drag);
  391.                         }
  392.  
  393.                         //do linear scale on remaining bit of time
  394.  
  395.                         vm_vec_scale_add2(obj->mtype.phys_info.velocity,accel,k);
  396.                         if (drag)
  397.                                 vm_vec_scale(obj->mtype.phys_info.velocity,f1_0-fixmul(k,drag));
  398.                 }
  399.                 else if (drag)
  400.                 {
  401.                         fix total_drag=f1_0;
  402.  
  403.                         while (count--)
  404.                                 total_drag = fixmul(total_drag,f1_0-drag);
  405.  
  406.                         //do linear scale on remaining bit of time
  407.  
  408.                         total_drag = fixmul(total_drag,f1_0-fixmul(k,drag));
  409.  
  410.                         vm_vec_scale(obj->mtype.phys_info.velocity,total_drag);
  411.                 }
  412.         }
  413.  
  414.         int count = 0;
  415.         auto &vcvertptr = Vertices.vcptr;
  416.         auto &Walls = LevelUniqueWallSubsystemState.Walls;
  417.         auto &vcwallptr = Walls.vcptr;
  418.         do {
  419.                 try_again = 0;
  420.  
  421.                 //Move the object
  422.                 const auto frame_vec = vm_vec_copy_scale(obj->mtype.phys_info.velocity, sim_time);
  423.  
  424.                 if ( (frame_vec.x==0) && (frame_vec.y==0) && (frame_vec.z==0) )
  425.                         break;
  426.  
  427.                 count++;
  428.  
  429.                 //      If retry count is getting large, then we are trying to do something stupid.
  430.                 if (count > 8) break; // in original code this was 3 for all non-player objects. still leave us some limit in case fvi goes apeshit.
  431.  
  432.                 const auto new_pos = vm_vec_add(obj->pos,frame_vec);
  433.                 fq.p0                                           = &obj->pos;
  434.                 fq.startseg                             = obj->segnum;
  435.                 fq.p1                                           = &new_pos;
  436.                 fq.rad                                  = obj->size;
  437.                 fq.thisobjnum                   = obj;
  438.                 fq.ignore_obj_list      = ignore_obj_list;
  439.                 fq.flags                                        = FQ_CHECK_OBJS;
  440.  
  441.                 if (obj->type == OBJ_WEAPON)
  442.                         fq.flags |= FQ_TRANSPOINT;
  443.  
  444.                 if (phys_segs)
  445.                         fq.flags |= FQ_GET_SEGLIST;
  446.  
  447.                 fate = find_vector_intersection(fq, hit_info);
  448.                 //      Matt: Mike's hack.
  449.                 if (fate == HIT_OBJECT) {
  450.                         auto &objp = *vcobjptr(hit_info.hit_object);
  451.  
  452.                         if ((objp.type == OBJ_WEAPON && is_proximity_bomb_or_player_smart_mine(get_weapon_id(objp))) ||
  453.                                 objp.type == OBJ_POWERUP) // do not increase count for powerups since they *should* not change our movement
  454.                                 count--;
  455.                 }
  456.  
  457. #ifndef NDEBUG
  458.                 if (fate == HIT_BAD_P0) {
  459.                         Int3();
  460.                 }
  461. #endif
  462.  
  463.                 if (phys_segs && !hit_info.seglist.empty())
  464.                 {
  465.                         auto n_phys_segs = phys_segs->nsegs;
  466.                         if (n_phys_segs && phys_segs->seglist[n_phys_segs - 1] == hit_info.seglist[0])
  467.                                 n_phys_segs--;
  468.                         range_for (const auto &hs, hit_info.seglist)
  469.                         {
  470.                                 if (!(n_phys_segs < phys_segs->seglist.size() - 1))
  471.                                         break;
  472.                                 phys_segs->seglist[n_phys_segs++] = hs;
  473.                         }
  474.                         phys_segs->nsegs = n_phys_segs;
  475.                 }
  476.  
  477.                 ipos = hit_info.hit_pnt;
  478.                 auto iseg = hit_info.hit_seg;
  479.                 WallHitSide = hit_info.hit_side;
  480.                 WallHitSeg = hit_info.hit_side_seg;
  481.  
  482.                 if (iseg==segment_none) {               //some sort of horrible error
  483.                         if (obj->type == OBJ_WEAPON)
  484.                                 obj->flags |= OF_SHOULD_BE_DEAD;
  485.                         break;
  486.                 }
  487.  
  488.                 Assert(!((fate==HIT_WALL) && ((WallHitSeg == segment_none) || (WallHitSeg > Highest_segment_index))));
  489.  
  490.                 save_pos = obj->pos;                    //save the object's position
  491.                 auto save_seg = obj->segnum;
  492.  
  493.                 // update object's position and segment number
  494.                 obj->pos = ipos;
  495.  
  496.                 const auto &&obj_segp = Segments.vmptridx(iseg);
  497.                 if ( iseg != obj->segnum )
  498.                         obj_relink(vmobjptr, Segments.vmptr, obj, obj_segp);
  499.  
  500.                 //if start point not in segment, move object to center of segment
  501.                 if (get_seg_masks(vcvertptr, obj->pos, Segments.vcptr(obj->segnum), 0).centermask != 0)
  502.                 {
  503.                         auto n = find_object_seg(LevelSharedSegmentState, LevelUniqueSegmentState, obj);
  504.                         if (n == segment_none)
  505.                         {
  506.                                 //Int3();
  507.                                 if (obj->type == OBJ_PLAYER && (n = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, obj_previous_position, obj_segp)) != segment_none)
  508.                                 {
  509.                                         obj->pos = obj_previous_position;
  510.                                         obj_relink(vmobjptr, Segments.vmptr, obj, n);
  511.                                 }
  512.                                 else {
  513.                                         compute_segment_center(vcvertptr, obj->pos, obj_segp);
  514.                                         obj->pos.x += obj;
  515.                                 }
  516.                                 if (obj->type == OBJ_WEAPON)
  517.                                         obj->flags |= OF_SHOULD_BE_DEAD;
  518.                         }
  519.                         return window_event_result::ignored;
  520.                 }
  521.  
  522.                 //calulate new sim time
  523.                 {
  524.                         //vms_vector moved_vec;
  525.                         vms_vector moved_vec_n;
  526.                         fix attempted_dist,actual_dist;
  527.  
  528.                         actual_dist = vm_vec_normalized_dir(moved_vec_n,obj->pos,save_pos);
  529.  
  530.                         if (fate==HIT_WALL && vm_vec_dot(moved_vec_n,frame_vec) < 0) {          //moved backwards
  531.  
  532.                                 //don't change position or sim_time
  533.  
  534.                                 obj->pos = save_pos;
  535.                
  536.                                 //iseg = obj->segnum;           //don't change segment
  537.  
  538.                                 obj_relink(vmobjptr, Segments.vmptr, obj, Segments.vmptridx(save_seg));
  539.  
  540.                                 moved_time = 0;
  541.                         }
  542.                         else {
  543.                                 fix old_sim_time;
  544.  
  545.                                 attempted_dist = vm_vec_mag(frame_vec);
  546.  
  547.                                 old_sim_time = sim_time;
  548.  
  549.                                 sim_time = fixmuldiv(sim_time,attempted_dist-actual_dist,attempted_dist);
  550.  
  551.                                 moved_time = old_sim_time - sim_time;
  552.  
  553.                                 if (sim_time < 0 || sim_time>old_sim_time) {
  554.                                         sim_time = old_sim_time;
  555.                                         //WHY DOES THIS HAPPEN??
  556.  
  557.                                         moved_time = 0;
  558.                                 }
  559.                         }
  560.                 }
  561.  
  562.  
  563.                 switch( fate )          {
  564.  
  565.                         case HIT_WALL:          {
  566.                                 fix hit_speed=0,wall_part=0;
  567.  
  568.                                 // Find hit speed      
  569.  
  570.                                 const auto moved_v = vm_vec_sub(obj->pos,save_pos);
  571.  
  572.                                 wall_part = vm_vec_dot(moved_v,hit_info.hit_wallnorm);
  573.  
  574.                                 if ((wall_part != 0 && moved_time>0 && (hit_speed=-fixdiv(wall_part,moved_time))>0) || obj->type == OBJ_WEAPON || obj->type == OBJ_DEBRIS)
  575.                                         result = collide_object_with_wall(
  576. #if defined(DXX_BUILD_DESCENT_II)
  577.                                                 LevelSharedSegmentState.DestructibleLights,
  578. #endif
  579.                                                 obj, hit_speed, Segments.vmptridx(WallHitSeg), WallHitSide, hit_info.hit_pnt);
  580.                                 /*
  581.                                  * Due to the nature of this loop, it's possible that a local player may receive scrape damage multiple times in one frame.
  582.                                  * Check if we received damage and do not apply more damage (nor produce damage sounds/flashes/bumps, etc) for the rest of the loop.
  583.                                  * It's possible that other walls later in the loop would still be valid for scraping but due to the generalized outcome, this should be negligible (practical wall sliding is handled below).
  584.                                  * NOTE: Remote players will return false and never receive damage. But since we handle only one object (remote or local) per loop, this is no problem.
  585.                                  */
  586.                                 if (obj->type == OBJ_PLAYER && Player_ScrapeFrame == false)
  587.                                         Player_ScrapeFrame = scrape_player_on_wall(obj, Segments.vmptridx(WallHitSeg), WallHitSide, hit_info.hit_pnt);
  588.  
  589.                                 Assert( WallHitSeg != segment_none );
  590.                                 Assert( WallHitSide > -1 );
  591.  
  592.                                 if ( !(obj->flags&OF_SHOULD_BE_DEAD) )  {
  593.                                         int forcefield_bounce;          //bounce off a forcefield
  594.  
  595. #if defined(DXX_BUILD_DESCENT_II)
  596.                                         if (!cheats.bouncyfire)
  597. #endif
  598.                                         Assert(!(obj->mtype.phys_info.flags & PF_STICK && obj->mtype.phys_info.flags & PF_BOUNCE));     //can't be bounce and stick
  599.  
  600. #if defined(DXX_BUILD_DESCENT_I)
  601.                                         /*
  602.                                          * Force fields are not supported in Descent 1.  Use
  603.                                          * this as a placeholder to make the code match the
  604.                                          * force field handling in Descent 2.
  605.                                          */
  606.                                         forcefield_bounce = 0;
  607. #elif defined(DXX_BUILD_DESCENT_II)
  608.                                         forcefield_bounce = (TmapInfo[Segments[WallHitSeg].unique_segment::sides[WallHitSide].tmap_num].flags & TMI_FORCE_FIELD);
  609.                                         int check_vel=0;
  610. #endif
  611.  
  612.                                         if (!forcefield_bounce && (obj->mtype.phys_info.flags & PF_STICK)) {            //stop moving
  613.  
  614.                                                 LevelUniqueStuckObjectState.add_stuck_object(vcwallptr, obj, Segments.vmptr(WallHitSeg), WallHitSide);
  615.  
  616.                                                 vm_vec_zero(obj->mtype.phys_info.velocity);
  617.                                                 obj_stopped = 1;
  618.                                                 try_again = 0;
  619.                                         }
  620.                                         else {                                  // Slide object along wall
  621.  
  622.                                                 wall_part = vm_vec_dot(hit_info.hit_wallnorm,obj->mtype.phys_info.velocity);
  623.  
  624.                                                 // if wall_part, make sure the value is sane enough to get usable velocity computed
  625.                                                 if (wall_part < 0 && wall_part > -f1_0) wall_part = -f1_0;
  626.                                                 if (wall_part > 0 && wall_part < f1_0) wall_part = f1_0;
  627.  
  628.                                                 if (forcefield_bounce || (obj->mtype.phys_info.flags & PF_BOUNCE)) {            //bounce off wall
  629.                                                         wall_part *= 2; //Subtract out wall part twice to achieve bounce
  630.  
  631. #if defined(DXX_BUILD_DESCENT_II)
  632.                                                         if (forcefield_bounce) {
  633.                                                                 check_vel = 1;                          //check for max velocity
  634.                                                                 if (obj->type == OBJ_PLAYER)
  635.                                                                         wall_part *= 2;         //player bounce twice as much
  636.                                                         }
  637.  
  638.                                                         if ( obj->mtype.phys_info.flags & PF_BOUNCES_TWICE) {
  639.                                                                 Assert(obj->mtype.phys_info.flags & PF_BOUNCE);
  640.                                                                 if (obj->mtype.phys_info.flags & PF_BOUNCED_ONCE)
  641.                                                                         obj->mtype.phys_info.flags &= ~(PF_BOUNCE+PF_BOUNCED_ONCE+PF_BOUNCES_TWICE);
  642.                                                                 else
  643.                                                                         obj->mtype.phys_info.flags |= PF_BOUNCED_ONCE;
  644.                                                         }
  645.  
  646.                                                         bounced = 1;            //this object bounced
  647. #endif
  648.                                                 }
  649.  
  650.                                                 vm_vec_scale_add2(obj->mtype.phys_info.velocity,hit_info.hit_wallnorm,-wall_part);
  651.  
  652. #if defined(DXX_BUILD_DESCENT_II)
  653.                                                 if (check_vel) {
  654.                                                         fix vel = vm_vec_mag_quick(obj->mtype.phys_info.velocity);
  655.  
  656.                                                         if (vel > MAX_OBJECT_VEL)
  657.                                                                 vm_vec_scale(obj->mtype.phys_info.velocity,fixdiv(MAX_OBJECT_VEL,vel));
  658.                                                 }
  659.  
  660.                                                 if (bounced && obj->type == OBJ_WEAPON)
  661.                                                         vm_vector_2_matrix(obj->orient,obj->mtype.phys_info.velocity,&obj->orient.uvec,nullptr);
  662. #endif
  663.  
  664.                                                 try_again = 1;
  665.                                         }
  666.                                 }
  667.  
  668.                                 break;
  669.                         }
  670.  
  671.                         case HIT_OBJECT:                {
  672.                                 vms_vector old_vel;
  673.  
  674.                                 // Mark the hit object so that on a retry the fvi code
  675.                                 // ignores this object.
  676.  
  677.                                 Assert(hit_info.hit_object != object_none);
  678.                                 //      Calculcate the hit point between the two objects.
  679.                                 {
  680.                                         fix                     size0, size1;
  681.                                         const auto &&hit = obj.absolute_sibling(hit_info.hit_object);
  682.                                         const auto &ppos0 = hit->pos;
  683.                                         const auto &ppos1 = obj->pos;
  684.                                         size0 = hit->size;
  685.                                         size1 = obj->size;
  686.                                         Assert(size0+size1 != 0);       // Error, both sizes are 0, so how did they collide, anyway?!?
  687.                                         //vm_vec_scale(vm_vec_sub(&pos_hit, ppos1, ppos0), fixdiv(size0, size0 + size1));
  688.                                         //vm_vec_add2(&pos_hit, ppos0);
  689.                                         auto pos_hit = vm_vec_sub(ppos1, ppos0);
  690.                                         vm_vec_scale_add(pos_hit,ppos0,pos_hit,fixdiv(size0, size0 + size1));
  691.  
  692.                                         old_vel = obj->mtype.phys_info.velocity;
  693.  
  694.                                         collide_two_objects( obj, hit, pos_hit);
  695.  
  696.                                 }
  697.  
  698.                                 // Let object continue its movement
  699.                                 if ( !(obj->flags&OF_SHOULD_BE_DEAD)  ) {
  700.                                         //obj->pos = save_pos;
  701.  
  702.                                         if (obj->mtype.phys_info.flags&PF_PERSISTENT || (old_vel.x == obj->mtype.phys_info.velocity.x && old_vel.y == obj->mtype.phys_info.velocity.y && old_vel.z == obj->mtype.phys_info.velocity.z)) {
  703.                                                 //if (Objects[hit_info.hit_object].type == OBJ_POWERUP)
  704.                                                 if (ignore_obj_list.push_back(hit_info.hit_object))
  705.                                                 try_again = 1;
  706.                                         }
  707.                                 }
  708.  
  709.                                 break;
  710.                         }      
  711.                         case HIT_NONE:         
  712.                                 break;
  713.  
  714. #ifndef NDEBUG
  715.                         case HIT_BAD_P0:
  716.                                 Int3();         // Unexpected collision type: start point not in specified segment.
  717.                                 break;
  718.                         default:
  719.                                 // Unknown collision type returned from find_vector_intersection!!
  720.                                 Int3();
  721.                                 break;
  722. #endif
  723.                 }
  724.         } while ( try_again );
  725.  
  726.         //      Pass retry count info to AI.
  727.         if (obj->control_type == CT_AI) {
  728.                 if (count > 0) {
  729.                         obj->ctype.ai_info.ail.retry_count = count-1;
  730. #ifndef NDEBUG
  731.                         Total_retries += count-1;
  732.                         Total_sims++;
  733. #endif
  734.                 }
  735.         }
  736.  
  737.         // After collision with objects and walls, set velocity from actual movement
  738.         if (!obj_stopped && !bounced
  739.                 && ((obj->type == OBJ_PLAYER) || (obj->type == OBJ_ROBOT) || (obj->type == OBJ_DEBRIS))
  740.                 && ((fate == HIT_WALL) || (fate == HIT_OBJECT) || (fate == HIT_BAD_P0))
  741.                 )
  742.         {      
  743.                 const auto moved_vec = vm_vec_sub(obj->pos,start_pos);
  744.                 vm_vec_copy_scale(obj->mtype.phys_info.velocity,moved_vec,fixdiv(f1_0,FrameTime));
  745.         }
  746.  
  747.         fix_illegal_wall_intersection(obj);
  748.  
  749.         //Assert(check_point_in_seg(&obj->pos,obj->segnum,0).centermask==0);
  750.  
  751.         //if (obj->control_type == CT_FLYING)
  752.         if (obj->mtype.phys_info.flags & PF_LEVELLING)
  753.                 do_physics_align_object( obj );
  754.  
  755.         //hack to keep player from going through closed doors
  756.         if (obj->type==OBJ_PLAYER && obj->segnum!=orig_segnum && (!cheats.ghostphysics) ) {
  757.  
  758.                 const cscusegment orig_segp = vcsegptr(orig_segnum);
  759.                 const auto &&sidenum = find_connect_side(vcsegptridx(obj->segnum), orig_segp);
  760.                 if (sidenum != side_none)
  761.                 {
  762.  
  763.                         if (! (WALL_IS_DOORWAY(GameBitmaps, Textures, vcwallptr, orig_segp, sidenum) & WID_FLY_FLAG))
  764.                         {
  765.                                 fix dist;
  766.  
  767.                                 //bump object back
  768.  
  769.                                 auto &s = orig_segp.s.sides[sidenum];
  770.  
  771.                                 const auto v = create_abs_vertex_lists(orig_segp, s, sidenum);
  772.                                 const auto &vertex_list = v.second;
  773.  
  774.                                 //let's pretend this wall is not triangulated
  775.                                 const auto b = begin(vertex_list);
  776.                                 const auto vertnum = *std::min_element(b, std::next(b, 4));
  777.  
  778.                                 dist = vm_dist_to_plane(start_pos, s.normals[0], vcvertptr(vertnum));
  779.                                 vm_vec_scale_add(obj->pos, start_pos, s.normals[0], obj->size-dist);
  780.                                 update_object_seg(vmobjptr, LevelSharedSegmentState, LevelUniqueSegmentState, obj);
  781.  
  782.                         }
  783.                 }
  784.         }
  785.  
  786. //--WE ALWYS WANT THIS IN, MATT AND MIKE DECISION ON 12/10/94, TWO MONTHS AFTER FINAL   #ifndef NDEBUG
  787.         //if end point not in segment, move object to last pos, or segment center
  788.         if (get_seg_masks(vcvertptr, obj->pos, vcsegptr(obj->segnum), 0).centermask != 0)
  789.         {
  790.                 if (find_object_seg(LevelSharedSegmentState, LevelUniqueSegmentState, obj) == segment_none)
  791.                 {
  792.                         segnum_t n;
  793.  
  794.                         //Int3();
  795.                         const auto &&obj_segp = Segments.vmptridx(obj->segnum);
  796.                         if (obj->type == OBJ_PLAYER && (n = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, obj_previous_position, obj_segp)) != segment_none)
  797.                         {
  798.                                 obj->pos = obj_previous_position;
  799.                                 obj_relink(vmobjptr, Segments.vmptr, obj, Segments.vmptridx(n));
  800.                         }
  801.                         else {
  802.                                 compute_segment_center(vcvertptr, obj->pos, obj_segp);
  803.                                 obj->pos.x += obj;
  804.                         }
  805.                         if (obj->type == OBJ_WEAPON)
  806.                                 obj->flags |= OF_SHOULD_BE_DEAD;
  807.                 }
  808.         }
  809.  
  810.         return result;
  811. //--WE ALWYS WANT THIS IN, MATT AND MIKE DECISION ON 12/10/94, TWO MONTHS AFTER FINAL   #endif
  812. }
  813. }
  814.  
  815. namespace dcx {
  816.  
  817. //Applies an instantaneous force on an object, resulting in an instantaneous
  818. //change in velocity.
  819. void phys_apply_force(object_base &obj, const vms_vector &force_vec)
  820. {
  821.         if (obj.movement_type != MT_PHYSICS)
  822.                 return;
  823.  
  824.         //      Put in by MK on 2/13/96 for force getting applied to Omega blobs, which have 0 mass,
  825.         //      in collision with crazy reactor robot thing on d2levf-s.
  826.         if (obj.mtype.phys_info.mass == 0)
  827.                 return;
  828.  
  829.         //Add in acceleration due to force
  830.         vm_vec_scale_add2(obj.mtype.phys_info.velocity, force_vec, fixdiv(f1_0, obj.mtype.phys_info.mass));
  831. }
  832.  
  833. //      ----------------------------------------------------------------
  834. //      Do *dest = *delta unless:
  835. //                              *delta is pretty small
  836. //              and     they are of different signs.
  837. static void physics_set_rotvel_and_saturate(fix &dest, fix delta)
  838. {
  839.         if ((delta ^ dest) < 0) {
  840.                 if (abs(delta) < F1_0/8) {
  841.                         dest = delta/4;
  842.                 } else
  843.                         dest = delta;
  844.         } else {
  845.                 dest = delta;
  846.         }
  847. }
  848.  
  849. static inline vms_angvec vm_extract_angles_vector(const vms_vector &v)
  850. {
  851.         vms_angvec a;
  852.         return vm_extract_angles_vector(a, v), a;
  853. }
  854.  
  855. //      ------------------------------------------------------------------------------------------------------
  856. //      Note: This is the old ai_turn_towards_vector code.
  857. //      phys_apply_rot used to call ai_turn_towards_vector until I fixed it, which broke phys_apply_rot.
  858. void physics_turn_towards_vector(const vms_vector &goal_vector, object_base &obj, fix rate)
  859. {
  860.         fix                     delta_p, delta_h;
  861.  
  862.         // Make this object turn towards the goal_vector.  Changes orientation, doesn't change direction of movement.
  863.         // If no one moves, will be facing goal_vector in 1 second.
  864.  
  865.         //      Detect null vector.
  866.         if ((goal_vector.x == 0) && (goal_vector.y == 0) && (goal_vector.z == 0))
  867.                 return;
  868.  
  869.         //      Make morph objects turn more slowly.
  870.         if (obj.control_type == CT_MORPH)
  871.                 rate *= 2;
  872.  
  873.         const auto dest_angles = vm_extract_angles_vector(goal_vector);
  874.         const auto cur_angles = vm_extract_angles_vector(obj.orient.fvec);
  875.  
  876.         delta_p = (dest_angles.p - cur_angles.p);
  877.         delta_h = (dest_angles.h - cur_angles.h);
  878.  
  879.         if (delta_p > F1_0/2) delta_p = dest_angles.p - cur_angles.p - F1_0;
  880.         if (delta_p < -F1_0/2) delta_p = dest_angles.p - cur_angles.p + F1_0;
  881.         if (delta_h > F1_0/2) delta_h = dest_angles.h - cur_angles.h - F1_0;
  882.         if (delta_h < -F1_0/2) delta_h = dest_angles.h - cur_angles.h + F1_0;
  883.  
  884.         delta_p = fixdiv(delta_p, rate);
  885.         delta_h = fixdiv(delta_h, rate);
  886.  
  887.         if (abs(delta_p) < F1_0/16) delta_p *= 4;
  888.         if (abs(delta_h) < F1_0/16) delta_h *= 4;
  889.  
  890.         auto &rotvel_ptr = obj.mtype.phys_info.rotvel;
  891.         physics_set_rotvel_and_saturate(rotvel_ptr.x, delta_p);
  892.         physics_set_rotvel_and_saturate(rotvel_ptr.y, delta_h);
  893.         rotvel_ptr.z = 0;
  894. }
  895.  
  896. }
  897.  
  898. namespace dsx {
  899.  
  900. //      -----------------------------------------------------------------------------
  901. //      Applies an instantaneous whack on an object, resulting in an instantaneous
  902. //      change in orientation.
  903. void phys_apply_rot(object &obj, const vms_vector &force_vec)
  904. {
  905.         fix     rate;
  906.  
  907.         if (obj.movement_type != MT_PHYSICS)
  908.                 return;
  909.  
  910. #if defined(DXX_BUILD_DESCENT_II)
  911.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  912. #endif
  913.         auto vecmag = vm_vec_mag(force_vec);
  914.         if (vecmag < F1_0/32)
  915.                 rate = 4*F1_0;
  916.         else if (vecmag < obj.mtype.phys_info.mass >> 11)
  917.                 rate = 4*F1_0;
  918.         else {
  919.                 rate = fixdiv(obj.mtype.phys_info.mass, vecmag / 8);
  920.                 if (obj.type == OBJ_ROBOT) {
  921.                         if (rate < F1_0/4)
  922.                                 rate = F1_0/4;
  923. #if defined(DXX_BUILD_DESCENT_I)
  924.                         obj.ctype.ai_info.SKIP_AI_COUNT = 2;
  925. #elif defined(DXX_BUILD_DESCENT_II)
  926.                         //      Changed by mk, 10/24/95, claw guys should not slow down when attacking!
  927.                         if (!Robot_info[get_robot_id(obj)].thief && !Robot_info[get_robot_id(obj)].attack_type) {
  928.                                 if (obj.ctype.ai_info.SKIP_AI_COUNT * FrameTime < 3*F1_0/4) {
  929.                                         fix     tval = fixdiv(F1_0, 8*FrameTime);
  930.                                         int     addval;
  931.  
  932.                                         addval = f2i(tval);
  933.  
  934.                                         if ( (d_rand() * 2) < (tval & 0xffff))
  935.                                                 addval++;
  936.                                         obj.ctype.ai_info.SKIP_AI_COUNT += addval;
  937.                                 }
  938.                         }
  939. #endif
  940.                 } else {
  941.                         if (rate < F1_0/2)
  942.                                 rate = F1_0/2;
  943.                 }
  944.         }
  945.  
  946.         //      Turn amount inversely proportional to mass.  Third parameter is seconds to do 360 turn.
  947.         physics_turn_towards_vector(force_vec, obj, rate);
  948. }
  949.  
  950. }
  951.  
  952. namespace dcx {
  953.  
  954. //this routine will set the thrust for an object to a value that will
  955. //(hopefully) maintain the object's current velocity
  956. void set_thrust_from_velocity(object_base &obj)
  957. {
  958.         Assert(obj.movement_type == MT_PHYSICS);
  959.         auto &phys_info = obj.mtype.phys_info;
  960.         vm_vec_copy_scale(phys_info.thrust, phys_info.velocity,
  961.                 fixmuldiv(phys_info.mass, phys_info.drag, F1_0 - phys_info.drag)
  962.         );
  963. }
  964.  
  965. }
  966.