1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447
|
/*
* Copyright (C) Volition, Inc. 1999. All rights reserved.
*
* All source code herein is the property of Volition, Inc. You may not sell
* or otherwise commercially exploit the source or things you created based on the
* source.
*
*/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include "ai/ai_profiles.h" // for the damping issue
#include "freespace.h"
#include "io/timer.h"
#include "mission/missionparse.h"
#include "mod_table/mod_table.h"
#include "physics/physics.h"
#include "ship/ship.h"
#include "utils/Random.h"
// defines for physics functions
#define MAX_TURN_LIMIT 0.2618f // about 15 degrees
#define ROTVEL_TOL 0.1 // Amount of rotvel is decreased if over cap
#define ROTVEL_CAP 14.0 // Rotational velocity cap for live objects
#define DEAD_ROTVEL_CAP 16.3 // Rotational velocity cap for dead objects
constexpr float MAX_SHIP_WHACK_DELTA = 500.0f; // Maximum change in speed allowed after whack or shockwave
constexpr float RESET_SHIP_WHACK_DELTA = 440.0f; // Change in speed that a ship is reset to after exceeding MAX_SHIP_WHACK_DELTA
#define REDUCED_DAMP_FACTOR 10 // increase in side_slip and acceleration time constants (scaled according to reduced damp time)
#define REDUCED_DAMP_VEL 30 // change in velocity at which reduced_damp_time is 2000 ms
#define REDUCED_DAMP_TIME 2000 // ms (2.0 sec)
#define WEAPON_SHAKE_TIME 500 // ms (0.5 sec) viewer shake time after hit by weapon (implemented via afterburner shake)
const float SUPERCAP_WARP_T_CONST = 0.651f; // special warp time constant (lose 99 % of excess speed in 3 sec)
const float SUPERCAP_WARP_EXCESS_SPD_THRESHOLD = 5.0f; // more than this many m/s faster than the ship's max speed and we use the above damp constant instead
void update_reduced_damp_timestamp( physics_info *pi, float impulse );
float velocity_ramp (float v_in, float v_goal, float time_const, float t);
float glide_ramp (float v_in, float v_goal, float ramp_time_const, float accel_mult, float t);
void physics_init( physics_info * pi )
{
memset( pi, 0, sizeof(physics_info) );
pi->mass = 10.0f; // This ship weighs 10 units
pi->side_slip_time_const = 0.05f;
pi->rotdamp = 0.1f;
pi->max_vel.xyz.x = 100.0f; //sideways
pi->max_vel.xyz.y = 100.0f; //up/down
pi->max_vel.xyz.z = 100.0f; //forward
pi->max_rear_vel = 100.0f; //backward -- controlled separately
pi->max_rotvel = vm_vec_new(2.0f, 1.0f, 2.0f);
vm_vec_zero(&pi->prev_ramp_vel);
vm_vec_zero(&pi->desired_vel);
pi->slide_accel_time_const=pi->side_slip_time_const; // slide using max_vel.xyz.x & .xyz.y
pi->slide_decel_time_const=pi->side_slip_time_const; // slide using max_vel.xyz.x & .xyz.y
pi->afterburner_decay = 1;
vm_vec_zero(&pi->linear_thrust);
vm_vec_zero(&pi->rotational_thrust);
pi->flags = 0;
// default values for moment of inertia
vm_vec_make( &pi->I_body_inv.vec.rvec, 1e-5f, 0.0f, 0.0f );
vm_vec_make( &pi->I_body_inv.vec.uvec, 0.0f, 1e-5f, 0.0f );
vm_vec_make( &pi->I_body_inv.vec.fvec, 0.0f, 0.0f, 1e-5f );
pi->ai_desired_orient = vmd_zero_matrix; // Asteroth - initialize to the "invalid" orientation, which will be ignored by physics unless set otherwise
vm_vec_zero(&pi->acceleration);
pi->gravity_const = 0.0f;
}
//==========================================================================
// apply_physics - This does correct physics independent of frame rate.
//
// Given:
// damping = damping factor. Setting this to zero make the object instantly
// go to the target velocity. Increasing it makes the object ramp
// up or down to the target velocity.
// desired_vel = the target velocity
// initial_vel = velocity at last call
// t = elapsed time since last call
//
// Returns:
// new_vel = current velocity
// delta_pos = delta position (framevec)
// You can extend this to 3d by calling it 3 times, once for each x,y,z component.
void apply_physics( float damping, float desired_vel, float initial_vel, float t, float * new_vel, float * delta_pos )
{
if ( damping < 0.0001f ) {
if ( delta_pos )
*delta_pos = desired_vel*t;
if ( new_vel )
*new_vel = desired_vel;
} else {
float dv, e;
dv = initial_vel - desired_vel;
e = (float)exp( -t/damping );
if ( delta_pos )
*delta_pos = (1.0f - e)*dv*damping + desired_vel*t;
if ( new_vel )
*new_vel = dv*e + desired_vel;
}
}
float Physics_viewer_bank = 0.0f;
int Physics_viewer_direction = PHYSICS_VIEWER_FRONT;
physics_info * Viewer_physics_info = NULL;
// If you would like Physics_viewer_bank to be tracked (Which is needed
// for rotating 3d bitmaps) call this and pass it a pointer to the
// viewer's physics info.
void physics_set_viewer( physics_info * p, int dir )
{
if ( (Viewer_physics_info != p) || (Physics_viewer_direction != dir) ) {
Viewer_physics_info = p;
Physics_viewer_bank = 0.0f;
Physics_viewer_direction = dir;
}
}
// -----------------------------------------------------------------------------------------------------------
// add rotational velocity & acceleration
void physics_sim_rot(matrix * orient, physics_info * pi, float sim_time )
{
vec3d new_vel;
matrix tmp;
float rotdamp;
float shock_fraction_time_left;
Assert(is_valid_matrix(orient));
Assert(is_valid_vec(&pi->rotvel));
Assert(is_valid_vec(&pi->desired_rotvel));
// Handle special case of shockwave
float shock_amplitude = 0.0f;
if (pi->flags & PF_IN_SHOCKWAVE) {
if (timestamp_elapsed(pi->shockwave_decay)) {
pi->flags &= ~PF_IN_SHOCKWAVE;
rotdamp = pi->rotdamp;
}
else {
shock_fraction_time_left = timestamp_until(pi->shockwave_decay) / (float)SW_BLAST_DURATION;
rotdamp = pi->rotdamp + pi->rotdamp * (SW_ROT_FACTOR - 1) * shock_fraction_time_left;
shock_amplitude = pi->shockwave_shake_amp * shock_fraction_time_left;
}
}
else if (pi->flags & PF_MANEUVER_NO_DAMP) {
rotdamp = 0.0f;
}
else {
rotdamp = pi->rotdamp;
}
angles tangles = vmd_zero_angles;
// "frit" here meaning Framerate_independent_turning
bool frit_ai_wants_to_move = Framerate_independent_turning && !IS_MAT_NULL(&pi->ai_desired_orient);
// In the case an ai entity used angular_move, we should use those calculations instead
if (frit_ai_wants_to_move){
Assert(is_valid_matrix(&pi->ai_desired_orient));
// AI simply get the rotvel they ask for, calculations were already done
pi->rotvel = pi->desired_rotvel;
// Zero it out because the same AI might not call angular_move again next time
// So we'll assume it's going to "go limp" unless it specifies otherwise next frame
vm_vec_zero(&pi->desired_rotvel);
// Pretty much same as above
*orient = pi->ai_desired_orient;
vm_mat_zero(&pi->ai_desired_orient);
} else { // players and no framerate_independent_turning go here
// Do rotational physics with given damping
apply_physics(rotdamp, pi->desired_rotvel.xyz.x, pi->rotvel.xyz.x, sim_time, &new_vel.xyz.x, nullptr);
apply_physics(rotdamp, pi->desired_rotvel.xyz.y, pi->rotvel.xyz.y, sim_time, &new_vel.xyz.y, nullptr);
apply_physics(rotdamp, pi->desired_rotvel.xyz.z, pi->rotvel.xyz.z, sim_time, &new_vel.xyz.z, nullptr);
Assert(is_valid_vec(&new_vel));
pi->rotvel = new_vel;
tangles.p = pi->rotvel.xyz.x * sim_time;
tangles.h = pi->rotvel.xyz.y * sim_time;
tangles.b = pi->rotvel.xyz.z * sim_time;
}
// Make ship shake due to shockwave, decreasing in amplitude at the end of the shockwave
if ( pi->flags & PF_IN_SHOCKWAVE ) {
tangles.p += (float) (Random::next()-Random::HALF_MAX_VALUE) * Random::INV_F_MAX_VALUE * shock_amplitude;
tangles.h += (float) (Random::next()-Random::HALF_MAX_VALUE) * Random::INV_F_MAX_VALUE * shock_amplitude;
}
vm_angles_2_matrix(&pi->last_rotmat, &tangles );
vm_matrix_x_matrix( &tmp, orient, &pi->last_rotmat );
*orient = tmp;
vm_orthogonalize_matrix(orient);
}
// -----------------------------------------------------------------------------------------------------------
// add rotational velocity & acceleration
void physics_sim_rot_editor(matrix * orient, physics_info * pi, float sim_time)
{
angles tangles;
vec3d new_vel;
matrix tmp;
angles t1, t2;
apply_physics( pi->rotdamp, pi->desired_rotvel.xyz.x, pi->rotvel.xyz.x, sim_time,
&new_vel.xyz.x, NULL );
apply_physics( pi->rotdamp, pi->desired_rotvel.xyz.y, pi->rotvel.xyz.y, sim_time,
&new_vel.xyz.y, NULL );
apply_physics( pi->rotdamp, pi->desired_rotvel.xyz.z, pi->rotvel.xyz.z, sim_time,
&new_vel.xyz.z, NULL );
pi->rotvel = new_vel;
tangles.p = pi->rotvel.xyz.x*sim_time;
tangles.h = pi->rotvel.xyz.y*sim_time;
tangles.b = pi->rotvel.xyz.z*sim_time;
t1 = t2 = tangles;
t1.h = 0.0f; t1.b = 0.0f;
t2.p = 0.0f; t2.b = 0.0f;
// put in p & b like normal
vm_angles_2_matrix(&pi->last_rotmat, &t1 );
vm_matrix_x_matrix( &tmp, orient, &pi->last_rotmat );
// Put in heading separately
vm_angles_2_matrix(&pi->last_rotmat, &t2 );
vm_matrix_x_matrix( orient, &pi->last_rotmat, &tmp );
vm_orthogonalize_matrix(orient);
}
// Adds velocity to position
// finds velocity and displacement in local coords
void physics_sim_vel(vec3d * position, physics_info * pi, matrix *orient, vec3d* gravity, float sim_time)
{
vec3d local_disp; // displacement in this frame
vec3d local_v_in; // velocity in local coords at the start of this frame
vec3d local_desired_vel; // desired velocity in local coords
vec3d local_v_out; // velocity in local coords following this frame
vec3d damp;
vec3d old_vel = pi->vel;
// Maybe clear the reduced_damp flag.
// This fixes the problem of the player getting near-instantaneous acceleration under unknown circumstances.
// The larger problem is probably that PF_USE_VEL is getting stuck set.
if ((pi->flags & PF_REDUCED_DAMP) && (timestamp_elapsed(pi->reduced_damp_decay))) {
pi->flags &= ~PF_REDUCED_DAMP;
}
// Set up damping constants based on special conditions
// dead
if (pi->flags & PF_DEAD_DAMP) {
// side_slip_time_const is already quite large and now needs to be applied in all directions
vm_vec_make( &damp, pi->side_slip_time_const, pi->side_slip_time_const, pi->side_slip_time_const );
}
// case of shock, weapon, collide, etc.
else if (pi->flags & PF_REDUCED_DAMP) {
if ( timestamp_elapsed(pi->reduced_damp_decay) ) {
vm_vec_make( &damp, pi->side_slip_time_const, pi->side_slip_time_const, 0.0f );
} else {
// damp is multiplied by fraction and not fraction^2, gives better collision separation
float reduced_damp_fraction_time_left = timestamp_until( pi->reduced_damp_decay ) / (float) REDUCED_DAMP_TIME;
damp.xyz.x = pi->side_slip_time_const * ( 1 + (REDUCED_DAMP_FACTOR-1) * reduced_damp_fraction_time_left );
damp.xyz.y = pi->side_slip_time_const * ( 1 + (REDUCED_DAMP_FACTOR-1) * reduced_damp_fraction_time_left );
damp.xyz.z = pi->side_slip_time_const * reduced_damp_fraction_time_left * REDUCED_DAMP_FACTOR;
}
}
// no damping at all
else if (pi->flags & PF_MANEUVER_NO_DAMP) {
damp = vmd_zero_vector;
}
// newtonian
else if (pi->flags & PF_NEWTONIAN_DAMP) {
vm_vec_make( &damp, pi->side_slip_time_const, pi->side_slip_time_const, pi->side_slip_time_const );
}
// regular damping
else {
vm_vec_make( &damp, pi->side_slip_time_const, pi->side_slip_time_const, 0.0f );
}
// Note: CANNOT maintain a *local velocity* since a rotation can occur in this frame.
// thus the local velocity of in the next frame can be different (this would require rotate to change local vel
// and this is not desired
// get local components of current velocity
vm_vec_rotate (&local_v_in, &pi->vel, orient);
// get local components of desired velocity
vm_vec_rotate (&local_desired_vel, &pi->desired_vel, orient);
// find updated LOCAL velocity and position in the local x direction
apply_physics (damp.xyz.x, local_desired_vel.xyz.x, local_v_in.xyz.x, sim_time, &local_v_out.xyz.x, &local_disp.xyz.x);
// find updated LOCAL velocity and position in the local y direction
apply_physics (damp.xyz.y, local_desired_vel.xyz.y, local_v_in.xyz.y, sim_time, &local_v_out.xyz.y, &local_disp.xyz.y);
// find updated LOCAL velocity and position in the local z direction
// for player ship, damp should normally be zero, but may be altered in a shockwave
// in death, shockwave,etc. we want damping time const large for all 3 axes
// warp in test - make excessive speed drop exponentially from max allowed
// become (0.01x in 3 sec)
vec3d grav_disp = vmd_zero_vector;
vec3d grav_vel = vmd_zero_vector;
int special_warp_in = FALSE;
float excess = local_v_in.xyz.z - pi->max_vel.xyz.z;
if (excess > SUPERCAP_WARP_EXCESS_SPD_THRESHOLD && (pi->flags & PF_SUPERCAP_WARP_IN)) {
special_warp_in = TRUE;
float exp_factor = float(exp(-sim_time / SUPERCAP_WARP_T_CONST));
local_v_out.xyz.z = pi->max_vel.xyz.z + excess * exp_factor;
local_disp.xyz.z = (pi->max_vel.xyz.z * sim_time) + excess * (float(SUPERCAP_WARP_T_CONST) * (1.0f - exp_factor));
} else if (pi->flags & PF_SUPERCAP_WARP_OUT) {
float exp_factor = float(exp(-sim_time / SUPERCAP_WARP_T_CONST));
vec3d temp;
vm_vec_rotate(&temp, &pi->prev_ramp_vel, orient);
float deficeit = temp.xyz.z - local_v_in.xyz.z;
local_v_out.xyz.z = local_v_in.xyz.z + deficeit * (1.0f - exp_factor);
local_disp.xyz.z = (local_v_in.xyz.z * sim_time) + deficeit * (sim_time - (float(SUPERCAP_WARP_T_CONST) * (1.0f - exp_factor)));
} else {
apply_physics (damp.xyz.z, local_desired_vel.xyz.z, local_v_in.xyz.z, sim_time, &local_v_out.xyz.z, &local_disp.xyz.z);
if (pi->gravity_const != 0.0f) {
grav_vel = *gravity * sim_time * pi->gravity_const;
grav_disp = (grav_vel * sim_time) * 0.5; // 1/2 * at^2
}
}
// maybe turn off special warp in flag
if ((pi->flags & PF_SUPERCAP_WARP_IN) && (excess < SUPERCAP_WARP_EXCESS_SPD_THRESHOLD)) {
pi->flags &= ~(PF_SUPERCAP_WARP_IN);
}
if (!(pi->flags & PF_SCRIPTED_VELOCITY)) {
// update world position from local to world coords using orient
vec3d world_disp;
vm_vec_unrotate(&world_disp, &local_disp, orient);
*position += world_disp;
*position += grav_disp;
// update world velocity
vm_vec_unrotate(&pi->vel, &local_v_out, orient);
pi->vel += grav_vel;
} else {
// velocity set by script, just trust whatever that is, at least for this frame
*position += pi->vel * sim_time;
pi->flags &= ~PF_SCRIPTED_VELOCITY;
}
// update acceleration
pi->acceleration = pi->vel - old_vel;
pi->acceleration *= 1 / sim_time;
if (special_warp_in) {
vm_vec_rotate(&pi->prev_ramp_vel, &pi->vel, orient);
}
}
// -----------------------------------------------------------------------------------------------------------
// Simulate a physics object for this frame
void physics_sim(vec3d* position, matrix* orient, physics_info* pi, vec3d* gravity, float sim_time)
{
// check flag which tells us whether or not to do velocity translation
if (pi->flags & PF_CONST_VEL) {
*position += pi->vel * sim_time;
}
else
{
if (pi->flags & PF_BALLISTIC) {
*position += pi->vel * sim_time + *gravity * sim_time * sim_time * pi->gravity_const * 0.5f; // vt + 1/2 * at^2
pi->vel += *gravity * sim_time * pi->gravity_const;
} else {
physics_sim_vel(position, pi, orient, gravity, sim_time);
}
physics_sim_rot(orient, pi, sim_time);
pi->speed = vm_vec_mag(&pi->vel); // Note, cannot use quick version, causes cumulative error, increasing speed.
pi->fspeed = vm_vec_dot(&orient->vec.fvec, &pi->vel); // instead of vector magnitude -- use only forward vector since we are only interested in forward velocity
}
}
// -----------------------------------------------------------------------------------------------------------
// Simulate a physics object for this frame. Used by the editor. The difference between
// this function and physics_sim() is that this one uses a heading change to rotate around
// the universal Y axis, rather than the local orientation's Y axis. Banking is also ignored.
void physics_sim_editor(vec3d *position, matrix * orient, physics_info * pi, float sim_time )
{
physics_sim_vel(position, pi,orient, &vmd_zero_vector, sim_time);
physics_sim_rot_editor(orient, pi, sim_time);
pi->speed = vm_vec_mag_quick(&pi->vel);
pi->fspeed = vm_vec_dot(&orient->vec.fvec, &pi->vel); // instead of vector magnitude -- use only forward vector since we are only interested in forward velocity
}
// physics_read_flying_controls()
//
// parmeters: *orient ==>
// *pi ==>
// *ci ==>
// Adam: Uncomment-out this define to enable banking while turning.
#define BANK_WHEN_TURN
// function looks at the flying controls and the current velocity to determine a goal velocity
// function determines velocity in object's reference frame and goal velocity in object's reference frame
void physics_read_flying_controls( matrix * orient, physics_info * pi, control_info * ci, float sim_time, vec3d *wash_rot)
{
vec3d goal_vel; // goal velocity in local coords, *not* accounting for ramping of velcity
float ramp_time_const; // time constant for velocity ramping
// apply throttle, unless reverse thrusters are held down
if (ci->forward != -1.0f)
ci->forward += (ci->forward_cruise_percent / 100.0f);
// give control input to cause rotation in engine wash
extern int Wash_on;
if ( wash_rot && Wash_on ) {
ci->pitch += wash_rot->xyz.x;
ci->bank += wash_rot->xyz.z;
ci->heading += wash_rot->xyz.y;
}
if ( pi->flags & PF_AFTERBURNER_ON ){
//SparK: modifield to accept reverse burners
if (!(pi->afterburner_max_reverse_vel > 0.0f)){
ci->forward = 1.0f;
}
}
// maybe we don't need to clamp our velocity...
if ( !(ci->control_flags & CIF_DONT_CLAMP_MAX_VELOCITY) )
{
if (ci->pitch > 1.0f ) ci->pitch = 1.0f;
else if (ci->pitch < -1.0f ) ci->pitch = -1.0f;
if (ci->vertical > 1.0f ) ci->vertical = 1.0f;
else if (ci->vertical < -1.0f ) ci->vertical = -1.0f;
if (ci->heading > 1.0f ) ci->heading = 1.0f;
else if (ci->heading < -1.0f ) ci->heading = -1.0f;
if (ci->sideways > 1.0f ) ci->sideways = 1.0f;
else if (ci->sideways < -1.0f ) ci->sideways = -1.0f;
if (ci->bank > 1.0f ) ci->bank = 1.0f;
else if (ci->bank < -1.0f ) ci->bank = -1.0f;
if (ci->forward > 1.0f ) ci->forward = 1.0f;
else if (ci->forward < -1.0f ) ci->forward = -1.0f;
}
// If Framerate_independent_turning is on, AI don't use CI to turn
if (!Framerate_independent_turning || IS_MAT_NULL(&pi->ai_desired_orient)){
if (!Flight_controls_follow_eyepoint_orientation || (Player_obj == nullptr) || (Player_obj->type != OBJ_SHIP)) {
// Default behavior; eyepoint orientation has no effect on controls
pi->desired_rotvel.xyz.x = ci->pitch * pi->max_rotvel.xyz.x;
pi->desired_rotvel.xyz.y = ci->heading * pi->max_rotvel.xyz.y;
} else {
// Optional behavior; pitch and yaw are always relative to the eyepoint
// orientation (excluding slew)
vec3d tmp_vec, new_rotvel;
matrix tmp_mat, eyemat, rotvelmat;
ship_get_eye(&tmp_vec, &eyemat, Player_obj, false);
vm_copy_transpose(&tmp_mat, &Player_obj->orient);
vm_matrix_x_matrix(&rotvelmat, &tmp_mat, &eyemat);
vm_vec_rotate(&new_rotvel, &pi->max_rotvel, &rotvelmat);
vm_vec_unrotate(&tmp_vec, &pi->max_rotvel, &rotvelmat);
new_rotvel.xyz.x = tmp_vec.xyz.x;
new_rotvel.xyz.x = ci->pitch * new_rotvel.xyz.x;
new_rotvel.xyz.y = ci->heading * new_rotvel.xyz.y;
vm_vec_unrotate(&tmp_vec, &new_rotvel, &rotvelmat);
pi->desired_rotvel = tmp_vec;
}
float delta_bank;
#ifdef BANK_WHEN_TURN
if ( ci->control_flags & CIF_DONT_BANK_WHEN_TURNING )
delta_bank = 0.0f;
else
{
// To change direction of bank, negate the whole expression.
// To increase magnitude of banking, decrease denominator.
// Adam: The following statement is all the math for banking while turning.
delta_bank = - (ci->heading * pi->max_rotvel.xyz.y) * pi->delta_bank_const;
}
#else
delta_bank = 0.0f;
#endif
pi->desired_rotvel.xyz.z = ci->bank * pi->max_rotvel.xyz.z + delta_bank;
}
// update 'thrust values' (these are cosmetic and do not affect physical behavior)
if (Thruster_easing > 0.0f) {
pi->linear_thrust.xyz.z = ci->forward + (pi->linear_thrust.xyz.z - ci->forward) * exp2f(-Thruster_easing * sim_time);
pi->linear_thrust.xyz.y = ci->vertical + (pi->linear_thrust.xyz.y - ci->vertical) * exp2f(-Thruster_easing * sim_time);
pi->linear_thrust.xyz.x = ci->sideways + (pi->linear_thrust.xyz.x - ci->sideways) * exp2f(-Thruster_easing * sim_time);
pi->rotational_thrust.xyz.z = ci->bank + (pi->rotational_thrust.xyz.z - ci->bank) * exp2f(-Thruster_easing * sim_time);
pi->rotational_thrust.xyz.y = ci->heading + (pi->rotational_thrust.xyz.y - ci->heading) * exp2f(-Thruster_easing * sim_time);
pi->rotational_thrust.xyz.x = ci->pitch + (pi->rotational_thrust.xyz.x - ci->pitch) * exp2f(-Thruster_easing * sim_time);
} else {
pi->linear_thrust.xyz.z = ci->forward;
pi->linear_thrust.xyz.y = ci->vertical;
pi->linear_thrust.xyz.x = ci->sideways;
pi->rotational_thrust.xyz.z = ci->bank;
pi->rotational_thrust.xyz.y = ci->heading;
pi->rotational_thrust.xyz.x = ci->pitch;
}
if ( pi->flags & PF_AFTERBURNER_ON ) {
goal_vel.xyz.x = ci->sideways*pi->afterburner_max_vel.xyz.x;
goal_vel.xyz.y = ci->vertical*pi->afterburner_max_vel.xyz.y;
if(ci->forward < 0.0f)
goal_vel.xyz.z = ci->forward* pi->afterburner_max_reverse_vel;
else
goal_vel.xyz.z = ci->forward* pi->afterburner_max_vel.xyz.z;
}
else if ( pi->flags & PF_BOOSTER_ON ) {
goal_vel.xyz.x = ci->sideways*pi->booster_max_vel.xyz.x;
goal_vel.xyz.y = ci->vertical*pi->booster_max_vel.xyz.y;
goal_vel.xyz.z = ci->forward* pi->booster_max_vel.xyz.z;
}
else {
goal_vel.xyz.x = ci->sideways*pi->max_vel.xyz.x;
goal_vel.xyz.y = ci->vertical*pi->max_vel.xyz.y;
goal_vel.xyz.z = ci->forward* pi->max_vel.xyz.z;
}
if ( goal_vel.xyz.z < -pi->max_rear_vel && !(pi->flags & PF_AFTERBURNER_ON) && !(ci->control_flags & CIF_DONT_CLAMP_MAX_VELOCITY) )
goal_vel.xyz.z = -pi->max_rear_vel;
// instantaneous acceleration is trickier than it looks
if ( ci->control_flags & CIF_INSTANTANEOUS_ACCELERATION ) {
// ramp up instantaneously
pi->prev_ramp_vel = goal_vel;
// translate local desired velocities to world velocities, as below
vm_vec_zero(&pi->desired_vel);
vm_vec_scale_add2(&pi->desired_vel, &orient->vec.rvec, pi->prev_ramp_vel.xyz.x);
vm_vec_scale_add2(&pi->desired_vel, &orient->vec.uvec, pi->prev_ramp_vel.xyz.y);
vm_vec_scale_add2(&pi->desired_vel, &orient->vec.fvec, pi->prev_ramp_vel.xyz.z);
}
// standard acceleration
else if ( pi->flags & PF_ACCELERATES ) {
//
// Determine *resultant* DESIRED VELOCITY (desired_vel) accounting for RAMPING of velocity
// Use LOCAL coordinates
// if slide_enabled, ramp velocity for x and y, otherwise set goal (0)
// always ramp velocity for z
//
// If reduced damp in effect, then adjust ramp_velocity and desired_velocity can not change as fast.
// Scale according to reduced_damp_time_expansion.
float reduced_damp_ramp_time_expansion;
if ( pi->flags & PF_REDUCED_DAMP && !timestamp_elapsed(pi->reduced_damp_decay) ) {
float reduced_damp_fraction_time_left = timestamp_until( pi->reduced_damp_decay ) / (float) REDUCED_DAMP_TIME;
reduced_damp_ramp_time_expansion = 1.0f + (REDUCED_DAMP_FACTOR-1) * reduced_damp_fraction_time_left;
} else {
reduced_damp_ramp_time_expansion = 1.0f;
}
if (pi->flags & PF_SLIDE_ENABLED) {
// determine the local velocity
// deterimine whether accelerating or deceleration toward goal for x
if ( goal_vel.xyz.x > 0.0f ) {
if ( goal_vel.xyz.x >= pi->prev_ramp_vel.xyz.x )
ramp_time_const = pi->slide_accel_time_const;
else
ramp_time_const = pi->slide_decel_time_const;
} else if ( goal_vel.xyz.x < 0.0f ) {
if ( goal_vel.xyz.x <= pi->prev_ramp_vel.xyz.x )
ramp_time_const = pi->slide_accel_time_const;
else
ramp_time_const = pi->slide_decel_time_const;
} else {
ramp_time_const = pi->slide_decel_time_const;
}
// If reduced damp in effect, then adjust ramp_velocity and desired_velocity can not change as fast
if ( pi->flags & PF_REDUCED_DAMP ) {
ramp_time_const *= reduced_damp_ramp_time_expansion;
}
pi->prev_ramp_vel.xyz.x = velocity_ramp(pi->prev_ramp_vel.xyz.x, goal_vel.xyz.x, ramp_time_const, sim_time);
// deterimine whether accelerating or deceleration toward goal for y
if ( goal_vel.xyz.y > 0.0f ) {
if ( goal_vel.xyz.y >= pi->prev_ramp_vel.xyz.y )
ramp_time_const = pi->slide_accel_time_const;
else
ramp_time_const = pi->slide_decel_time_const;
} else if ( goal_vel.xyz.y < 0.0f ) {
if ( goal_vel.xyz.y <= pi->prev_ramp_vel.xyz.y )
ramp_time_const = pi->slide_accel_time_const;
else
ramp_time_const = pi->slide_decel_time_const;
} else {
ramp_time_const = pi->slide_decel_time_const;
}
// If reduced damp in effect, then adjust ramp_velocity and desired_velocity can not change as fast
if ( pi->flags & PF_REDUCED_DAMP ) {
ramp_time_const *= reduced_damp_ramp_time_expansion;
}
pi->prev_ramp_vel.xyz.y = velocity_ramp( pi->prev_ramp_vel.xyz.y, goal_vel.xyz.y, ramp_time_const, sim_time);
} else {
// slide not enabled
pi->prev_ramp_vel.xyz.x = 0.0f;
pi->prev_ramp_vel.xyz.y = 0.0f;
}
// deterimine whether accelerating or deceleration toward goal for z
if ( goal_vel.xyz.z > 0.0f ) {
if ( goal_vel.xyz.z >= pi->prev_ramp_vel.xyz.z ) {
if ( pi->flags & PF_AFTERBURNER_ON )
ramp_time_const = pi->afterburner_forward_accel_time_const;
else if (pi->flags & PF_BOOSTER_ON)
ramp_time_const = pi->booster_forward_accel_time_const;
else
ramp_time_const = pi->forward_accel_time_const;
} else {
ramp_time_const = pi->forward_decel_time_const;
}
} else if ( goal_vel.xyz.z < 0.0f ) {
if ( pi->flags & PF_AFTERBURNER_ON )
ramp_time_const = pi->afterburner_reverse_accel;
else
ramp_time_const = pi->forward_decel_time_const;
} else {
ramp_time_const = pi->forward_decel_time_const;
}
// If reduced damp in effect, then adjust ramp_velocity and desired_velocity can not change as fast
if ( pi->flags & PF_REDUCED_DAMP ) {
ramp_time_const *= reduced_damp_ramp_time_expansion;
}
pi->prev_ramp_vel.xyz.z = velocity_ramp(pi->prev_ramp_vel.xyz.z, goal_vel.xyz.z, ramp_time_const, sim_time);
//Deternine the current dynamic glide cap, and ramp to it
//This is outside the normal "glide" block since we want the cap to adjust whether or not the ship is in glide mode
float dynamic_glide_cap_goal = 0.0;
if (pi->flags & PF_AFTERBURNER_ON) {
dynamic_glide_cap_goal = ( goal_vel.xyz.z >= 0.0f ) ? pi->afterburner_max_vel.xyz.z : pi->afterburner_max_reverse_vel;
}
else {
//Use the maximum value in X, Y, and Z (including overclocking)
dynamic_glide_cap_goal = std::max({ pi->max_vel.xyz.x,pi->max_vel.xyz.y, pi->max_vel.xyz.z });
}
pi->cur_glide_cap = velocity_ramp(pi->cur_glide_cap, dynamic_glide_cap_goal, ramp_time_const, sim_time);
if ( (pi->flags & PF_GLIDING) || (pi->flags & PF_FORCE_GLIDE ) ) {
pi->desired_vel = pi->vel;
//SUSHI: A (hopefully better) approach to dealing with accelerations in glide mode
//Get *actual* current velocities along each axis and use those instead of ramped velocities
vec3d local_vel;
vm_vec_rotate(&local_vel, &pi->vel, orient);
//Having pi->glide_cap == 0 means we're using a dynamic glide cap
float curGlideCap = 0.0f;
if (pi->glide_cap == 0.0f)
curGlideCap = pi->cur_glide_cap;
else
curGlideCap = pi->glide_cap;
//If we're near the (positive) glide cap, decay velocity where we aren't thrusting
//This is a hack, but makes the flight feel a lot smoother
//Don't do this if we aren't applying any thrust, we have no glide cap, or the accel multiplier is 0 (no thrust while gliding)
float cap_decay_threshold = 0.95f;
float cap_decay_amount = 0.2f;
if (curGlideCap >= 0.0f && vm_vec_mag(&pi->desired_vel) >= cap_decay_threshold * curGlideCap &&
vm_vec_mag(&goal_vel) > 0.0f &&
pi->glide_accel_mult != 0.0f)
{
if (goal_vel.xyz.x == 0.0f)
vm_vec_scale_add2(&pi->desired_vel, &orient->vec.rvec, -cap_decay_amount * local_vel.xyz.x);
if (goal_vel.xyz.y == 0.0f)
vm_vec_scale_add2(&pi->desired_vel, &orient->vec.uvec, -cap_decay_amount * local_vel.xyz.y);
if (goal_vel.xyz.z == 0.0f)
vm_vec_scale_add2(&pi->desired_vel, &orient->vec.fvec, -cap_decay_amount * local_vel.xyz.z);
}
//The glide_ramp function uses (basically) the same math as the velocity ramp so that thruster power is consistent
//Only ramp if the glide cap is positive
float xVal = glide_ramp(local_vel.xyz.x, goal_vel.xyz.x, pi->slide_accel_time_const, pi->glide_accel_mult, sim_time);
float yVal = glide_ramp(local_vel.xyz.y, goal_vel.xyz.y, pi->slide_accel_time_const, pi->glide_accel_mult, sim_time);
float zVal = 0.0;
if (pi->flags & PF_AFTERBURNER_ON)
zVal = glide_ramp(local_vel.xyz.z, goal_vel.xyz.z, pi->afterburner_forward_accel_time_const, pi->glide_accel_mult, sim_time);
else {
if (goal_vel.xyz.z >= 0.0f)
zVal = glide_ramp(local_vel.xyz.z, goal_vel.xyz.z, pi->forward_accel_time_const, pi->glide_accel_mult, sim_time);
else
zVal = glide_ramp(local_vel.xyz.z, goal_vel.xyz.z, pi->forward_decel_time_const, pi->glide_accel_mult, sim_time);
}
//Compensate for effect of dampening: normal flight cheats here, so /we make up for it this way so glide acts the same way
xVal *= pi->side_slip_time_const / sim_time;
yVal *= pi->side_slip_time_const / sim_time;
if (pi->flags & PF_NEWTONIAN_DAMP)
zVal *= pi->side_slip_time_const / sim_time;
vm_vec_scale_add2(&pi->desired_vel, &orient->vec.fvec, zVal);
vm_vec_scale_add2(&pi->desired_vel, &orient->vec.rvec, xVal);
vm_vec_scale_add2(&pi->desired_vel, &orient->vec.uvec, yVal);
// Only do the glide cap if we have one and are actively thrusting in some direction.
// Unless AIPF2_GLIDE_DECAY_REQUIRES_THRUST isn't set. -MageKing17
if ( curGlideCap >= 0.0f && (!(The_mission.ai_profile->flags[AI::Profile_Flags::Glide_decay_requires_thrust]) || ci->forward != 0.0f || ci->sideways != 0.0f || ci->vertical != 0.0f) ) {
float currentmag = vm_vec_mag(&pi->desired_vel);
if ( currentmag > curGlideCap ) {
vm_vec_scale( &pi->desired_vel, curGlideCap / currentmag );
}
}
}
else
{
// this translates local desired velocities to world velocities
vm_vec_zero(&pi->desired_vel);
vm_vec_scale_add2( &pi->desired_vel, &orient->vec.rvec, pi->prev_ramp_vel.xyz.x );
vm_vec_scale_add2( &pi->desired_vel, &orient->vec.uvec, pi->prev_ramp_vel.xyz.y );
vm_vec_scale_add2( &pi->desired_vel, &orient->vec.fvec, pi->prev_ramp_vel.xyz.z );
}
}
// object does not accelerate (PF_ACCELERATES not set)
else {
pi->desired_vel = pi->vel;
}
}
void physics_maybe_reset_speed_after_whack(physics_info *pi, const vec3d *previous_vel, const char *source)
{
// for release builds
SCP_UNUSED(source);
if (pi->flags & PF_USE_VEL || The_mission.ai_profile->flags[AI::Profile_Flags::Dont_limit_change_in_speed_due_to_physics_whack])
return;
vec3d delta_vel = pi->vel - *previous_vel;
float speed_delta = vm_vec_mag(&delta_vel);
// Limit change in speed due to whack
if (speed_delta > MAX_SHIP_WHACK_DELTA)
{
// Get DaveA
nprintf(("Physics", "speed reset in %s [change in speed: %f; current speed: %f]\n", source, speed_delta, vm_vec_mag(&pi->vel)));
vm_vec_normalize(&delta_vel);
vm_vec_scale_add(&pi->vel, previous_vel, &delta_vel, RESET_SHIP_WHACK_DELTA);
}
}
#define WHACK_LIMIT 0.001f
#define ROTVEL_WHACK_CONST 0.12f
// -----------------------------------------------------------------------------------------------------------
// Returns true if this impulse is below the limit and should be ignored.
bool whack_below_limit(const vec3d* impulse)
{
return (fl_abs(impulse->xyz.x) < WHACK_LIMIT) && (fl_abs(impulse->xyz.y) < WHACK_LIMIT) &&
(fl_abs(impulse->xyz.z) < WHACK_LIMIT);
}
bool whack_below_limit(float impulse)
{
return fl_abs(impulse) < WHACK_LIMIT;
}
// ----------------------------------------------------------------------------
// physics_calculate_and_apply_whack changes the rotaional and linear velocites of a ship due to
// an instantaneous whack.
//
// input: impulse => impulse vector (direction and magnitude of the impulse)
// pos => vector from center of mass to location (in world coords) of where the force acts
// pi => pointer to phys_info struct of object getting whacked
// orient => orientation matrix (needed to set rotational impulse in body coords)
//
void physics_calculate_and_apply_whack(vec3d *impulse, vec3d *pos, physics_info *pi, matrix *orient, matrix *inv_moi)
{
vec3d local_angular_impulse, angular_impulse;
// Detect null vector.
if (whack_below_limit(impulse))
return;
// first do the rotational velocity
// calculate the angular impulse on the body based on the point on the
// object that was hit and the momentum being applied to the object
vm_vec_cross(&angular_impulse, pos, impulse);
vm_vec_rotate ( &local_angular_impulse, &angular_impulse, orient );
vec3d delta_rotvel;
vm_vec_rotate(&delta_rotvel, &local_angular_impulse, inv_moi);
vec3d delta_vel = *impulse * (1.0f / pi->mass);
physics_apply_whack(vm_vec_mag(impulse), pi, &delta_rotvel, &delta_vel, orient);
}
// This function applies the calculated delta rotational and linear velocities calculated by physics_calculate_and_apply_whack
// or dock_calculate_and_apply_whack_docked_object in objectdock.cpp if it was a docked object
void physics_apply_whack(float orig_impulse, physics_info* pi, vec3d *delta_rotvel, vec3d* delta_vel, matrix* orient)
{
Assertion((pi != nullptr) && (delta_rotvel != nullptr) && (delta_vel != nullptr) && (orient != nullptr),
"physics_apply_whack_direct invalid argument(s)");
vm_vec_scale(delta_rotvel, (float)ROTVEL_WHACK_CONST);
vm_vec_add2(&pi->rotvel, delta_rotvel);
//mprintf(("Whack: %7.3f %7.3f %7.3f\n", pi->rotvel.xyz.x, pi->rotvel.xyz.y, pi->rotvel.xyz.z));
// instant whack on the velocity
// reduce damping on all axes
pi->flags |= PF_REDUCED_DAMP;
update_reduced_damp_timestamp( pi, orig_impulse );
// find time for shake from weapon to end
int dtime = timestamp_until(pi->afterburner_decay);
if (dtime < WEAPON_SHAKE_TIME) {
pi->afterburner_decay = timestamp( WEAPON_SHAKE_TIME );
}
vec3d previous_vel = pi->vel;
vm_vec_add2(&pi->vel, delta_vel);
physics_maybe_reset_speed_after_whack(pi, &previous_vel, "physics_apply_whack");
// set so velocity will ramp starting from current speed
// ramped velocity is now affected by collision
vm_vec_rotate( &pi->prev_ramp_vel, &pi->vel, orient );
}
// function generates a velocity ramp with a given time constant independent of frame rate
// uses an exponential approach to desired velocity and a cheat when close to improve closure speed
float velocity_ramp (float v_in, float v_goal, float ramp_time_const, float t)
{
float delta_v;
float decay_factor;
float dist;
// JAS: If no time elapsed, change nothing
if ( t==0.0f )
return v_in;
delta_v = v_goal - v_in;
dist = (float)fl_abs(delta_v);
// hack to speed up closure when close to goal
if (dist < ramp_time_const/3)
ramp_time_const = dist / 3;
// Rather than get a divide by zero, just go to the goal
if ( ramp_time_const < 0.0001f ) {
return v_goal;
}
// determine decay factor (ranges from 0 for short times to 1 for long times)
// when decay factor is near 0, the velocity in nearly unchanged
// when decay factor in near 1, the velocity approaches goal
decay_factor = (float)exp(- t / ramp_time_const);
return (v_in + delta_v * (1.0f - decay_factor) );
}
//Handles thrust values when gliding. Purposefully similar to velocity_ramp in order to yeild a similar "feel" in
//terms of thruster accels (as much as possible)
//This is all in the local frame of reference for a single movement axis
float glide_ramp (float v_in, float v_goal, float ramp_time_const, float accel_mult, float t)
{
if (v_goal == 0.0f) {
return 0.0f;
}
//This approach to delta_v allows us to ramp accelerations even in glide mode
//Get the difference in velocity between current and goal as thrust
//(capped by the goal velocity on one end and 0 on the other)
//If accel_mult is < 0, don't ramp (fixed acceleration)
float delta_v = 0.0f;
if (accel_mult < 0.0f) {
if (v_goal > 0.0f) {
delta_v = MAX(MIN(v_goal - v_in, v_goal), 0.0f);
}
else {
delta_v = MIN(MAX(v_goal - v_in, v_goal), 0.0f);
}
}
else {
delta_v = v_goal * accel_mult;
}
//Calculate the (decayed) thrust
float decay_factor = (ramp_time_const > 0.0f) ? (1.0f - (float)exp(-t / ramp_time_const)) : 1.0f;
return delta_v * decay_factor;
}
// ----------------------------------------------------------------------------
// physics_apply_shock applies applies a shockwave to an object. This causes a velocity impulse and
// and a rotational impulse. This is different than physics_apply_whack since a shock wave is a pressure
// wave which acts over the *surface* of the object, not a point.
//
// inputs: direction_vec => a position vector whose direction is from the center of the shock wave to the object
// pressure => the pressure of the shock wave at the object
// pi => physics_info structure
// orient => matrix orientation of the object
// min => vector of minimum values of the bounding box
// max => vector of maximum values of the bounding box
// radius => bounding box radius of the object, used for scaling rotation
//
// outputs: makes changes to physics_info structure rotvel and vel variables
//
#define STD_PRESSURE 1000 // amplitude of standard shockwave blasts
#define MIN_RADIUS 10 // radius within which full rotvel and shake applied
#define MAX_RADIUS 50 // radius at which no rotvel or shake applied
#define MAX_ROTVEL 0.4 // max rotational velocity
#define MAX_SHAKE 0.1 // max rotational amplitude of shake
#define MAX_VEL 8 // max vel from shockwave
void physics_apply_shock(vec3d *direction_vec, float pressure, physics_info *pi, matrix *orient, vec3d *min, vec3d *max, float radius)
{
vec3d normal;
vec3d local_torque, temp_torque, torque;
vec3d impact_vec;
vec3d area;
vec3d sin;
if (radius > MAX_RADIUS) {
return;
}
vm_vec_normalize_safe ( direction_vec );
area.xyz.x = (max->xyz.y - min->xyz.y) * (max->xyz.z - min->xyz.z);
area.xyz.y = (max->xyz.x - min->xyz.x) * (max->xyz.z - min->xyz.z);
area.xyz.z = (max->xyz.x - min->xyz.x) * (max->xyz.y - min->xyz.y);
normal.xyz.x = vm_vec_dot( direction_vec, &orient->vec.rvec );
normal.xyz.y = vm_vec_dot( direction_vec, &orient->vec.uvec );
normal.xyz.z = vm_vec_dot( direction_vec, &orient->vec.fvec );
sin.xyz.x = fl_sqrt( fl_abs(1.0f - normal.xyz.x*normal.xyz.x) );
sin.xyz.y = fl_sqrt( fl_abs(1.0f - normal.xyz.y*normal.xyz.y) );
sin.xyz.z = fl_sqrt( fl_abs(1.0f - normal.xyz.z*normal.xyz.z) );
vm_vec_make( &torque, 0.0f, 0.0f, 0.0f );
// find the torque exerted due to the shockwave hitting each face
// model the effect of the shockwave as if the shockwave were a plane of projectiles,
// all moving in the direction direction_vec. then find the torque as the cross prod
// of the force (pressure * area * normal * sin * scale * mass)
// normal takes account the fraction of the surface exposed to the shockwave
// the sin term is not technically needed but "feels" better
// scale factors out the increase in area with larger objects
// more massive objects get less rotation
// find torque due to forces on the right/left face
if ( normal.xyz.x < 0.0f ) // normal < 0, hits the right face
vm_vec_copy_scale( &impact_vec, &orient->vec.rvec, max->xyz.x * pressure * area.xyz.x * normal.xyz.x * sin.xyz.x / pi->mass );
else // normal > 0, hits the left face
vm_vec_copy_scale( &impact_vec, &orient->vec.rvec, min->xyz.x * pressure * area.xyz.x * -normal.xyz.x * sin.xyz.x / pi->mass );
vm_vec_cross( &temp_torque, &impact_vec, direction_vec );
vm_vec_add2( &torque, &temp_torque );
// find torque due to forces on the up/down face
if ( normal.xyz.y < 0.0f )
vm_vec_copy_scale( &impact_vec, &orient->vec.uvec, max->xyz.y * pressure * area.xyz.y * normal.xyz.y * sin.xyz.y / pi->mass );
else
vm_vec_copy_scale( &impact_vec, &orient->vec.uvec, min->xyz.y * pressure * area.xyz.y * -normal.xyz.y * sin.xyz.y / pi->mass );
vm_vec_cross( &temp_torque, &impact_vec, direction_vec );
vm_vec_add2( &torque, &temp_torque );
// find torque due to forces on the forward/backward face
if ( normal.xyz.z < 0.0f )
vm_vec_copy_scale( &impact_vec, &orient->vec.fvec, max->xyz.z * pressure * area.xyz.z * normal.xyz.z * sin.xyz.z / pi->mass );
else
vm_vec_copy_scale( &impact_vec, &orient->vec.fvec, min->xyz.z * pressure * area.xyz.z * -normal.xyz.z * sin.xyz.z / pi->mass );
vm_vec_cross( &temp_torque, &impact_vec, direction_vec );
vm_vec_add2( &torque, &temp_torque );
// compute delta rotvel, scale according to blast and radius
float scale;
if (radius < MIN_RADIUS) {
scale = 1.0f;
} else {
scale = (MAX_RADIUS - radius)/(MAX_RADIUS-MIN_RADIUS);
}
// set shockwave shake amplitude, duration, flag
pi->shockwave_shake_amp = (float)(MAX_SHAKE*(pressure/STD_PRESSURE)*scale);
pi->shockwave_decay = timestamp( SW_BLAST_DURATION );
pi->flags |= PF_IN_SHOCKWAVE;
// safety dance
if (!(IS_VEC_NULL_SQ_SAFE(&torque))) {
vec3d delta_rotvel;
vm_vec_rotate( &local_torque, &torque, orient );
vm_vec_copy_normalize(&delta_rotvel, &local_torque);
vm_vec_scale(&delta_rotvel, (float)(MAX_ROTVEL*(pressure/STD_PRESSURE)*scale));
// nprintf(("Physics", "rotvel scale %f\n", (MAX_ROTVEL*(pressure/STD_PRESSURE)*scale)));
vm_vec_add2(&pi->rotvel, &delta_rotvel);
}
// set reduced translational damping, set flags
float velocity_scale = (float)MAX_VEL*scale;
pi->flags |= PF_REDUCED_DAMP;
update_reduced_damp_timestamp( pi, velocity_scale*pi->mass );
vec3d previous_vel = pi->vel;
vm_vec_scale_add2( &pi->vel, direction_vec, velocity_scale );
// check that kick from shockwave is not too large
physics_maybe_reset_speed_after_whack(pi, &previous_vel, "physics_apply_shock");
// set so velocity will ramp starting from current speed
// ramped velocity is now affected by shockwave
vm_vec_rotate( &pi->prev_ramp_vel, &pi->vel, orient );
}
// ----------------------------------------------------------------------------
// physics_collide_whack applies an instaneous whack on an object changing
// both the objects velocity and the rotational velocity based on the impulse
// being applied.
//
// input: impulse => impulse vector ( force*time = impulse = change in momentum (mv) )
// world_delta_rotvel => change in rotational velocity (already calculated)
// pi => pointer to phys_info struct of object getting whacked
// orient => orientation matrix (needed to set rotational impulse in body coords)
//
// Warning: Do not change ROTVEL_COLLIDE_WHACK_CONST. This will mess up collision physics.
// If you need to change the rotation, change COLLISION_ROTATION_FACTOR in collide_ship_ship.
#define ROTVEL_COLLIDE_WHACK_CONST 1.0
void physics_collide_whack( vec3d *impulse, vec3d *world_delta_rotvel, physics_info *pi, matrix *orient, bool is_landing )
{
vec3d body_delta_rotvel;
// Detect null vector.
if ((fl_abs(impulse->xyz.x) <= WHACK_LIMIT) && (fl_abs(impulse->xyz.y) <= WHACK_LIMIT) && (fl_abs(impulse->xyz.z) <= WHACK_LIMIT))
return;
vm_vec_rotate( &body_delta_rotvel, world_delta_rotvel, orient );
// vm_vec_scale( &body_delta_rotvel, (float) ROTVEL_COLLIDE_WHACK_CONST );
vm_vec_add2( &pi->rotvel, &body_delta_rotvel );
pi->flags |= PF_REDUCED_DAMP;
update_reduced_damp_timestamp( pi, vm_vec_mag(impulse) );
// find time for shake from weapon to end
if (!is_landing) {
int dtime = timestamp_until(pi->afterburner_decay);
if (dtime < WEAPON_SHAKE_TIME) {
pi->afterburner_decay = timestamp( WEAPON_SHAKE_TIME );
}
}
vec3d previous_vel = pi->vel;
vm_vec_scale_add2( &pi->vel, impulse, 1.0f / pi->mass );
// check that collision does not give ship too much speed
physics_maybe_reset_speed_after_whack(pi, &previous_vel, "physics_collide_whack");
// set so velocity will ramp starting from current speed
// ramped velocity is now affected by collision
vm_vec_rotate( &pi->prev_ramp_vel, &pi->vel, orient );
}
int check_rotvel_limit( physics_info *pi )
{
if ( 0 == pi->flags ) // weapon
return 0;
if ( Fred_running )
return 0;
int change_made = 0;
if ( !(pi->flags & PF_DEAD_DAMP) ) {
// case of normal, live ship
// -- Commented out by MK: Assert( vm_vec_mag_squared(&pi->max_rotvel) > ROTVEL_TOL );
// Assert( (pi->max_rotvel.xyz.x <= ROTVEL_CAP) && (pi->max_rotvel.xyz.y <= ROTVEL_CAP) && (pi->max_rotvel.xyz.z <= ROTVEL_CAP) );
// Warning(LOCATION,"Excessive rotvel (wx: %f, wy: %f, wz:%f)\n", pi->rotvel.xyz.x, pi->rotvel.xyz.y, pi->rotvel.xyz.z);
if ( fl_abs(pi->rotvel.xyz.x) > pi->max_rotvel.xyz.x ) {
pi->rotvel.xyz.x = (pi->rotvel.xyz.x / fl_abs(pi->rotvel.xyz.x)) * (pi->max_rotvel.xyz.x - (float) ROTVEL_TOL);
change_made = 1;
}
if ( fl_abs(pi->rotvel.xyz.y) > pi->max_rotvel.xyz.y ) {
pi->rotvel.xyz.y = (pi->rotvel.xyz.y / fl_abs(pi->rotvel.xyz.y)) * (pi->max_rotvel.xyz.y - (float) ROTVEL_TOL);
change_made = 1;
}
if ( fl_abs(pi->rotvel.xyz.z) > pi->max_rotvel.xyz.z ) {
pi->rotvel.xyz.z = (pi->rotvel.xyz.z / fl_abs(pi->rotvel.xyz.z)) * (pi->max_rotvel.xyz.z - (float) ROTVEL_TOL);
change_made = 1;
}
} else {
// case of dead ship
if ( fl_abs(pi->rotvel.xyz.x) > DEAD_ROTVEL_CAP ) {
pi->rotvel.xyz.x = (pi->rotvel.xyz.x / fl_abs(pi->rotvel.xyz.x)) * (float) (DEAD_ROTVEL_CAP - ROTVEL_TOL);
change_made = 1;
}
if ( fl_abs(pi->rotvel.xyz.y) > DEAD_ROTVEL_CAP ) {
pi->rotvel.xyz.y = (pi->rotvel.xyz.y / fl_abs(pi->rotvel.xyz.y)) * (float) (DEAD_ROTVEL_CAP - ROTVEL_TOL);
change_made = 1;
}
if ( fl_abs(pi->rotvel.xyz.z) > DEAD_ROTVEL_CAP ) {
pi->rotvel.xyz.z = (pi->rotvel.xyz.z / fl_abs(pi->rotvel.xyz.z)) * (float) (DEAD_ROTVEL_CAP - ROTVEL_TOL);
change_made = 1;
}
}
return change_made;
}
// ----------------------------------------------------------------------------
// update_reduced_damp_timestamp()
//
void update_reduced_damp_timestamp( physics_info *pi, float impulse )
{
// Compute duration of reduced damp from present
// Compare with current value and increase if greater, otherwise ignore
int reduced_damp_decay_time;
reduced_damp_decay_time = (int) (REDUCED_DAMP_TIME * impulse / (REDUCED_DAMP_VEL * pi->mass));
if ( reduced_damp_decay_time > REDUCED_DAMP_TIME )
reduced_damp_decay_time = REDUCED_DAMP_TIME;
// Reset timestamp if larger than current (if any)
if ( timestamp_valid( pi->reduced_damp_decay ) ) {
int time_left = timestamp_until( pi->reduced_damp_decay );
if ( time_left > 0 ) {
// increment old time, but apply cap
int new_time = reduced_damp_decay_time + time_left;
if ( new_time < REDUCED_DAMP_TIME ) {
pi->reduced_damp_decay = timestamp( new_time );
}
} else {
pi->reduced_damp_decay = timestamp( reduced_damp_decay_time );
}
} else {
// set if not valid
pi->reduced_damp_decay = timestamp( reduced_damp_decay_time );
}
}
void physics_add_point_mass_moi(matrix *moi, float mass, vec3d *pos)
{
// moment of inertia for a point mass:
// I_xx = m(y^2+z^2) | I_yx = -mxy | I_zx = -mxz
// I_xy = -mxy | I_yy = m(x^2+z^2) | I_zy = -myz
// I_xz = -mxz | I_yz = -myz | I_zz = m(x^2+y^2)
moi->a2d[0][0] += mass * (pos->xyz.y * pos->xyz.y + pos->xyz.z * pos->xyz.z);
moi->a2d[0][1] -= mass * pos->xyz.x * pos->xyz.y;
moi->a2d[0][2] -= mass * pos->xyz.x * pos->xyz.z;
moi->a2d[1][0] -= mass * pos->xyz.x * pos->xyz.y;
moi->a2d[1][1] += mass * (pos->xyz.x * pos->xyz.x + pos->xyz.z * pos->xyz.z);
moi->a2d[1][2] -= mass * pos->xyz.y * pos->xyz.z;
moi->a2d[2][0] -= mass * pos->xyz.x * pos->xyz.z;
moi->a2d[2][1] -= mass * pos->xyz.y * pos->xyz.z;
moi->a2d[2][2] += mass * (pos->xyz.x * pos->xyz.x + pos->xyz.y * pos->xyz.y);
}
// equation from https://en.wikipedia.org/wiki/Projectile_motion#Angle_%CE%B8_required_to_hit_coordinate_(x,_y)
bool physics_lead_ballistic_trajectory(const vec3d* start, const vec3d* end_pos, const vec3d* target_vel, float weapon_speed, const vec3d* gravity, vec3d* out_direction) {
float best_guess_time = 0.0f;
*out_direction = vmd_zero_vector;
float time = 0.0f;
for (int i = 0; i < 6; i++) {
vec3d target = *end_pos - *start;
target += *target_vel * time;
vec3d gravity_dir = *gravity;
float gravity_accel = vm_vec_normalize(&gravity_dir);
float height = -vm_vec_dot(&target, &gravity_dir);
vec3d plane_pos = target + gravity_dir * height;
float range = vm_vec_normalize(&plane_pos);
float vel_2 = weapon_speed * weapon_speed;
float gx = gravity_accel * range;
float gy = gravity_accel * height;
float discriminant = vel_2 * vel_2 - (gx * gx + 2 * gy * vel_2);
if (discriminant < 0)
return false;
float angle = atanf((vel_2 - sqrtf(discriminant)) / gx);
*out_direction = plane_pos * cosf(angle) - (gravity_dir)*sinf(angle);
time = range / (weapon_speed * cosf(angle));
if (abs(time - best_guess_time) < 0.01f)
break;
else
best_guess_time = time;
}
return true;
}
//*************************CLASS: avd_movement*************************
avd_movement::avd_movement() : Pc(0.0f), Vc(0.0f), TSi(0), Pi(0.0f), Vi(0.0f), Pf(0.0f), Tf(0.0f), Tai(0.0f), Taf(0.0f), Vf(0.0f), Vm(0.0f), Ai(0.0f), Af(0.0f)
{
}
void avd_movement::clear()
{
// We should really refactor so that a ::clear() method isn't needed.
// For now, be sure this syncs with the class declaration
//Current
Pc = 0; //Current position
Vc = 0; //Current velocity
//Initial
TSi = 0; //Initial timestamp <-- note TIMESTAMP
Pi = 0; //Initial position
Vi = 0; //Initial velocity
//Given
Pf = 0; //Final position
Tf = 0; //Final duration
Tai = 0; //Starting acceleration duration
Taf = 0; //Ending acceleration duration
Vf = 0; //Final velocity
//Calculated
Vm = 0; //Middle velocity
Ai = 0; //Starting acceleration
Af = 0; //Ending acceleration
}
void avd_movement::get(float Time, float *Position, float *Velocity)
{
this->update(Time);
if(Position != NULL)
*Position = Pc;
if(Velocity != NULL)
*Velocity = Vc;
}
void avd_movement::get(float *Position, float *Velocity)
{
float time = (float)(timestamp() - TSi)/1000.0f;
this->get(time, Position, Velocity);
}
void avd_movement::set(float position)
{
this->clear();
Pi = Pf = Pc = position;
}
void avd_movement::setAVD(float final_position, float total_movement_time, float starting_accleration_time, float ending_acceleration_time, float final_velocity)
{
//Make sure Pc et al are up-to-date
this->update();
Pf = final_position;
Tf = total_movement_time;
Tai = starting_accleration_time;
Taf = ending_acceleration_time;
Vf = final_velocity;
if(Tai+Taf >= Tf)
{
Tai = Tf;
Taf = 0.0f;
}
if(Tf <= 0.0f)
{
Pc = Pi = Pf;
Vc = Vi = Vf;
return;
}
Pi = Pc;
Vi = Vc;
TSi = timestamp();
Vm = (Pf-Pi-0.5f*(Vi*Tai)-0.5f*(Vf*Taf)) / (Tf - 0.5f*Tai - 0.5f*Taf);
// Cyborg - Many coverity dividing by zero issues were caused by this piece of low level code. Most places that called this function were also not checking inputs.
// The two if blocks in this function do not guarantee that they will not be float zero (e.g. tai = 2.0 taf = 0.0 Tf = 1.0)
// The reason why this code used to work even when there were divisions by zero is that the NAN results were gated behind Taf > 0.0 and Tai > 0.0
// checks in other functions. Ai, initial Acceleration, and Af, final Acceleration, are completely meaningless and explicitly disabled if their respective
// time amounts are zero or less than zero. So instead of just letting some NAN's chill out in memory, waiting for someone else to unwisely access them,
// we are going to explicitly set them to zero, like a good engine should.
if (Tai <= 0.0f){
Ai = 0.0;
} else {
Ai = (Vm-Vi)/Tai;
}
if (Taf <= 0.0f){
Af = 0.0f;
} else {
Af = (Vf-Vm)/Taf;
}
}
void avd_movement::setVD(float total_movement_time, float ending_acceleration_time, float final_velocity)
{
//Make sure Pc et al are up-to-date
this->update();
Tf = total_movement_time;
Tai = 0.0f;
Taf = ending_acceleration_time;
Vf = final_velocity;
Pi = Pc;
Vm = Vi = Vc;
TSi = timestamp();
Ai = 0.0f;
// Avoid division by float zero. See the note in setAVD for more info.
if (Taf <= 0.0f){
Af = 0.0f;
} else {
Af = (Vf-Vm)/Taf;
}
Pf = Pi + Pf*(Tf - Taf) + Vm*Taf + 0.5f*Af*(Taf*Taf);
}
void avd_movement::update()
{
float time = (float)(timestamp() - TSi)/1000.0f;
this->update(time);
}
void avd_movement::update(float Time)
{
if(Tf <= 0.0f)
{
//This avd_movement is just serving as static storage
Pc = Pi;
Vc = Vi;
}
else if(Time >= Tf)
{
//Movement has ended, but the thing may still have a constant velocity
Pc = Pf + Vf*(Time-Tf);
Vc = Vf;
}
else
{
//Movement is in-progress and we must calculate where the thing is now.
float t = Time;
float Tc = 0.0f;
if(t >= 0.0f)
{
Pc = Pi;
Vc = Vi;
}
if(t >= 0.0f && Tai > 0.0f)
{
if(t < Tai)
Tc = t;
else
Tc = Tai;
Pc = Pc + Vi*Tc + 0.5f*Ai*(Tc*Tc);
Vc = Vc + Ai*Tc;
}
if(t >= Tai && (Tai+Taf) < Tf)
{
if(t < (Tf-Taf))
Tc = (t-Tai);
else
Tc = (Tf-Tai-Taf);
Pc = Pc + Vm*Tc;
// Vc = Vc;
}
if(t >= (Tf-Taf) && Taf > 0.0f)
{
if(t < Tf)
Tc = t-(Tf-Taf);
else
Tc = Taf;
Pc = Pc + Vm*Tc + 0.5f*Af*(Tc*Tc);
Vc = Vc + Af*Tc;
}
}
}
|