/* * Portions of this file are copyright Rebirth contributors and licensed as * described in COPYING.txt. * Portions of this file are copyright Parallax Software and licensed * according to the Parallax license below. * See COPYING.txt for license details. THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE. COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED. */ /* * * Code for flying through the mines * */ #include #include #include "joy.h" #include "dxxerror.h" #include "inferno.h" #include "segment.h" #include "object.h" #include "physics.h" #include "robot.h" #include "key.h" #include "game.h" #include "collide.h" #include "fvi.h" #include "newdemo.h" #include "gameseg.h" #include "timer.h" #include "ai.h" #include "wall.h" #include "laser.h" #if defined(DXX_BUILD_DESCENT_II) #include "bm.h" #include "player.h" #define MAX_OBJECT_VEL i2f(100) #endif #include "dxxsconf.h" #include "compiler-range_for.h" //Global variables for physics system #define ROLL_RATE 0x2000 #define DAMP_ANG 0x400 //min angle to bank #define TURNROLL_SCALE (0x4ec4/2) //check point against each side of segment. return bitmask, where bit //set means behind that side //make sure matrix is orthogonal void check_and_fix_matrix(vms_matrix &m) { m = vm_vector_2_matrix(m.fvec,&m.uvec,nullptr); } static void do_physics_align_object(object_base &obj) { vms_vector desired_upvec; fixang delta_ang,roll_ang; fix largest_d = INT32_MIN; const shared_side *best_side = nullptr; // bank player according to segment orientation //find side of segment that player is most alligned with range_for (auto &i, vcsegptr(obj.segnum)->shared_segment::sides) { const auto d = vm_vec_dot(i.normals[0], obj.orient.uvec); if (largest_d < d) { largest_d = d; best_side = &i; } } // new player leveling code: use normal of side closest to our up vec if (!best_side) return; if (!get_side_is_quad(*best_side)) { desired_upvec = vm_vec_avg(best_side->normals[0], best_side->normals[1]); vm_vec_normalize(desired_upvec); } else desired_upvec = best_side->normals[0]; if (labs(vm_vec_dot(desired_upvec, obj.orient.fvec)) < f1_0 / 2) { vms_angvec tangles; const auto temp_matrix = vm_vector_2_matrix(obj.orient.fvec, &desired_upvec, nullptr); delta_ang = vm_vec_delta_ang(obj.orient.uvec, temp_matrix.uvec, obj.orient.fvec); delta_ang += obj.mtype.phys_info.turnroll; if (abs(delta_ang) > DAMP_ANG) { roll_ang = fixmul(FrameTime,ROLL_RATE); if (abs(delta_ang) < roll_ang) roll_ang = delta_ang; else if (delta_ang<0) roll_ang = -roll_ang; tangles.p = tangles.h = 0; tangles.b = roll_ang; const auto &&rotmat = vm_angles_2_matrix(tangles); obj.orient = vm_matrix_x_matrix(obj.orient, rotmat); } } } static void set_object_turnroll(object_base &obj) { fixang desired_bank; desired_bank = -fixmul(obj.mtype.phys_info.rotvel.y, TURNROLL_SCALE); if (obj.mtype.phys_info.turnroll != desired_bank) { fixang delta_ang,max_roll; max_roll = fixmul(ROLL_RATE,FrameTime); delta_ang = desired_bank - obj.mtype.phys_info.turnroll; if (labs(delta_ang) < max_roll) max_roll = delta_ang; else if (delta_ang < 0) max_roll = -max_roll; obj.mtype.phys_info.turnroll += max_roll; } } #define MAX_IGNORE_OBJS 100 #ifndef NDEBUG int Total_retries=0, Total_sims=0; int Dont_move_ai_objects=0; #endif #define FT (f1_0/64) // ----------------------------------------------------------------------------------------------------------- // add rotational velocity & acceleration namespace dsx { static void do_physics_sim_rot(object_base &obj) { vms_angvec tangles; //fix rotdrag_scale; physics_info *pi; Assert(FrameTime > 0); //Get MATT if hit this! pi = &obj.mtype.phys_info; if (!(pi->rotvel.x || pi->rotvel.y || pi->rotvel.z || pi->rotthrust.x || pi->rotthrust.y || pi->rotthrust.z)) return; if (obj.mtype.phys_info.drag) { int count; fix drag,r,k; count = FrameTime / FT; r = FrameTime % FT; k = fixdiv(r,FT); drag = (obj.mtype.phys_info.drag * 5) / 2; if (obj.mtype.phys_info.flags & PF_USES_THRUST) { const auto accel = vm_vec_copy_scale(obj.mtype.phys_info.rotthrust, fixdiv(f1_0, obj.mtype.phys_info.mass)); while (count--) { vm_vec_add2(obj.mtype.phys_info.rotvel, accel); vm_vec_scale(obj.mtype.phys_info.rotvel, f1_0 - drag); } //do linear scale on remaining bit of time vm_vec_scale_add2(obj.mtype.phys_info.rotvel, accel, k); vm_vec_scale(obj.mtype.phys_info.rotvel, f1_0 - fixmul(k, drag)); } else #if defined(DXX_BUILD_DESCENT_II) if (! (obj.mtype.phys_info.flags & PF_FREE_SPINNING)) #endif { fix total_drag=f1_0; while (count--) total_drag = fixmul(total_drag,f1_0-drag); //do linear scale on remaining bit of time total_drag = fixmul(total_drag,f1_0-fixmul(k,drag)); vm_vec_scale(obj.mtype.phys_info.rotvel, total_drag); } } //now rotate object //unrotate object for bank caused by turn if (obj.mtype.phys_info.turnroll) { tangles.p = tangles.h = 0; tangles.b = -obj.mtype.phys_info.turnroll; const auto &&rotmat = vm_angles_2_matrix(tangles); obj.orient = vm_matrix_x_matrix(obj.orient, rotmat); } const auto frametime = FrameTime; tangles.p = fixmul(obj.mtype.phys_info.rotvel.x, frametime); tangles.h = fixmul(obj.mtype.phys_info.rotvel.y, frametime); tangles.b = fixmul(obj.mtype.phys_info.rotvel.z, frametime); obj.orient = vm_matrix_x_matrix(obj.orient, vm_angles_2_matrix(tangles)); if (obj.mtype.phys_info.flags & PF_TURNROLL) set_object_turnroll(obj); //re-rotate object for bank caused by turn if (obj.mtype.phys_info.turnroll) { tangles.p = tangles.h = 0; tangles.b = obj.mtype.phys_info.turnroll; obj.orient = vm_matrix_x_matrix(obj.orient, vm_angles_2_matrix(tangles)); } check_and_fix_matrix(obj.orient); } } // 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. static void fix_illegal_wall_intersection(const vmobjptridx_t obj) { auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); auto &Objects = LevelUniqueObjectState.Objects; auto &Vertices = LevelSharedVertexState.get_vertices(); auto &vmobjptr = Objects.vmptr; if (!(obj->type == OBJ_PLAYER || obj->type == OBJ_ROBOT)) return; auto &vcvertptr = Vertices.vcptr; const auto &&hresult = sphere_intersects_wall(vcvertptr, obj->pos, vcsegptridx(obj->segnum), obj->size); if (hresult.seg) { vm_vec_scale_add2(obj->pos, hresult.seg->sides[hresult.side].normals[0], FrameTime*10); update_object_seg(vmobjptr, LevelSharedSegmentState, LevelUniqueSegmentState, obj); } } namespace { class ignore_objects_array_t { using array_t = std::array; array_t::iterator e; union { array_t a; }; public: /* The iterator should be initialized in a * member-initialization-list. However, clang complains that the * union is uninitialized during the member-initialization-list, but * accepts the still-uninitialized union member once the constructor * body starts. Assign the iterator in the body to silence this * useless clang warning. * * Known bad: * clang-5 * clang-7 */ ignore_objects_array_t() { e = a.begin(); } bool push_back(const vcobjidx_t o) { if (unlikely(e == a.end())) return false; std::uninitialized_fill_n(e, 1, o); ++e; return true; } operator std::pair() const { return {a.begin(), e}; } }; } // ----------------------------------------------------------------------------------------------------------- //Simulate a physics object for this frame namespace dsx { window_event_result do_physics_sim(const vmobjptridx_t obj, const vms_vector &obj_previous_position, phys_visited_seglist *const phys_segs) { auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); auto &Objects = LevelUniqueObjectState.Objects; auto &Vertices = LevelSharedVertexState.get_vertices(); auto &vcobjptr = Objects.vcptr; auto &vmobjptr = Objects.vmptr; ignore_objects_array_t ignore_obj_list; int try_again; int fate=0; vms_vector ipos; //position after this frame segnum_t WallHitSeg; int WallHitSide; fvi_info hit_info; fvi_query fq; vms_vector save_pos; fix drag; fix sim_time; vms_vector start_pos; int obj_stopped=0; fix moved_time; //how long objected moved before hit something physics_info *pi; auto orig_segnum = obj->segnum; int bounced=0; bool Player_ScrapeFrame=false; auto result = window_event_result::handled; #if defined(DXX_BUILD_DESCENT_II) auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo; #endif Assert(obj->movement_type == MT_PHYSICS); #ifndef NDEBUG if (Dont_move_ai_objects) if (obj->control_type == CT_AI) return window_event_result::ignored; #endif pi = &obj->mtype.phys_info; do_physics_sim_rot(obj); if (!(pi->velocity.x || pi->velocity.y || pi->velocity.z || pi->thrust.x || pi->thrust.y || pi->thrust.z)) return window_event_result::ignored; sim_time = FrameTime; start_pos = obj->pos; //if uses thrust, cannot have zero drag Assert(!(obj->mtype.phys_info.flags&PF_USES_THRUST) || obj->mtype.phys_info.drag!=0); //do thrust & drag if ((drag = obj->mtype.phys_info.drag) != 0) { int count; fix r,k,have_accel; count = FrameTime / FT; r = FrameTime % FT; k = fixdiv(r,FT); if (obj->mtype.phys_info.flags & PF_USES_THRUST) { const auto accel = vm_vec_copy_scale(obj->mtype.phys_info.thrust,fixdiv(f1_0,obj->mtype.phys_info.mass)); have_accel = (accel.x || accel.y || accel.z); while (count--) { if (have_accel) vm_vec_add2(obj->mtype.phys_info.velocity,accel); vm_vec_scale(obj->mtype.phys_info.velocity,f1_0-drag); } //do linear scale on remaining bit of time vm_vec_scale_add2(obj->mtype.phys_info.velocity,accel,k); if (drag) vm_vec_scale(obj->mtype.phys_info.velocity,f1_0-fixmul(k,drag)); } else if (drag) { fix total_drag=f1_0; while (count--) total_drag = fixmul(total_drag,f1_0-drag); //do linear scale on remaining bit of time total_drag = fixmul(total_drag,f1_0-fixmul(k,drag)); vm_vec_scale(obj->mtype.phys_info.velocity,total_drag); } } int count = 0; auto &vcvertptr = Vertices.vcptr; auto &Walls = LevelUniqueWallSubsystemState.Walls; auto &vcwallptr = Walls.vcptr; do { try_again = 0; //Move the object const auto frame_vec = vm_vec_copy_scale(obj->mtype.phys_info.velocity, sim_time); if ( (frame_vec.x==0) && (frame_vec.y==0) && (frame_vec.z==0) ) break; count++; // If retry count is getting large, then we are trying to do something stupid. 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. const auto new_pos = vm_vec_add(obj->pos,frame_vec); fq.p0 = &obj->pos; fq.startseg = obj->segnum; fq.p1 = &new_pos; fq.rad = obj->size; fq.thisobjnum = obj; fq.ignore_obj_list = ignore_obj_list; fq.flags = FQ_CHECK_OBJS; if (obj->type == OBJ_WEAPON) fq.flags |= FQ_TRANSPOINT; if (phys_segs) fq.flags |= FQ_GET_SEGLIST; fate = find_vector_intersection(fq, hit_info); // Matt: Mike's hack. if (fate == HIT_OBJECT) { auto &objp = *vcobjptr(hit_info.hit_object); if ((objp.type == OBJ_WEAPON && is_proximity_bomb_or_player_smart_mine(get_weapon_id(objp))) || objp.type == OBJ_POWERUP) // do not increase count for powerups since they *should* not change our movement count--; } #ifndef NDEBUG if (fate == HIT_BAD_P0) { Int3(); } #endif if (phys_segs && !hit_info.seglist.empty()) { auto n_phys_segs = phys_segs->nsegs; if (n_phys_segs && phys_segs->seglist[n_phys_segs - 1] == hit_info.seglist[0]) n_phys_segs--; range_for (const auto &hs, hit_info.seglist) { if (!(n_phys_segs < phys_segs->seglist.size() - 1)) break; phys_segs->seglist[n_phys_segs++] = hs; } phys_segs->nsegs = n_phys_segs; } ipos = hit_info.hit_pnt; auto iseg = hit_info.hit_seg; WallHitSide = hit_info.hit_side; WallHitSeg = hit_info.hit_side_seg; if (iseg==segment_none) { //some sort of horrible error if (obj->type == OBJ_WEAPON) obj->flags |= OF_SHOULD_BE_DEAD; break; } Assert(!((fate==HIT_WALL) && ((WallHitSeg == segment_none) || (WallHitSeg > Highest_segment_index)))); save_pos = obj->pos; //save the object's position auto save_seg = obj->segnum; // update object's position and segment number obj->pos = ipos; const auto &&obj_segp = Segments.vmptridx(iseg); if ( iseg != obj->segnum ) obj_relink(vmobjptr, Segments.vmptr, obj, obj_segp); //if start point not in segment, move object to center of segment if (get_seg_masks(vcvertptr, obj->pos, Segments.vcptr(obj->segnum), 0).centermask != 0) { auto n = find_object_seg(LevelSharedSegmentState, LevelUniqueSegmentState, obj); if (n == segment_none) { //Int3(); if (obj->type == OBJ_PLAYER && (n = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, obj_previous_position, obj_segp)) != segment_none) { obj->pos = obj_previous_position; obj_relink(vmobjptr, Segments.vmptr, obj, n); } else { compute_segment_center(vcvertptr, obj->pos, obj_segp); obj->pos.x += obj; } if (obj->type == OBJ_WEAPON) obj->flags |= OF_SHOULD_BE_DEAD; } return window_event_result::ignored; } //calulate new sim time { //vms_vector moved_vec; vms_vector moved_vec_n; fix attempted_dist,actual_dist; actual_dist = vm_vec_normalized_dir(moved_vec_n,obj->pos,save_pos); if (fate==HIT_WALL && vm_vec_dot(moved_vec_n,frame_vec) < 0) { //moved backwards //don't change position or sim_time obj->pos = save_pos; //iseg = obj->segnum; //don't change segment obj_relink(vmobjptr, Segments.vmptr, obj, Segments.vmptridx(save_seg)); moved_time = 0; } else { fix old_sim_time; attempted_dist = vm_vec_mag(frame_vec); old_sim_time = sim_time; sim_time = fixmuldiv(sim_time,attempted_dist-actual_dist,attempted_dist); moved_time = old_sim_time - sim_time; if (sim_time < 0 || sim_time>old_sim_time) { sim_time = old_sim_time; //WHY DOES THIS HAPPEN?? moved_time = 0; } } } switch( fate ) { case HIT_WALL: { fix hit_speed=0,wall_part=0; // Find hit speed const auto moved_v = vm_vec_sub(obj->pos,save_pos); wall_part = vm_vec_dot(moved_v,hit_info.hit_wallnorm); if ((wall_part != 0 && moved_time>0 && (hit_speed=-fixdiv(wall_part,moved_time))>0) || obj->type == OBJ_WEAPON || obj->type == OBJ_DEBRIS) result = collide_object_with_wall( #if defined(DXX_BUILD_DESCENT_II) LevelSharedSegmentState.DestructibleLights, #endif obj, hit_speed, Segments.vmptridx(WallHitSeg), WallHitSide, hit_info.hit_pnt); /* * Due to the nature of this loop, it's possible that a local player may receive scrape damage multiple times in one frame. * Check if we received damage and do not apply more damage (nor produce damage sounds/flashes/bumps, etc) for the rest of the loop. * 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). * 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. */ if (obj->type == OBJ_PLAYER && Player_ScrapeFrame == false) Player_ScrapeFrame = scrape_player_on_wall(obj, Segments.vmptridx(WallHitSeg), WallHitSide, hit_info.hit_pnt); Assert( WallHitSeg != segment_none ); Assert( WallHitSide > -1 ); if ( !(obj->flags&OF_SHOULD_BE_DEAD) ) { int forcefield_bounce; //bounce off a forcefield #if defined(DXX_BUILD_DESCENT_II) if (!cheats.bouncyfire) #endif Assert(!(obj->mtype.phys_info.flags & PF_STICK && obj->mtype.phys_info.flags & PF_BOUNCE)); //can't be bounce and stick #if defined(DXX_BUILD_DESCENT_I) /* * Force fields are not supported in Descent 1. Use * this as a placeholder to make the code match the * force field handling in Descent 2. */ forcefield_bounce = 0; #elif defined(DXX_BUILD_DESCENT_II) forcefield_bounce = (TmapInfo[Segments[WallHitSeg].unique_segment::sides[WallHitSide].tmap_num].flags & TMI_FORCE_FIELD); int check_vel=0; #endif if (!forcefield_bounce && (obj->mtype.phys_info.flags & PF_STICK)) { //stop moving LevelUniqueStuckObjectState.add_stuck_object(vcwallptr, obj, Segments.vmptr(WallHitSeg), WallHitSide); vm_vec_zero(obj->mtype.phys_info.velocity); obj_stopped = 1; try_again = 0; } else { // Slide object along wall wall_part = vm_vec_dot(hit_info.hit_wallnorm,obj->mtype.phys_info.velocity); // if wall_part, make sure the value is sane enough to get usable velocity computed if (wall_part < 0 && wall_part > -f1_0) wall_part = -f1_0; if (wall_part > 0 && wall_part < f1_0) wall_part = f1_0; if (forcefield_bounce || (obj->mtype.phys_info.flags & PF_BOUNCE)) { //bounce off wall wall_part *= 2; //Subtract out wall part twice to achieve bounce #if defined(DXX_BUILD_DESCENT_II) if (forcefield_bounce) { check_vel = 1; //check for max velocity if (obj->type == OBJ_PLAYER) wall_part *= 2; //player bounce twice as much } if ( obj->mtype.phys_info.flags & PF_BOUNCES_TWICE) { Assert(obj->mtype.phys_info.flags & PF_BOUNCE); if (obj->mtype.phys_info.flags & PF_BOUNCED_ONCE) obj->mtype.phys_info.flags &= ~(PF_BOUNCE+PF_BOUNCED_ONCE+PF_BOUNCES_TWICE); else obj->mtype.phys_info.flags |= PF_BOUNCED_ONCE; } bounced = 1; //this object bounced #endif } vm_vec_scale_add2(obj->mtype.phys_info.velocity,hit_info.hit_wallnorm,-wall_part); #if defined(DXX_BUILD_DESCENT_II) if (check_vel) { fix vel = vm_vec_mag_quick(obj->mtype.phys_info.velocity); if (vel > MAX_OBJECT_VEL) vm_vec_scale(obj->mtype.phys_info.velocity,fixdiv(MAX_OBJECT_VEL,vel)); } if (bounced && obj->type == OBJ_WEAPON) vm_vector_2_matrix(obj->orient,obj->mtype.phys_info.velocity,&obj->orient.uvec,nullptr); #endif try_again = 1; } } break; } case HIT_OBJECT: { vms_vector old_vel; // Mark the hit object so that on a retry the fvi code // ignores this object. Assert(hit_info.hit_object != object_none); // Calculcate the hit point between the two objects. { fix size0, size1; const auto &&hit = obj.absolute_sibling(hit_info.hit_object); const auto &ppos0 = hit->pos; const auto &ppos1 = obj->pos; size0 = hit->size; size1 = obj->size; Assert(size0+size1 != 0); // Error, both sizes are 0, so how did they collide, anyway?!? //vm_vec_scale(vm_vec_sub(&pos_hit, ppos1, ppos0), fixdiv(size0, size0 + size1)); //vm_vec_add2(&pos_hit, ppos0); auto pos_hit = vm_vec_sub(ppos1, ppos0); vm_vec_scale_add(pos_hit,ppos0,pos_hit,fixdiv(size0, size0 + size1)); old_vel = obj->mtype.phys_info.velocity; collide_two_objects( obj, hit, pos_hit); } // Let object continue its movement if ( !(obj->flags&OF_SHOULD_BE_DEAD) ) { //obj->pos = save_pos; 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)) { //if (Objects[hit_info.hit_object].type == OBJ_POWERUP) if (ignore_obj_list.push_back(hit_info.hit_object)) try_again = 1; } } break; } case HIT_NONE: break; #ifndef NDEBUG case HIT_BAD_P0: Int3(); // Unexpected collision type: start point not in specified segment. break; default: // Unknown collision type returned from find_vector_intersection!! Int3(); break; #endif } } while ( try_again ); // Pass retry count info to AI. if (obj->control_type == CT_AI) { if (count > 0) { obj->ctype.ai_info.ail.retry_count = count-1; #ifndef NDEBUG Total_retries += count-1; Total_sims++; #endif } } // After collision with objects and walls, set velocity from actual movement if (!obj_stopped && !bounced && ((obj->type == OBJ_PLAYER) || (obj->type == OBJ_ROBOT) || (obj->type == OBJ_DEBRIS)) && ((fate == HIT_WALL) || (fate == HIT_OBJECT) || (fate == HIT_BAD_P0)) ) { const auto moved_vec = vm_vec_sub(obj->pos,start_pos); vm_vec_copy_scale(obj->mtype.phys_info.velocity,moved_vec,fixdiv(f1_0,FrameTime)); } fix_illegal_wall_intersection(obj); //Assert(check_point_in_seg(&obj->pos,obj->segnum,0).centermask==0); //if (obj->control_type == CT_FLYING) if (obj->mtype.phys_info.flags & PF_LEVELLING) do_physics_align_object( obj ); //hack to keep player from going through closed doors if (obj->type==OBJ_PLAYER && obj->segnum!=orig_segnum && (!cheats.ghostphysics) ) { const cscusegment orig_segp = vcsegptr(orig_segnum); const auto &&sidenum = find_connect_side(vcsegptridx(obj->segnum), orig_segp); if (sidenum != side_none) { if (! (WALL_IS_DOORWAY(GameBitmaps, Textures, vcwallptr, orig_segp, sidenum) & WID_FLY_FLAG)) { fix dist; //bump object back auto &s = orig_segp.s.sides[sidenum]; const auto v = create_abs_vertex_lists(orig_segp, s, sidenum); const auto &vertex_list = v.second; //let's pretend this wall is not triangulated const auto b = begin(vertex_list); const auto vertnum = *std::min_element(b, std::next(b, 4)); dist = vm_dist_to_plane(start_pos, s.normals[0], vcvertptr(vertnum)); vm_vec_scale_add(obj->pos, start_pos, s.normals[0], obj->size-dist); update_object_seg(vmobjptr, LevelSharedSegmentState, LevelUniqueSegmentState, obj); } } } //--WE ALWYS WANT THIS IN, MATT AND MIKE DECISION ON 12/10/94, TWO MONTHS AFTER FINAL #ifndef NDEBUG //if end point not in segment, move object to last pos, or segment center if (get_seg_masks(vcvertptr, obj->pos, vcsegptr(obj->segnum), 0).centermask != 0) { if (find_object_seg(LevelSharedSegmentState, LevelUniqueSegmentState, obj) == segment_none) { segnum_t n; //Int3(); const auto &&obj_segp = Segments.vmptridx(obj->segnum); if (obj->type == OBJ_PLAYER && (n = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, obj_previous_position, obj_segp)) != segment_none) { obj->pos = obj_previous_position; obj_relink(vmobjptr, Segments.vmptr, obj, Segments.vmptridx(n)); } else { compute_segment_center(vcvertptr, obj->pos, obj_segp); obj->pos.x += obj; } if (obj->type == OBJ_WEAPON) obj->flags |= OF_SHOULD_BE_DEAD; } } return result; //--WE ALWYS WANT THIS IN, MATT AND MIKE DECISION ON 12/10/94, TWO MONTHS AFTER FINAL #endif } } namespace dcx { //Applies an instantaneous force on an object, resulting in an instantaneous //change in velocity. void phys_apply_force(object_base &obj, const vms_vector &force_vec) { if (obj.movement_type != MT_PHYSICS) return; // Put in by MK on 2/13/96 for force getting applied to Omega blobs, which have 0 mass, // in collision with crazy reactor robot thing on d2levf-s. if (obj.mtype.phys_info.mass == 0) return; //Add in acceleration due to force vm_vec_scale_add2(obj.mtype.phys_info.velocity, force_vec, fixdiv(f1_0, obj.mtype.phys_info.mass)); } // ---------------------------------------------------------------- // Do *dest = *delta unless: // *delta is pretty small // and they are of different signs. static void physics_set_rotvel_and_saturate(fix &dest, fix delta) { if ((delta ^ dest) < 0) { if (abs(delta) < F1_0/8) { dest = delta/4; } else dest = delta; } else { dest = delta; } } static inline vms_angvec vm_extract_angles_vector(const vms_vector &v) { vms_angvec a; return vm_extract_angles_vector(a, v), a; } // ------------------------------------------------------------------------------------------------------ // 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(const vms_vector &goal_vector, object_base &obj, fix rate) { fix delta_p, delta_h; // 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; const auto dest_angles = vm_extract_angles_vector(goal_vector); const auto cur_angles = vm_extract_angles_vector(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; auto &rotvel_ptr = obj.mtype.phys_info.rotvel; physics_set_rotvel_and_saturate(rotvel_ptr.x, delta_p); physics_set_rotvel_and_saturate(rotvel_ptr.y, delta_h); rotvel_ptr.z = 0; } } namespace dsx { // ----------------------------------------------------------------------------- // Applies an instantaneous whack on an object, resulting in an instantaneous // change in orientation. void phys_apply_rot(object &obj, const vms_vector &force_vec) { fix rate; if (obj.movement_type != MT_PHYSICS) return; #if defined(DXX_BUILD_DESCENT_II) auto &Robot_info = LevelSharedRobotInfoState.Robot_info; #endif auto vecmag = vm_vec_mag(force_vec); if (vecmag < F1_0/32) rate = 4*F1_0; else if (vecmag < obj.mtype.phys_info.mass >> 11) rate = 4*F1_0; else { rate = fixdiv(obj.mtype.phys_info.mass, vecmag / 8); if (obj.type == OBJ_ROBOT) { if (rate < F1_0/4) rate = F1_0/4; #if defined(DXX_BUILD_DESCENT_I) obj.ctype.ai_info.SKIP_AI_COUNT = 2; #elif defined(DXX_BUILD_DESCENT_II) // Changed by mk, 10/24/95, claw guys should not slow down when attacking! if (!Robot_info[get_robot_id(obj)].thief && !Robot_info[get_robot_id(obj)].attack_type) { if (obj.ctype.ai_info.SKIP_AI_COUNT * FrameTime < 3*F1_0/4) { fix tval = fixdiv(F1_0, 8*FrameTime); int addval; addval = f2i(tval); if ( (d_rand() * 2) < (tval & 0xffff)) addval++; obj.ctype.ai_info.SKIP_AI_COUNT += addval; } } #endif } 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); } } namespace dcx { //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_base &obj) { Assert(obj.movement_type == MT_PHYSICS); auto &phys_info = obj.mtype.phys_info; vm_vec_copy_scale(phys_info.thrust, phys_info.velocity, fixmuldiv(phys_info.mass, phys_info.drag, F1_0 - phys_info.drag) ); } }