dxx-rebirth/similar/main/physics.cpp
Chris Taylor 4cc801f42f Remove calls to window_close(Game_wind) when game finished or over
Replace calls to window_close(Game_wind) with returning window_event_result::close to game handler. Applies to when DoEndGame() is called, DoGameOver() is called, aborting in the kmatrix screen (multiplayer game) during AdvanceLevel() and playing one demo frame causes playback to stop in GameProcessFrame(). Closing a window within its handler is problematic - it can result in an unstable state.
2017-01-08 21:08:50 +08:00

928 lines
26 KiB
C++

/*
* 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 <stdio.h>
#include <stdlib.h>
#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;
//vms_vector forvec = {0,0,f1_0};
fix largest_d=-f1_0;
const 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)->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 (get_num_faces(best_side) == 2)
{
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;
}
}
//list of segments went through
unsigned n_phys_segs;
array<segnum_t, MAX_FVI_SEGS> phys_seglist;
#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 vobjptridx_t obj)
{
if (!(obj->type == OBJ_PLAYER || obj->type == OBJ_ROBOT))
return;
object_intersects_wall_result_t hresult;
if (object_intersects_wall_d(obj, hresult))
{
vm_vec_scale_add2(obj->pos, Segments[hresult.seg].sides[hresult.side].normals[0], FrameTime*10);
update_object_seg(obj);
}
}
namespace {
class ignore_objects_array_t
{
typedef array<objnum_t, MAX_IGNORE_OBJS> array_t;
array_t a;
array_t::iterator e;
public:
ignore_objects_array_t() :
e(a.begin())
{
}
bool push_back(objnum_t o)
{
if (e == a.end())
return false;
*e++ = o;
return true;
}
operator std::pair<const objnum_t *, const objnum_t *>() const
{
return {a.begin(), e};
}
};
}
// -----------------------------------------------------------------------------------------------------------
//Simulate a physics object for this frame
namespace dsx {
window_event_result do_physics_sim(const vobjptridx_t obj)
{
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;
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;
n_phys_segs = 0;
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;
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 (obj->type == OBJ_PLAYER)
fq.flags |= FQ_GET_SEGLIST;
fate = find_vector_intersection(fq, hit_info);
// Matt: Mike's hack.
if (fate == HIT_OBJECT) {
const auto &&objp = vcobjptr(hit_info.hit_object);
if (((objp->type == OBJ_WEAPON) && is_proximity_bomb_or_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 (obj->type == OBJ_PLAYER) {
if (n_phys_segs && !hit_info.seglist.empty() && phys_seglist[n_phys_segs-1]==hit_info.seglist[0])
n_phys_segs--;
range_for (const auto &hs, hit_info.seglist)
{
if (!(n_phys_segs < MAX_FVI_SEGS-1))
break;
phys_seglist[n_phys_segs++] = hs;
}
}
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;
if ( iseg != obj->segnum )
obj_relink(obj, vsegptridx(iseg));
//if start point not in segment, move object to center of segment
if (get_seg_masks(obj->pos, vcsegptr(obj->segnum), 0).centermask !=0 )
{
auto n = find_object_seg(obj);
if (n == segment_none)
{
//Int3();
if (obj->type == OBJ_PLAYER && (n = find_point_seg(obj->last_pos, vsegptridx(obj->segnum))) != segment_none)
{
obj->pos = obj->last_pos;
obj_relink(obj, n);
}
else {
compute_segment_center(obj->pos, vcsegptr(obj->segnum));
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(obj, vsegptridx(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(obj, hit_speed, vsegptridx(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, vsegptridx(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].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
add_stuck_object(obj, vsegptr(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 auto orig_segp = vcsegptr(orig_segnum);
const auto &&sidenum = find_connect_side(vcsegptridx(obj->segnum), orig_segp);
if (sidenum != side_none)
{
if (! (WALL_IS_DOORWAY(orig_segp,sidenum) & WID_FLY_FLAG)) {
fix dist;
//bump object back
auto s = &orig_segp->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
auto b = begin(vertex_list);
auto vertnum = *std::min_element(b, std::next(b, 4));
dist = vm_dist_to_plane(start_pos, s->normals[0], Vertices[vertnum]);
vm_vec_scale_add(obj->pos,start_pos,s->normals[0],obj->size-dist);
update_object_seg(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(obj->pos, vcsegptr(obj->segnum), 0).centermask != 0)
{
if (find_object_seg(obj)==segment_none) {
segnum_t n;
//Int3();
const auto &&obj_segp = vsegptridx(obj->segnum);
if (obj->type==OBJ_PLAYER && (n = find_point_seg(obj->last_pos,obj_segp)) != segment_none)
{
obj->pos = obj->last_pos;
obj_relink(obj, vsegptridx(n));
}
else {
compute_segment_center(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;
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)
);
}
}