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