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 rendering external scenes |
||
23 | * |
||
24 | */ |
||
25 | |||
26 | //#define _MARK_ON |
||
27 | |||
28 | #include <algorithm> |
||
29 | #include <stdlib.h> |
||
30 | |||
31 | |||
32 | #include <stdio.h> |
||
33 | #include <string.h> |
||
34 | #include <ctype.h> // for isspace |
||
35 | |||
36 | #include "maths.h" |
||
37 | #include "vecmat.h" |
||
38 | #include "gr.h" |
||
39 | #include "3d.h" |
||
40 | #include "dxxerror.h" |
||
41 | #include "palette.h" |
||
42 | #include "iff.h" |
||
43 | #include "console.h" |
||
44 | #include "texmap.h" |
||
45 | #include "fvi.h" |
||
46 | #include "u_mem.h" |
||
47 | #include "sounds.h" |
||
48 | #include "playsave.h" |
||
49 | #include "inferno.h" |
||
50 | #include "endlevel.h" |
||
51 | #include "object.h" |
||
52 | #include "game.h" |
||
53 | #include "gamepal.h" |
||
54 | #include "screens.h" |
||
55 | #include "gauges.h" |
||
56 | #include "terrain.h" |
||
57 | #include "robot.h" |
||
58 | #include "player.h" |
||
59 | #include "physfsx.h" |
||
60 | #include "bm.h" |
||
61 | #include "gameseg.h" |
||
62 | #include "gameseq.h" |
||
63 | #include "newdemo.h" |
||
64 | #include "gamepal.h" |
||
65 | #include "multi.h" |
||
66 | #include "vclip.h" |
||
67 | #include "fireball.h" |
||
68 | #include "text.h" |
||
69 | #include "digi.h" |
||
70 | #include "songs.h" |
||
71 | #if defined(DXX_BUILD_DESCENT_II) |
||
72 | #include "movie.h" |
||
73 | #endif |
||
74 | #include "render.h" |
||
75 | #include "titles.h" |
||
76 | #include "hudmsg.h" |
||
77 | #if DXX_USE_OGL |
||
78 | #include "ogl_init.h" |
||
79 | #endif |
||
80 | |||
81 | #if DXX_USE_EDITOR |
||
82 | #include "editor/editor.h" |
||
83 | #endif |
||
84 | |||
85 | #include "compiler-range_for.h" |
||
86 | #include "d_enumerate.h" |
||
87 | #include "d_range.h" |
||
88 | #include <iterator> |
||
89 | |||
90 | using std::min; |
||
91 | using std::max; |
||
92 | |||
93 | namespace { |
||
94 | |||
95 | struct flythrough_data |
||
96 | { |
||
97 | object *obj; |
||
98 | vms_angvec angles; //orientation in angles |
||
99 | vms_vector step; //how far in a second |
||
100 | vms_vector angstep; //rotation per second |
||
101 | fix speed; //how fast object is moving |
||
102 | vms_vector headvec; //where we want to be pointing |
||
103 | int first_time; //flag for if first time through |
||
104 | fix offset_frac; //how far off-center as portion of way |
||
105 | fix offset_dist; //how far currently off-center |
||
106 | }; |
||
107 | |||
108 | #define MAX_FLY_OBJECTS 2 |
||
109 | |||
110 | d_unique_endlevel_state UniqueEndlevelState; |
||
111 | |||
112 | } |
||
113 | |||
114 | static std::array<flythrough_data, MAX_FLY_OBJECTS> fly_objects; |
||
115 | |||
116 | //endlevel sequence states |
||
117 | |||
118 | #define EL_OFF 0 //not in endlevel |
||
119 | #define EL_FLYTHROUGH 1 //auto-flythrough in tunnel |
||
120 | #define EL_LOOKBACK 2 //looking back at player |
||
121 | #define EL_OUTSIDE 3 //flying outside for a while |
||
122 | #define EL_STOPPED 4 //stopped, watching explosion |
||
123 | #define EL_PANNING 5 //panning around, watching player |
||
124 | #define EL_CHASING 6 //chasing player to station |
||
125 | |||
126 | #define SHORT_SEQUENCE 1 //if defined, end sequnce when panning starts |
||
127 | |||
128 | int Endlevel_sequence = 0; |
||
129 | |||
130 | static object *endlevel_camera; |
||
131 | |||
132 | #define FLY_SPEED i2f(50) |
||
133 | |||
134 | static void do_endlevel_flythrough(flythrough_data *flydata); |
||
135 | static void draw_stars(grs_canvas &, const d_unique_endlevel_state::starfield_type &stars); |
||
136 | static int find_exit_side(const object_base &obj); |
||
137 | static void generate_starfield(d_unique_endlevel_state::starfield_type &stars); |
||
138 | static void start_endlevel_flythrough(flythrough_data *flydata,const vmobjptr_t obj,fix speed); |
||
139 | |||
140 | #if defined(DXX_BUILD_DESCENT_II) |
||
141 | constexpr std::array<const char, 24> movie_table{{ |
||
142 | 'A','B','C','A', |
||
143 | 'D','F','D','F', |
||
144 | 'G','H','I','G', |
||
145 | 'J','K','L','J', |
||
146 | 'M','O','M','O', |
||
147 | 'P','Q','P','Q' |
||
148 | }}; |
||
149 | static int endlevel_movie_played = MOVIE_NOT_PLAYED; |
||
150 | #endif |
||
151 | |||
152 | |||
153 | #define FLY_ACCEL i2f(5) |
||
154 | |||
155 | static fix cur_fly_speed,desired_fly_speed; |
||
156 | |||
157 | static grs_bitmap *satellite_bitmap; |
||
158 | grs_bitmap *terrain_bitmap; //!!*exit_bitmap, |
||
159 | vms_vector satellite_pos,satellite_upvec; |
||
160 | //!!grs_bitmap **exit_bitmap_list[1]; |
||
161 | unsigned exit_modelnum,destroyed_exit_modelnum; |
||
162 | |||
163 | static vms_vector station_pos{0xf8c4<<10,0x3c1c<<12,0x372<<10}; |
||
164 | |||
165 | static vms_vector mine_exit_point; |
||
166 | static vms_vector mine_ground_exit_point; |
||
167 | static vms_vector mine_side_exit_point; |
||
168 | static vms_matrix mine_exit_orient; |
||
169 | |||
170 | static int outside_mine; |
||
171 | |||
172 | static grs_main_bitmap terrain_bm_instance, satellite_bm_instance; |
||
173 | |||
174 | //find delta between two angles |
||
175 | static fixang delta_ang(fixang a,fixang b) |
||
176 | { |
||
177 | fixang delta0,delta1; |
||
178 | |||
179 | return (abs(delta0 = a - b) < abs(delta1 = b - a)) ? delta0 : delta1; |
||
180 | |||
181 | } |
||
182 | |||
183 | //return though which side of seg0 is seg1 |
||
184 | static size_t matt_find_connect_side(const shared_segment &seg0, const vcsegidx_t seg1) |
||
185 | { |
||
186 | auto &children = seg0.children; |
||
187 | return std::distance(children.begin(), std::find(children.begin(), children.end(), seg1)); |
||
188 | } |
||
189 | |||
190 | #if defined(DXX_BUILD_DESCENT_II) |
||
191 | #define MOVIE_REQUIRED 1 |
||
192 | |||
193 | //returns movie played status. see movie.h |
||
194 | static int start_endlevel_movie() |
||
195 | { |
||
196 | int r; |
||
197 | palette_array_t save_pal; |
||
198 | |||
199 | //Assert(PLAYING_BUILTIN_MISSION); //only play movie for built-in mission |
||
200 | |||
201 | //Assert(N_MOVIES >= Last_level); |
||
202 | //Assert(N_MOVIES_SECRET >= -Last_secret_level); |
||
203 | |||
204 | const auto current_level_num = Current_level_num; |
||
205 | if (is_SHAREWARE) |
||
206 | return 0; |
||
207 | if (!is_D2_OEM) |
||
208 | if (current_level_num == Last_level) |
||
209 | return 1; //don't play movie |
||
210 | |||
211 | if (!(current_level_num > 0)) |
||
212 | return 0; //no escapes for secret level |
||
213 | char movie_name[] = "ESA.MVE"; |
||
214 | movie_name[2] = movie_table[Current_level_num-1]; |
||
215 | |||
216 | save_pal = gr_palette; |
||
217 | |||
218 | r=PlayMovie(NULL, movie_name,(Game_mode & GM_MULTI)?0:MOVIE_REQUIRED); |
||
219 | |||
220 | if (Newdemo_state == ND_STATE_PLAYBACK) { |
||
221 | set_screen_mode(SCREEN_GAME); |
||
222 | gr_palette = save_pal; |
||
223 | } |
||
224 | |||
225 | return (r); |
||
226 | |||
227 | } |
||
228 | #endif |
||
229 | |||
230 | void free_endlevel_data() |
||
231 | { |
||
232 | terrain_bm_instance.reset(); |
||
233 | satellite_bm_instance.reset(); |
||
234 | |||
235 | free_light_table(); |
||
236 | free_height_array(); |
||
237 | } |
||
238 | |||
239 | static object *external_explosion; |
||
240 | static int ext_expl_playing,mine_destroyed; |
||
241 | |||
242 | static vms_angvec exit_angles={-0xa00,0,0}; |
||
243 | |||
244 | vms_matrix surface_orient; |
||
245 | |||
246 | static int endlevel_data_loaded; |
||
247 | |||
248 | static unsigned get_tunnel_length(fvcsegptridx &vcsegptridx, const vcsegptridx_t console_seg, const unsigned exit_console_side) |
||
249 | { |
||
250 | auto seg = console_seg; |
||
251 | auto exit_side = exit_console_side; |
||
252 | unsigned tunnel_length = 0; |
||
253 | for (;;) |
||
254 | { |
||
255 | const auto child = seg->children[exit_side]; |
||
256 | if (child == segment_none) |
||
257 | return 0; |
||
258 | ++tunnel_length; |
||
259 | if (child == segment_exit) |
||
260 | { |
||
261 | assert(seg == PlayerUniqueEndlevelState.exit_segnum); |
||
262 | return tunnel_length; |
||
263 | } |
||
264 | const vcsegidx_t old_segidx = seg; |
||
265 | seg = vcsegptridx(child); |
||
266 | const auto entry_side = matt_find_connect_side(seg, old_segidx); |
||
267 | if (entry_side >= Side_opposite.size()) |
||
268 | return 0; |
||
269 | exit_side = Side_opposite[entry_side]; |
||
270 | } |
||
271 | } |
||
272 | |||
273 | static vcsegidx_t get_tunnel_transition_segment(const unsigned tunnel_length, const vcsegptridx_t console_seg, const unsigned exit_console_side) |
||
274 | { |
||
275 | auto seg = console_seg; |
||
276 | auto exit_side = exit_console_side; |
||
277 | for (auto i = tunnel_length / 3;; --i) |
||
278 | { |
||
279 | /* |
||
280 | * If the tunnel ended with segment_none, the caller would have |
||
281 | * returned immediately after the prior loop. If the tunnel |
||
282 | * ended with segment_exit, then tunnel_length is the count of |
||
283 | * segments to reach the exit. The termination condition on |
||
284 | * this loop quits at (tunnel_length / 3), so the loop will quit |
||
285 | * before it reaches segment_exit. |
||
286 | */ |
||
287 | const auto child = seg->children[exit_side]; |
||
288 | if (!IS_CHILD(child)) |
||
289 | /* This is only a sanity check. It should never execute |
||
290 | * unless there is a bug elsewhere. |
||
291 | */ |
||
292 | return seg; |
||
293 | if (!i) |
||
294 | return child; |
||
295 | const vcsegidx_t old_segidx = seg; |
||
296 | seg = vcsegptridx(child); |
||
297 | const auto entry_side = matt_find_connect_side(seg, old_segidx); |
||
298 | exit_side = Side_opposite[entry_side]; |
||
299 | } |
||
300 | } |
||
301 | |||
302 | namespace dsx { |
||
303 | window_event_result start_endlevel_sequence() |
||
304 | { |
||
305 | auto &Objects = LevelUniqueObjectState.Objects; |
||
306 | auto &vmobjptr = Objects.vmptr; |
||
307 | reset_rear_view(); //turn off rear view if set - NOTE: make sure this happens before we pause demo recording!! |
||
308 | |||
309 | if (Newdemo_state == ND_STATE_RECORDING) // stop demo recording |
||
310 | Newdemo_state = ND_STATE_PAUSED; |
||
311 | |||
312 | if (Newdemo_state == ND_STATE_PLAYBACK) { // don't do this if in playback mode |
||
313 | #if defined(DXX_BUILD_DESCENT_II) |
||
314 | if (PLAYING_BUILTIN_MISSION) // only play movie for built-in mission |
||
315 | { |
||
316 | window_set_visible(Game_wind, 0); // suspend the game, including drawing |
||
317 | start_endlevel_movie(); |
||
318 | window_set_visible(Game_wind, 1); |
||
319 | } |
||
320 | strcpy(last_palette_loaded,""); //force palette load next time |
||
321 | #endif |
||
322 | return window_event_result::ignored; |
||
323 | } |
||
324 | |||
325 | if (Player_dead_state != player_dead_state::no || |
||
326 | (ConsoleObject->flags & OF_SHOULD_BE_DEAD)) |
||
327 | return window_event_result::ignored; //don't start if dead! |
||
328 | con_puts(CON_NORMAL, "You have escaped the mine!"); |
||
329 | |||
330 | #if defined(DXX_BUILD_DESCENT_II) |
||
331 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
332 | // Dematerialize Buddy! |
||
333 | range_for (const auto &&objp, vmobjptr) |
||
334 | { |
||
335 | if (objp->type == OBJ_ROBOT) |
||
336 | if (Robot_info[get_robot_id(objp)].companion) { |
||
337 | object_create_explosion(vmsegptridx(objp->segnum), objp->pos, F1_0*7/2, VCLIP_POWERUP_DISAPPEARANCE ); |
||
338 | objp->flags |= OF_SHOULD_BE_DEAD; |
||
339 | } |
||
340 | } |
||
341 | #endif |
||
342 | |||
343 | get_local_plrobj().ctype.player_info.homing_object_dist = -F1_0; // Turn off homing sound. |
||
344 | |||
345 | if (Game_mode & GM_MULTI) { |
||
346 | multi_send_endlevel_start(multi_endlevel_type::normal); |
||
347 | multi_do_protocol_frame(1, 1); |
||
348 | } |
||
349 | |||
350 | #if defined(DXX_BUILD_DESCENT_I) |
||
351 | if (!endlevel_data_loaded) |
||
352 | #elif defined(DXX_BUILD_DESCENT_II) |
||
353 | |||
354 | if (PLAYING_BUILTIN_MISSION) // only play movie for built-in mission |
||
355 | if (!(Game_mode & GM_MULTI)) |
||
356 | { |
||
357 | window_set_visible(Game_wind, 0); // suspend the game, including drawing |
||
358 | endlevel_movie_played = start_endlevel_movie(); |
||
359 | window_set_visible(Game_wind, 1); |
||
360 | } |
||
361 | |||
362 | if (!(!(Game_mode & GM_MULTI) && (endlevel_movie_played == MOVIE_NOT_PLAYED) && endlevel_data_loaded)) |
||
363 | #endif |
||
364 | { |
||
365 | |||
366 | return PlayerFinishedLevel(0); //done with level |
||
367 | } |
||
368 | #if defined(DXX_BUILD_DESCENT_II) |
||
369 | int exit_models_loaded = 0; |
||
370 | |||
371 | if (Piggy_hamfile_version < 3) |
||
372 | exit_models_loaded = 1; // built-in for PC shareware |
||
373 | |||
374 | else |
||
375 | exit_models_loaded = load_exit_models(); |
||
376 | |||
377 | if (!exit_models_loaded) |
||
378 | return window_event_result::ignored; |
||
379 | #endif |
||
380 | { |
||
381 | //count segments in exit tunnel |
||
382 | |||
383 | const object_base &console = vmobjptr(ConsoleObject); |
||
384 | const auto exit_console_side = find_exit_side(console); |
||
385 | const auto &&console_seg = vcsegptridx(console.segnum); |
||
386 | const auto tunnel_length = get_tunnel_length(vcsegptridx, console_seg, exit_console_side); |
||
387 | if (!tunnel_length) |
||
388 | { |
||
389 | return PlayerFinishedLevel(0); //don't do special sequence |
||
390 | } |
||
391 | //now pick transition segnum 1/3 of the way in |
||
392 | |||
393 | PlayerUniqueEndlevelState.transition_segnum = get_tunnel_transition_segment(tunnel_length, console_seg, exit_console_side); |
||
394 | } |
||
395 | |||
396 | if (Game_mode & GM_MULTI) { |
||
397 | multi_send_endlevel_start(multi_endlevel_type::normal); |
||
398 | multi_do_protocol_frame(1, 1); |
||
399 | } |
||
400 | songs_play_song( SONG_ENDLEVEL, 0 ); |
||
401 | |||
402 | Endlevel_sequence = EL_FLYTHROUGH; |
||
403 | |||
404 | ConsoleObject->movement_type = MT_NONE; //movement handled by flythrough |
||
405 | ConsoleObject->control_type = CT_NONE; |
||
406 | |||
407 | Game_suspended |= SUSP_ROBOTS; //robots don't move |
||
408 | |||
409 | cur_fly_speed = desired_fly_speed = FLY_SPEED; |
||
410 | |||
411 | start_endlevel_flythrough(&fly_objects[0], vmobjptr(ConsoleObject), cur_fly_speed); //initialize |
||
412 | |||
413 | HUD_init_message_literal(HM_DEFAULT, TXT_EXIT_SEQUENCE ); |
||
414 | |||
415 | outside_mine = ext_expl_playing = 0; |
||
416 | |||
417 | flash_scale = f1_0; |
||
418 | |||
419 | generate_starfield(UniqueEndlevelState.stars); |
||
420 | |||
421 | mine_destroyed=0; |
||
422 | |||
423 | return window_event_result::handled; |
||
424 | } |
||
425 | } |
||
426 | |||
427 | static vms_angvec player_angles,player_dest_angles; |
||
428 | #ifndef SHORT_SEQUENCE |
||
429 | static vms_angvec camera_desired_angles,camera_cur_angles; |
||
430 | #endif |
||
431 | |||
432 | #define CHASE_TURN_RATE (0x4000/4) //max turn per second |
||
433 | |||
434 | //returns bitmask of which angles are at dest. bits 0,1,2 = p,b,h |
||
435 | static int chase_angles(vms_angvec *cur_angles,vms_angvec *desired_angles) |
||
436 | { |
||
437 | vms_angvec delta_angs,alt_angles,alt_delta_angs; |
||
438 | fix total_delta,alt_total_delta; |
||
439 | fix frame_turn; |
||
440 | int mask=0; |
||
441 | |||
442 | delta_angs.p = desired_angles->p - cur_angles->p; |
||
443 | delta_angs.h = desired_angles->h - cur_angles->h; |
||
444 | delta_angs.b = desired_angles->b - cur_angles->b; |
||
445 | |||
446 | total_delta = abs(delta_angs.p) + abs(delta_angs.b) + abs(delta_angs.h); |
||
447 | |||
448 | alt_angles.p = f1_0/2 - cur_angles->p; |
||
449 | alt_angles.b = cur_angles->b + f1_0/2; |
||
450 | alt_angles.h = cur_angles->h + f1_0/2; |
||
451 | |||
452 | alt_delta_angs.p = desired_angles->p - alt_angles.p; |
||
453 | alt_delta_angs.h = desired_angles->h - alt_angles.h; |
||
454 | alt_delta_angs.b = desired_angles->b - alt_angles.b; |
||
455 | |||
456 | alt_total_delta = abs(alt_delta_angs.p) + abs(alt_delta_angs.b) + abs(alt_delta_angs.h); |
||
457 | |||
458 | if (alt_total_delta < total_delta) { |
||
459 | *cur_angles = alt_angles; |
||
460 | delta_angs = alt_delta_angs; |
||
461 | } |
||
462 | |||
463 | frame_turn = fixmul(FrameTime,CHASE_TURN_RATE); |
||
464 | |||
465 | if (abs(delta_angs.p) < frame_turn) { |
||
466 | cur_angles->p = desired_angles->p; |
||
467 | mask |= 1; |
||
468 | } |
||
469 | else |
||
470 | if (delta_angs.p > 0) |
||
471 | cur_angles->p += frame_turn; |
||
472 | else |
||
473 | cur_angles->p -= frame_turn; |
||
474 | |||
475 | if (abs(delta_angs.b) < frame_turn) { |
||
476 | cur_angles->b = desired_angles->b; |
||
477 | mask |= 2; |
||
478 | } |
||
479 | else |
||
480 | if (delta_angs.b > 0) |
||
481 | cur_angles->b += frame_turn; |
||
482 | else |
||
483 | cur_angles->b -= frame_turn; |
||
484 | //cur_angles->b = 0; |
||
485 | |||
486 | if (abs(delta_angs.h) < frame_turn) { |
||
487 | cur_angles->h = desired_angles->h; |
||
488 | mask |= 4; |
||
489 | } |
||
490 | else |
||
491 | if (delta_angs.h > 0) |
||
492 | cur_angles->h += frame_turn; |
||
493 | else |
||
494 | cur_angles->h -= frame_turn; |
||
495 | |||
496 | return mask; |
||
497 | } |
||
498 | |||
499 | window_event_result stop_endlevel_sequence() |
||
500 | { |
||
501 | #if !DXX_USE_OGL |
||
502 | Interpolation_method = 0; |
||
503 | #endif |
||
504 | |||
505 | select_cockpit(PlayerCfg.CockpitMode[0]); |
||
506 | |||
507 | Endlevel_sequence = EL_OFF; |
||
508 | |||
509 | return PlayerFinishedLevel(0); |
||
510 | } |
||
511 | |||
512 | #define VCLIP_BIG_PLAYER_EXPLOSION 58 |
||
513 | |||
514 | //--unused-- vms_vector upvec = {0,f1_0,0}; |
||
515 | |||
516 | //find the angle between the player's heading & the station |
||
517 | static void get_angs_to_object(vms_angvec &av,const vms_vector &targ_pos,const vms_vector &cur_pos) |
||
518 | { |
||
519 | const auto tv = vm_vec_sub(targ_pos,cur_pos); |
||
520 | vm_extract_angles_vector(av,tv); |
||
521 | } |
||
522 | |||
523 | namespace dsx { |
||
524 | window_event_result do_endlevel_frame() |
||
525 | { |
||
526 | auto &Objects = LevelUniqueObjectState.Objects; |
||
527 | auto &vmobjptr = Objects.vmptr; |
||
528 | static fix timer; |
||
529 | static fix bank_rate; |
||
530 | static fix explosion_wait1=0; |
||
531 | static fix explosion_wait2=0; |
||
532 | static fix ext_expl_halflife; |
||
533 | |||
534 | auto result = endlevel_move_all_objects(); |
||
535 | |||
536 | if (ext_expl_playing) { |
||
537 | |||
538 | do_explosion_sequence(vmobjptr(external_explosion)); |
||
539 | |||
540 | if (external_explosion->lifeleft < ext_expl_halflife) |
||
541 | mine_destroyed = 1; |
||
542 | |||
543 | if (external_explosion->flags & OF_SHOULD_BE_DEAD) |
||
544 | ext_expl_playing = 0; |
||
545 | } |
||
546 | |||
547 | if (cur_fly_speed != desired_fly_speed) { |
||
548 | fix delta = desired_fly_speed - cur_fly_speed; |
||
549 | fix frame_accel = fixmul(FrameTime,FLY_ACCEL); |
||
550 | |||
551 | if (abs(delta) < frame_accel) |
||
552 | cur_fly_speed = desired_fly_speed; |
||
553 | else |
||
554 | if (delta > 0) |
||
555 | cur_fly_speed += frame_accel; |
||
556 | else |
||
557 | cur_fly_speed -= frame_accel; |
||
558 | } |
||
559 | |||
560 | //do big explosions |
||
561 | if (!outside_mine) { |
||
562 | |||
563 | if (Endlevel_sequence==EL_OUTSIDE) { |
||
564 | const auto tvec = vm_vec_sub(ConsoleObject->pos,mine_side_exit_point); |
||
565 | if (vm_vec_dot(tvec,mine_exit_orient.fvec) > 0) { |
||
566 | vms_vector mov_vec; |
||
567 | |||
568 | outside_mine = 1; |
||
569 | |||
570 | const auto &&exit_segp = vmsegptridx(PlayerUniqueEndlevelState.exit_segnum); |
||
571 | const auto &&tobj = object_create_explosion(exit_segp, mine_side_exit_point, i2f(50), VCLIP_BIG_PLAYER_EXPLOSION); |
||
572 | |||
573 | if (tobj) { |
||
574 | // Move explosion to Viewer to draw it in front of mine exit model |
||
575 | vm_vec_normalized_dir_quick(mov_vec,Viewer->pos,tobj->pos); |
||
576 | vm_vec_scale_add2(tobj->pos,mov_vec,i2f(30)); |
||
577 | external_explosion = tobj; |
||
578 | |||
579 | flash_scale = 0; //kill lights in mine |
||
580 | |||
581 | ext_expl_halflife = tobj->lifeleft; |
||
582 | |||
583 | ext_expl_playing = 1; |
||
584 | } |
||
585 | |||
586 | digi_link_sound_to_pos(SOUND_BIG_ENDLEVEL_EXPLOSION, exit_segp, 0, mine_side_exit_point, 0, i2f(3)/4); |
||
587 | } |
||
588 | } |
||
589 | |||
590 | //do explosions chasing player |
||
591 | if ((explosion_wait1-=FrameTime) < 0) { |
||
592 | static int sound_count; |
||
593 | |||
594 | auto tpnt = vm_vec_scale_add(ConsoleObject->pos,ConsoleObject->orient.fvec,-ConsoleObject->size*5); |
||
595 | vm_vec_scale_add2(tpnt,ConsoleObject->orient.rvec,(d_rand()-D_RAND_MAX/2)*15); |
||
596 | vm_vec_scale_add2(tpnt,ConsoleObject->orient.uvec,(d_rand()-D_RAND_MAX/2)*15); |
||
597 | |||
598 | const auto &&segnum = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, tpnt, Segments.vmptridx(ConsoleObject->segnum)); |
||
599 | |||
600 | if (segnum != segment_none) { |
||
601 | object_create_explosion(segnum,tpnt,i2f(20),VCLIP_BIG_PLAYER_EXPLOSION); |
||
602 | if (d_rand()<10000 || ++sound_count==7) { //pseudo-random |
||
603 | digi_link_sound_to_pos( SOUND_TUNNEL_EXPLOSION, segnum, 0, tpnt, 0, F1_0 ); |
||
604 | sound_count=0; |
||
605 | } |
||
606 | } |
||
607 | |||
608 | explosion_wait1 = 0x2000 + d_rand()/4; |
||
609 | |||
610 | } |
||
611 | } |
||
612 | |||
613 | //do little explosions on walls |
||
614 | if (Endlevel_sequence >= EL_FLYTHROUGH && Endlevel_sequence < EL_OUTSIDE) |
||
615 | if ((explosion_wait2-=FrameTime) < 0) { |
||
616 | fvi_query fq; |
||
617 | fvi_info hit_data; |
||
618 | |||
619 | //create little explosion on wall |
||
620 | |||
621 | auto tpnt = vm_vec_copy_scale(ConsoleObject->orient.rvec,(d_rand()-D_RAND_MAX/2)*100); |
||
622 | vm_vec_scale_add2(tpnt,ConsoleObject->orient.uvec,(d_rand()-D_RAND_MAX/2)*100); |
||
623 | vm_vec_add2(tpnt,ConsoleObject->pos); |
||
624 | |||
625 | if (Endlevel_sequence == EL_FLYTHROUGH) |
||
626 | vm_vec_scale_add2(tpnt,ConsoleObject->orient.fvec,d_rand()*200); |
||
627 | else |
||
628 | vm_vec_scale_add2(tpnt,ConsoleObject->orient.fvec,d_rand()*60); |
||
629 | |||
630 | //find hit point on wall |
||
631 | |||
632 | fq.p0 = &ConsoleObject->pos; |
||
633 | fq.p1 = &tpnt; |
||
634 | fq.startseg = ConsoleObject->segnum; |
||
635 | fq.rad = 0; |
||
636 | fq.thisobjnum = object_first; |
||
637 | fq.ignore_obj_list.first = nullptr; |
||
638 | fq.flags = 0; |
||
639 | |||
640 | find_vector_intersection(fq, hit_data); |
||
641 | |||
642 | if (hit_data.hit_type==HIT_WALL && hit_data.hit_seg!=segment_none) |
||
643 | object_create_explosion(vmsegptridx(hit_data.hit_seg), hit_data.hit_pnt, i2f(3) + d_rand() * 6, VCLIP_SMALL_EXPLOSION); |
||
644 | |||
645 | explosion_wait2 = (0xa00 + d_rand()/8)/2; |
||
646 | } |
||
647 | |||
648 | switch (Endlevel_sequence) { |
||
649 | |||
650 | case EL_OFF: return result; |
||
651 | |||
652 | case EL_FLYTHROUGH: { |
||
653 | |||
654 | do_endlevel_flythrough(&fly_objects[0]); |
||
655 | |||
656 | if (ConsoleObject->segnum == PlayerUniqueEndlevelState.transition_segnum) |
||
657 | { |
||
658 | #if defined(DXX_BUILD_DESCENT_II) |
||
659 | if (PLAYING_BUILTIN_MISSION && endlevel_movie_played != MOVIE_NOT_PLAYED) |
||
660 | result = std::max(stop_endlevel_sequence(), result); |
||
661 | else |
||
662 | #endif |
||
663 | { |
||
664 | |||
665 | //songs_play_song( SONG_ENDLEVEL, 0 ); |
||
666 | |||
667 | Endlevel_sequence = EL_LOOKBACK; |
||
668 | |||
669 | auto objnum = obj_create(OBJ_CAMERA, 0, |
||
670 | vmsegptridx(ConsoleObject->segnum), ConsoleObject->pos, &ConsoleObject->orient, 0, |
||
671 | CT_NONE,MT_NONE,RT_NONE); |
||
672 | |||
673 | if (objnum == object_none) { //can't get object, so abort |
||
674 | return std::max(stop_endlevel_sequence(), result); |
||
675 | } |
||
676 | |||
677 | Viewer = endlevel_camera = objnum; |
||
678 | |||
679 | select_cockpit(CM_LETTERBOX); |
||
680 | |||
681 | fly_objects[1] = fly_objects[0]; |
||
682 | fly_objects[1].obj = endlevel_camera; |
||
683 | fly_objects[1].speed = (5*cur_fly_speed)/4; |
||
684 | fly_objects[1].offset_frac = 0x4000; |
||
685 | |||
686 | vm_vec_scale_add2(endlevel_camera->pos,endlevel_camera->orient.fvec,i2f(7)); |
||
687 | |||
688 | timer=0x20000; |
||
689 | |||
690 | } |
||
691 | } |
||
692 | |||
693 | break; |
||
694 | } |
||
695 | |||
696 | |||
697 | case EL_LOOKBACK: { |
||
698 | |||
699 | do_endlevel_flythrough(&fly_objects[0]); |
||
700 | do_endlevel_flythrough(&fly_objects[1]); |
||
701 | |||
702 | if (timer>0) { |
||
703 | |||
704 | timer -= FrameTime; |
||
705 | |||
706 | if (timer < 0) //reduce speed |
||
707 | fly_objects[1].speed = fly_objects[0].speed; |
||
708 | } |
||
709 | |||
710 | if (endlevel_camera->segnum == PlayerUniqueEndlevelState.exit_segnum) |
||
711 | { |
||
712 | Endlevel_sequence = EL_OUTSIDE; |
||
713 | |||
714 | timer = i2f(2); |
||
715 | |||
716 | vm_vec_negate(endlevel_camera->orient.fvec); |
||
717 | vm_vec_negate(endlevel_camera->orient.rvec); |
||
718 | |||
719 | const auto cam_angles = vm_extract_angles_matrix(endlevel_camera->orient); |
||
720 | const auto exit_seg_angles = vm_extract_angles_matrix(mine_exit_orient); |
||
721 | bank_rate = (-exit_seg_angles.b - cam_angles.b)/2; |
||
722 | |||
723 | ConsoleObject->control_type = endlevel_camera->control_type = CT_NONE; |
||
724 | |||
725 | } |
||
726 | |||
727 | break; |
||
728 | } |
||
729 | |||
730 | case EL_OUTSIDE: { |
||
731 | vm_vec_scale_add2(ConsoleObject->pos,ConsoleObject->orient.fvec,fixmul(FrameTime,cur_fly_speed)); |
||
732 | vm_vec_scale_add2(endlevel_camera->pos,endlevel_camera->orient.fvec,fixmul(FrameTime,-2*cur_fly_speed)); |
||
733 | vm_vec_scale_add2(endlevel_camera->pos,endlevel_camera->orient.uvec,fixmul(FrameTime,-cur_fly_speed/10)); |
||
734 | |||
735 | auto cam_angles = vm_extract_angles_matrix(endlevel_camera->orient); |
||
736 | cam_angles.b += fixmul(bank_rate,FrameTime); |
||
737 | vm_angles_2_matrix(endlevel_camera->orient,cam_angles); |
||
738 | |||
739 | timer -= FrameTime; |
||
740 | |||
741 | if (timer < 0) { |
||
742 | |||
743 | Endlevel_sequence = EL_STOPPED; |
||
744 | |||
745 | vm_extract_angles_matrix(player_angles,ConsoleObject->orient); |
||
746 | |||
747 | timer = i2f(3); |
||
748 | |||
749 | } |
||
750 | |||
751 | break; |
||
752 | } |
||
753 | |||
754 | case EL_STOPPED: { |
||
755 | |||
756 | get_angs_to_object(player_dest_angles,station_pos,ConsoleObject->pos); |
||
757 | chase_angles(&player_angles,&player_dest_angles); |
||
758 | vm_angles_2_matrix(ConsoleObject->orient,player_angles); |
||
759 | |||
760 | vm_vec_scale_add2(ConsoleObject->pos,ConsoleObject->orient.fvec,fixmul(FrameTime,cur_fly_speed)); |
||
761 | |||
762 | timer -= FrameTime; |
||
763 | |||
764 | if (timer < 0) { |
||
765 | |||
766 | |||
767 | #ifdef SHORT_SEQUENCE |
||
768 | |||
769 | result = std::max(stop_endlevel_sequence(), result); |
||
770 | |||
771 | #else |
||
772 | Endlevel_sequence = EL_PANNING; |
||
773 | |||
774 | vm_extract_angles_matrix(camera_cur_angles,endlevel_camera->orient); |
||
775 | |||
776 | |||
777 | timer = i2f(3); |
||
778 | |||
779 | if (Game_mode & GM_MULTI) { // try to skip part of the seq if multiplayer |
||
780 | result = std::max(stop_endlevel_sequence(), result); |
||
781 | return result; |
||
782 | } |
||
783 | |||
784 | #endif //SHORT_SEQUENCE |
||
785 | |||
786 | } |
||
787 | break; |
||
788 | } |
||
789 | |||
790 | #ifndef SHORT_SEQUENCE |
||
791 | case EL_PANNING: { |
||
792 | int mask; |
||
793 | |||
794 | get_angs_to_object(player_dest_angles,station_pos,ConsoleObject->pos); |
||
795 | chase_angles(&player_angles,&player_dest_angles); |
||
796 | vm_angles_2_matrix(ConsoleObject->orient,player_angles); |
||
797 | vm_vec_scale_add2(ConsoleObject->pos,ConsoleObject->orient.fvec,fixmul(FrameTime,cur_fly_speed)); |
||
798 | |||
799 | |||
800 | get_angs_to_object(camera_desired_angles,ConsoleObject->pos,endlevel_camera->pos); |
||
801 | mask = chase_angles(&camera_cur_angles,&camera_desired_angles); |
||
802 | vm_angles_2_matrix(endlevel_camera->orient,camera_cur_angles); |
||
803 | |||
804 | if ((mask&5) == 5) { |
||
805 | |||
806 | vms_vector tvec; |
||
807 | |||
808 | Endlevel_sequence = EL_CHASING; |
||
809 | |||
810 | vm_vec_normalized_dir_quick(tvec,station_pos,ConsoleObject->pos); |
||
811 | vm_vector_2_matrix(ConsoleObject->orient,tvec,&surface_orient.uvec,nullptr); |
||
812 | |||
813 | desired_fly_speed *= 2; |
||
814 | } |
||
815 | |||
816 | break; |
||
817 | } |
||
818 | |||
819 | case EL_CHASING: { |
||
820 | fix d,speed_scale; |
||
821 | |||
822 | |||
823 | get_angs_to_object(camera_desired_angles,ConsoleObject->pos,endlevel_camera->pos); |
||
824 | chase_angles(&camera_cur_angles,&camera_desired_angles); |
||
825 | |||
826 | vm_angles_2_matrix(endlevel_camera->orient,camera_cur_angles); |
||
827 | |||
828 | d = vm_vec_dist_quick(ConsoleObject->pos,endlevel_camera->pos); |
||
829 | |||
830 | speed_scale = fixdiv(d,i2f(0x20)); |
||
831 | if (d<f1_0) d=f1_0; |
||
832 | |||
833 | get_angs_to_object(player_dest_angles,station_pos,ConsoleObject->pos); |
||
834 | chase_angles(&player_angles,&player_dest_angles); |
||
835 | vm_angles_2_matrix(ConsoleObject->orient,player_angles); |
||
836 | |||
837 | vm_vec_scale_add2(ConsoleObject->pos,ConsoleObject->orient.fvec,fixmul(FrameTime,cur_fly_speed)); |
||
838 | vm_vec_scale_add2(endlevel_camera->pos,endlevel_camera->orient.fvec,fixmul(FrameTime,fixmul(speed_scale,cur_fly_speed))); |
||
839 | |||
840 | if (vm_vec_dist(ConsoleObject->pos,station_pos) < i2f(10)) |
||
841 | result = std::max(stop_endlevel_sequence(), result); |
||
842 | |||
843 | break; |
||
844 | |||
845 | } |
||
846 | #endif //ifdef SHORT_SEQUENCE |
||
847 | |||
848 | } |
||
849 | |||
850 | return result; |
||
851 | } |
||
852 | } |
||
853 | |||
854 | |||
855 | #define MIN_D 0x100 |
||
856 | |||
857 | //find which side to fly out of |
||
858 | static int find_exit_side(const object_base &obj) |
||
859 | { |
||
860 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
861 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
862 | vms_vector prefvec; |
||
863 | fix best_val=-f2_0; |
||
864 | int best_side; |
||
865 | |||
866 | //find exit side |
||
867 | |||
868 | vm_vec_normalized_dir_quick(prefvec, obj.pos, LevelUniqueObjectState.last_console_player_position); |
||
869 | |||
870 | const shared_segment &pseg = *vcsegptr(obj.segnum); |
||
871 | auto &vcvertptr = Vertices.vcptr; |
||
872 | const auto segcenter = compute_segment_center(vcvertptr, pseg); |
||
873 | |||
874 | best_side=-1; |
||
875 | for (int i=MAX_SIDES_PER_SEGMENT;--i >= 0;) { |
||
876 | fix d; |
||
877 | |||
878 | if (pseg.children[i] != segment_none) |
||
879 | { |
||
880 | auto sidevec = compute_center_point_on_side(vcvertptr, pseg, i); |
||
881 | vm_vec_normalized_dir_quick(sidevec,sidevec,segcenter); |
||
882 | d = vm_vec_dot(sidevec,prefvec); |
||
883 | |||
884 | if (labs(d) < MIN_D) d=0; |
||
885 | |||
886 | if (d > best_val) {best_val=d; best_side=i;} |
||
887 | |||
888 | } |
||
889 | } |
||
890 | |||
891 | Assert(best_side!=-1); |
||
892 | |||
893 | return best_side; |
||
894 | } |
||
895 | |||
896 | static void draw_mine_exit_cover(grs_canvas &canvas) |
||
897 | { |
||
898 | const int of = 10; |
||
899 | const fix u = i2f(6), d = i2f(9), ur = i2f(14), dr = i2f(17); |
||
900 | const uint8_t color = BM_XRGB(0, 0, 0); |
||
901 | vms_vector v; |
||
902 | g3s_point p0, p1, p2, p3; |
||
903 | |||
904 | vm_vec_scale_add(v,mine_exit_point,mine_exit_orient.fvec,i2f(of)); |
||
905 | |||
906 | auto mrd = mine_exit_orient.rvec; |
||
907 | { |
||
908 | vms_vector vu; |
||
909 | vm_vec_scale_add(vu, v, mine_exit_orient.uvec, u); |
||
910 | auto mru = mrd; |
||
911 | vm_vec_scale(mru, ur); |
||
912 | vms_vector p; |
||
913 | g3_rotate_point(p0, (vm_vec_add(p, vu, mru), p)); |
||
914 | g3_rotate_point(p1, (vm_vec_sub(p, vu, mru), p)); |
||
915 | } |
||
916 | { |
||
917 | vms_vector vd; |
||
918 | vm_vec_scale_add(vd, v, mine_exit_orient.uvec, -d); |
||
919 | vm_vec_scale(mrd, dr); |
||
920 | vms_vector p; |
||
921 | g3_rotate_point(p2, (vm_vec_sub(p, vd, mrd), p)); |
||
922 | g3_rotate_point(p3, (vm_vec_add(p, vd, mrd), p)); |
||
923 | } |
||
924 | const std::array<cg3s_point *, 4> pointlist{{ |
||
925 | &p0, |
||
926 | &p1, |
||
927 | &p2, |
||
928 | &p3, |
||
929 | }}; |
||
930 | |||
931 | g3_draw_poly(canvas, pointlist.size(), pointlist, color); |
||
932 | } |
||
933 | |||
934 | void draw_exit_model(grs_canvas &canvas) |
||
935 | { |
||
936 | int f=15,u=0; //21; |
||
937 | g3s_lrgb lrgb = { f1_0, f1_0, f1_0 }; |
||
938 | |||
939 | if (mine_destroyed) |
||
940 | { |
||
941 | // draw black shape to mask out terrain in exit hatch |
||
942 | draw_mine_exit_cover(canvas); |
||
943 | } |
||
944 | |||
945 | auto model_pos = vm_vec_scale_add(mine_exit_point,mine_exit_orient.fvec,i2f(f)); |
||
946 | vm_vec_scale_add2(model_pos,mine_exit_orient.uvec,i2f(u)); |
||
947 | draw_polygon_model(canvas, model_pos, mine_exit_orient, nullptr, mine_destroyed ? destroyed_exit_modelnum : exit_modelnum, 0, lrgb, nullptr, nullptr); |
||
948 | } |
||
949 | |||
950 | static int exit_point_bmx,exit_point_bmy; |
||
951 | |||
952 | static fix satellite_size = i2f(400); |
||
953 | |||
954 | #define SATELLITE_DIST i2f(1024) |
||
955 | #define SATELLITE_WIDTH satellite_size |
||
956 | #define SATELLITE_HEIGHT ((satellite_size*9)/4) //((satellite_size*5)/2) |
||
957 | |||
958 | constexpr vms_vector vmd_zero_vector{}; |
||
959 | |||
960 | namespace dsx { |
||
961 | |||
962 | static void render_external_scene(fvcobjptridx &vcobjptridx, grs_canvas &canvas, const d_level_unique_light_state &LevelUniqueLightState, const fix eye_offset) |
||
963 | { |
||
964 | auto &Objects = LevelUniqueObjectState.Objects; |
||
965 | auto &vmobjptridx = Objects.vmptridx; |
||
966 | #if DXX_USE_OGL |
||
967 | int orig_Render_depth = Render_depth; |
||
968 | #endif |
||
969 | g3s_lrgb lrgb = { f1_0, f1_0, f1_0 }; |
||
970 | |||
971 | auto Viewer_eye = Viewer->pos; |
||
972 | |||
973 | if (eye_offset) |
||
974 | vm_vec_scale_add2(Viewer_eye,Viewer->orient.rvec,eye_offset); |
||
975 | |||
976 | g3_set_view_matrix(Viewer->pos,Viewer->orient,Render_zoom); |
||
977 | |||
978 | //g3_draw_horizon(BM_XRGB(0,0,0),BM_XRGB(16,16,16)); //,-1); |
||
979 | gr_clear_canvas(canvas, BM_XRGB(0,0,0)); |
||
980 | |||
981 | g3_start_instance_matrix(vmd_zero_vector, surface_orient); |
||
982 | draw_stars(canvas, UniqueEndlevelState.stars); |
||
983 | g3_done_instance(); |
||
984 | |||
985 | { //draw satellite |
||
986 | |||
987 | vms_vector delta; |
||
988 | g3s_point top_pnt; |
||
989 | |||
990 | const auto p = g3_rotate_point(satellite_pos); |
||
991 | g3_rotate_delta_vec(delta,satellite_upvec); |
||
992 | |||
993 | g3_add_delta_vec(top_pnt,p,delta); |
||
994 | |||
995 | if (! (p.p3_codes & CC_BEHIND)) { |
||
996 | //p.p3_flags &= ~PF_PROJECTED; |
||
997 | //g3_project_point(&p); |
||
998 | if (! (p.p3_flags & PF_OVERFLOW)) { |
||
999 | push_interpolation_method save_im(0); |
||
1000 | //gr_bitmapm(f2i(p.p3_sx)-32,f2i(p.p3_sy)-32,satellite_bitmap); |
||
1001 | g3_draw_rod_tmap(canvas, *satellite_bitmap, p, SATELLITE_WIDTH, top_pnt, SATELLITE_WIDTH, lrgb); |
||
1002 | } |
||
1003 | } |
||
1004 | } |
||
1005 | |||
1006 | #if DXX_USE_OGL |
||
1007 | ogl_toggle_depth_test(0); |
||
1008 | Render_depth = (200-(vm_vec_dist_quick(mine_ground_exit_point, Viewer_eye)/F1_0))/36; |
||
1009 | #endif |
||
1010 | render_terrain(canvas, Viewer_eye, mine_ground_exit_point, exit_point_bmx, exit_point_bmy); |
||
1011 | #if DXX_USE_OGL |
||
1012 | Render_depth = orig_Render_depth; |
||
1013 | ogl_toggle_depth_test(1); |
||
1014 | #endif |
||
1015 | |||
1016 | draw_exit_model(canvas); |
||
1017 | if (ext_expl_playing) |
||
1018 | { |
||
1019 | const auto alpha = PlayerCfg.AlphaBlendMineExplosion; |
||
1020 | if (alpha) // set nice transparency/blending for the big explosion |
||
1021 | gr_settransblend(canvas, GR_FADE_OFF, gr_blend::additive_c); |
||
1022 | draw_fireball(Vclip, canvas, vcobjptridx(external_explosion)); |
||
1023 | #if DXX_USE_OGL |
||
1024 | /* If !OGL, the third argument is discarded, so this call |
||
1025 | * becomes the same as the one above. |
||
1026 | */ |
||
1027 | if (alpha) |
||
1028 | gr_settransblend(canvas, GR_FADE_OFF, gr_blend::normal); // revert any transparency/blending setting back to normal |
||
1029 | #endif |
||
1030 | } |
||
1031 | |||
1032 | #if !DXX_USE_OGL |
||
1033 | Lighting_on=0; |
||
1034 | #endif |
||
1035 | render_object(canvas, LevelUniqueLightState, vmobjptridx(ConsoleObject)); |
||
1036 | #if !DXX_USE_OGL |
||
1037 | Lighting_on=1; |
||
1038 | #endif |
||
1039 | } |
||
1040 | |||
1041 | } |
||
1042 | |||
1043 | static void generate_starfield(d_unique_endlevel_state::starfield_type &stars) |
||
1044 | { |
||
1045 | range_for (auto &i, stars) |
||
1046 | { |
||
1047 | i.x = (d_rand() - D_RAND_MAX / 2) << 14; |
||
1048 | i.z = (d_rand() - D_RAND_MAX / 2) << 14; |
||
1049 | i.y = (d_rand() / 2) << 14; |
||
1050 | } |
||
1051 | } |
||
1052 | |||
1053 | void draw_stars(grs_canvas &canvas, const d_unique_endlevel_state::starfield_type &stars) |
||
1054 | { |
||
1055 | int intensity=31; |
||
1056 | g3s_point p; |
||
1057 | |||
1058 | uint8_t color = 0; |
||
1059 | range_for (auto &&e, enumerate(stars)) |
||
1060 | { |
||
1061 | const auto i = e.idx; |
||
1062 | auto &si = e.value; |
||
1063 | |||
1064 | if ((i&63) == 0) { |
||
1065 | color = BM_XRGB(intensity,intensity,intensity); |
||
1066 | intensity-=3; |
||
1067 | } |
||
1068 | |||
1069 | g3_rotate_delta_vec(p.p3_vec, si); |
||
1070 | g3_code_point(p); |
||
1071 | |||
1072 | if (p.p3_codes == 0) { |
||
1073 | |||
1074 | p.p3_flags &= ~PF_PROJECTED; |
||
1075 | |||
1076 | g3_project_point(p); |
||
1077 | #if !DXX_USE_OGL |
||
1078 | gr_pixel(canvas.cv_bitmap, f2i(p.p3_sx), f2i(p.p3_sy), color); |
||
1079 | #else |
||
1080 | g3_draw_sphere(canvas, p, F1_0 * 3, color); |
||
1081 | #endif |
||
1082 | } |
||
1083 | } |
||
1084 | |||
1085 | //@@ { |
||
1086 | //@@ vms_vector delta; |
||
1087 | //@@ g3s_point top_pnt; |
||
1088 | //@@ |
||
1089 | //@@ g3_rotate_point(&p,&satellite_pos); |
||
1090 | //@@ g3_rotate_delta_vec(&delta,&satellite_upvec); |
||
1091 | //@@ |
||
1092 | //@@ g3_add_delta_vec(&top_pnt,&p,&delta); |
||
1093 | //@@ |
||
1094 | //@@ if (! (p.p3_codes & CC_BEHIND)) { |
||
1095 | //@@ int save_im = Interpolation_method; |
||
1096 | //@@ Interpolation_method = 0; |
||
1097 | //@@ //p.p3_flags &= ~PF_PROJECTED; |
||
1098 | //@@ g3_project_point(&p); |
||
1099 | //@@ if (! (p.p3_flags & PF_OVERFLOW)) |
||
1100 | //@@ //gr_bitmapm(f2i(p.p3_sx)-32,f2i(p.p3_sy)-32,satellite_bitmap); |
||
1101 | //@@ g3_draw_rod_tmap(satellite_bitmap,&p,SATELLITE_WIDTH,&top_pnt,SATELLITE_WIDTH,f1_0); |
||
1102 | //@@ Interpolation_method = save_im; |
||
1103 | //@@ } |
||
1104 | //@@ } |
||
1105 | |||
1106 | } |
||
1107 | |||
1108 | namespace dsx { |
||
1109 | |||
1110 | static void endlevel_render_mine(const d_level_shared_segment_state &LevelSharedSegmentState, grs_canvas &canvas, fix eye_offset) |
||
1111 | { |
||
1112 | auto Viewer_eye = Viewer->pos; |
||
1113 | |||
1114 | if (Viewer->type == OBJ_PLAYER ) |
||
1115 | vm_vec_scale_add2(Viewer_eye,Viewer->orient.fvec,(Viewer->size*3)/4); |
||
1116 | |||
1117 | if (eye_offset) |
||
1118 | vm_vec_scale_add2(Viewer_eye,Viewer->orient.rvec,eye_offset); |
||
1119 | |||
1120 | #if DXX_USE_EDITOR |
||
1121 | if (EditorWindow) |
||
1122 | Viewer_eye = Viewer->pos; |
||
1123 | #endif |
||
1124 | |||
1125 | segnum_t start_seg_num; |
||
1126 | if (Endlevel_sequence >= EL_OUTSIDE) { |
||
1127 | start_seg_num = PlayerUniqueEndlevelState.exit_segnum; |
||
1128 | } |
||
1129 | else { |
||
1130 | start_seg_num = find_point_seg(LevelSharedSegmentState, Viewer_eye, Segments.vcptridx(Viewer->segnum)); |
||
1131 | |||
1132 | if (start_seg_num==segment_none) |
||
1133 | start_seg_num = Viewer->segnum; |
||
1134 | } |
||
1135 | |||
1136 | g3_set_view_matrix(Viewer_eye, Endlevel_sequence == EL_LOOKBACK |
||
1137 | ? vm_matrix_x_matrix(Viewer->orient, vm_angles_2_matrix(vms_angvec{0, 0, INT16_MAX})) |
||
1138 | : Viewer->orient, Render_zoom); |
||
1139 | |||
1140 | window_rendered_data window; |
||
1141 | render_mine(canvas, Viewer_eye, start_seg_num, eye_offset, window); |
||
1142 | } |
||
1143 | |||
1144 | } |
||
1145 | |||
1146 | void render_endlevel_frame(grs_canvas &canvas, fix eye_offset) |
||
1147 | { |
||
1148 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1149 | auto &vcobjptridx = Objects.vcptridx; |
||
1150 | g3_start_frame(canvas); |
||
1151 | |||
1152 | if (Endlevel_sequence < EL_OUTSIDE) |
||
1153 | endlevel_render_mine(LevelSharedSegmentState, canvas, eye_offset); |
||
1154 | else |
||
1155 | render_external_scene(vcobjptridx, canvas, LevelUniqueLightState, eye_offset); |
||
1156 | |||
1157 | g3_end_frame(); |
||
1158 | } |
||
1159 | |||
1160 | ///////////////////////// copy of flythrough code for endlevel |
||
1161 | |||
1162 | |||
1163 | #define DEFAULT_SPEED i2f(16) |
||
1164 | |||
1165 | #define MIN_D 0x100 |
||
1166 | |||
1167 | //if speed is zero, use default speed |
||
1168 | void start_endlevel_flythrough(flythrough_data *flydata,const vmobjptr_t obj,fix speed) |
||
1169 | { |
||
1170 | flydata->obj = obj; |
||
1171 | |||
1172 | flydata->first_time = 1; |
||
1173 | |||
1174 | flydata->speed = speed?speed:DEFAULT_SPEED; |
||
1175 | |||
1176 | flydata->offset_frac = 0; |
||
1177 | } |
||
1178 | |||
1179 | static void angvec_add2_scale(vms_angvec &dest,const vms_vector &src,fix s) |
||
1180 | { |
||
1181 | dest.p += fixmul(src.x,s); |
||
1182 | dest.b += fixmul(src.z,s); |
||
1183 | dest.h += fixmul(src.y,s); |
||
1184 | } |
||
1185 | |||
1186 | #define MAX_ANGSTEP 0x4000 //max turn per second |
||
1187 | |||
1188 | #define MAX_SLIDE_PER_SEGMENT 0x10000 |
||
1189 | |||
1190 | void do_endlevel_flythrough(flythrough_data *flydata) |
||
1191 | { |
||
1192 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
1193 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1194 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
1195 | auto &vmobjptr = Objects.vmptr; |
||
1196 | auto &vmobjptridx = Objects.vmptridx; |
||
1197 | const auto &&obj = vmobjptridx(flydata->obj); |
||
1198 | |||
1199 | vcsegidx_t old_player_seg = obj->segnum; |
||
1200 | |||
1201 | //move the player for this frame |
||
1202 | |||
1203 | if (!flydata->first_time) { |
||
1204 | |||
1205 | vm_vec_scale_add2(obj->pos,flydata->step,FrameTime); |
||
1206 | angvec_add2_scale(flydata->angles,flydata->angstep,FrameTime); |
||
1207 | |||
1208 | vm_angles_2_matrix(obj->orient,flydata->angles); |
||
1209 | } |
||
1210 | |||
1211 | //check new player seg |
||
1212 | |||
1213 | update_object_seg(vmobjptr, LevelSharedSegmentState, LevelUniqueSegmentState, obj); |
||
1214 | const shared_segment &pseg = *vcsegptr(obj->segnum); |
||
1215 | |||
1216 | if (flydata->first_time || obj->segnum != old_player_seg) { //moved into new seg |
||
1217 | fix seg_time; |
||
1218 | short entry_side,exit_side = -1;//what sides we entry and leave through |
||
1219 | int up_side=0; |
||
1220 | |||
1221 | entry_side=0; |
||
1222 | |||
1223 | //find new exit side |
||
1224 | |||
1225 | if (!flydata->first_time) { |
||
1226 | |||
1227 | entry_side = matt_find_connect_side(vcsegptr(obj->segnum), old_player_seg); |
||
1228 | exit_side = Side_opposite[entry_side]; |
||
1229 | } |
||
1230 | |||
1231 | if (flydata->first_time || entry_side == side_none || pseg.children[exit_side] == segment_none) |
||
1232 | exit_side = find_exit_side(obj); |
||
1233 | |||
1234 | { //find closest side to align to |
||
1235 | fix d,largest_d=-f1_0; |
||
1236 | range_for (const int i, xrange(6u)) { |
||
1237 | d = vm_vec_dot(pseg.sides[i].normals[0], flydata->obj->orient.uvec); |
||
1238 | if (d > largest_d) {largest_d = d; up_side=i;} |
||
1239 | } |
||
1240 | |||
1241 | } |
||
1242 | |||
1243 | //update target point & angles |
||
1244 | |||
1245 | //where we are heading (center of exit_side) |
||
1246 | auto &vcvertptr = Vertices.vcptr; |
||
1247 | auto dest_point = compute_center_point_on_side(vcvertptr, pseg, exit_side); |
||
1248 | const vms_vector nextcenter = (pseg.children[exit_side] == segment_exit) |
||
1249 | ? dest_point |
||
1250 | : compute_segment_center(vcvertptr, vcsegptr(pseg.children[exit_side])); |
||
1251 | |||
1252 | //update target point and movement points |
||
1253 | |||
1254 | //offset object sideways |
||
1255 | if (flydata->offset_frac) { |
||
1256 | int s0=-1,s1=0; |
||
1257 | fix dist; |
||
1258 | |||
1259 | range_for (const int i, xrange(6u)) |
||
1260 | if (i!=entry_side && i!=exit_side && i!=up_side && i!=Side_opposite[up_side]) |
||
1261 | { |
||
1262 | if (s0==-1) |
||
1263 | s0 = i; |
||
1264 | else |
||
1265 | s1 = i; |
||
1266 | } |
||
1267 | |||
1268 | const auto &&s0p = compute_center_point_on_side(vcvertptr, pseg, s0); |
||
1269 | const auto &&s1p = compute_center_point_on_side(vcvertptr, pseg, s1); |
||
1270 | dist = fixmul(vm_vec_dist(s0p,s1p),flydata->offset_frac); |
||
1271 | |||
1272 | if (dist-flydata->offset_dist > MAX_SLIDE_PER_SEGMENT) |
||
1273 | dist = flydata->offset_dist + MAX_SLIDE_PER_SEGMENT; |
||
1274 | |||
1275 | flydata->offset_dist = dist; |
||
1276 | |||
1277 | vm_vec_scale_add2(dest_point,obj->orient.rvec,dist); |
||
1278 | |||
1279 | } |
||
1280 | |||
1281 | vm_vec_sub(flydata->step,dest_point,obj->pos); |
||
1282 | auto step_size = vm_vec_normalize_quick(flydata->step); |
||
1283 | vm_vec_scale(flydata->step,flydata->speed); |
||
1284 | |||
1285 | const auto curcenter = compute_segment_center(vcvertptr, pseg); |
||
1286 | vm_vec_sub(flydata->headvec,nextcenter,curcenter); |
||
1287 | |||
1288 | const auto dest_orient = vm_vector_2_matrix(flydata->headvec,&pseg.sides[up_side].normals[0],nullptr); |
||
1289 | //where we want to be pointing |
||
1290 | const auto dest_angles = vm_extract_angles_matrix(dest_orient); |
||
1291 | |||
1292 | if (flydata->first_time) |
||
1293 | vm_extract_angles_matrix(flydata->angles,obj->orient); |
||
1294 | |||
1295 | seg_time = fixdiv(step_size,flydata->speed); //how long through seg |
||
1296 | |||
1297 | if (seg_time) { |
||
1298 | flydata->angstep.x = max(-MAX_ANGSTEP,min(MAX_ANGSTEP,fixdiv(delta_ang(flydata->angles.p,dest_angles.p),seg_time))); |
||
1299 | flydata->angstep.z = max(-MAX_ANGSTEP,min(MAX_ANGSTEP,fixdiv(delta_ang(flydata->angles.b,dest_angles.b),seg_time))); |
||
1300 | flydata->angstep.y = max(-MAX_ANGSTEP,min(MAX_ANGSTEP,fixdiv(delta_ang(flydata->angles.h,dest_angles.h),seg_time))); |
||
1301 | |||
1302 | } |
||
1303 | else { |
||
1304 | flydata->angles = dest_angles; |
||
1305 | flydata->angstep.x = flydata->angstep.y = flydata->angstep.z = 0; |
||
1306 | } |
||
1307 | } |
||
1308 | |||
1309 | flydata->first_time=0; |
||
1310 | } |
||
1311 | |||
1312 | #include "key.h" |
||
1313 | #include "joy.h" |
||
1314 | |||
1315 | |||
1316 | #define LINE_LEN 80 |
||
1317 | #define NUM_VARS 8 |
||
1318 | |||
1319 | #define STATION_DIST i2f(1024) |
||
1320 | |||
1321 | namespace dcx { |
||
1322 | |||
1323 | static int convert_ext(d_fname &dest, const char (&ext)[4]) |
||
1324 | { |
||
1325 | auto b = std::begin(dest); |
||
1326 | auto e = std::end(dest); |
||
1327 | auto t = std::find(b, e, '.'); |
||
1328 | if (t != e && std::distance(b, t) <= 8) |
||
1329 | { |
||
1330 | std::copy(std::begin(ext), std::end(ext), std::next(t)); |
||
1331 | return 1; |
||
1332 | } |
||
1333 | else |
||
1334 | return 0; |
||
1335 | } |
||
1336 | |||
1337 | static std::pair<icsegidx_t, sidenum_fast_t> find_exit_segment_side(fvcsegptridx &vcsegptridx) |
||
1338 | { |
||
1339 | range_for (const auto &&segp, vcsegptridx) |
||
1340 | { |
||
1341 | range_for (const auto &&e, enumerate(segp->children)) |
||
1342 | { |
||
1343 | const auto child_segnum = e.value; |
||
1344 | if (child_segnum == segment_exit) |
||
1345 | { |
||
1346 | const auto sidenum = e.idx; |
||
1347 | return {segp, sidenum}; |
||
1348 | } |
||
1349 | } |
||
1350 | } |
||
1351 | return {segment_none, side_none}; |
||
1352 | } |
||
1353 | |||
1354 | } |
||
1355 | |||
1356 | //called for each level to load & setup the exit sequence |
||
1357 | namespace dsx { |
||
1358 | void load_endlevel_data(int level_num) |
||
1359 | { |
||
1360 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
1361 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
1362 | d_fname filename; |
||
1363 | char *p; |
||
1364 | int var; |
||
1365 | int have_binary = 0; |
||
1366 | |||
1367 | endlevel_data_loaded = 0; //not loaded yet |
||
1368 | |||
1369 | try_again: |
||
1370 | ; |
||
1371 | |||
1372 | if (level_num<0) //secret level |
||
1373 | filename = Secret_level_names[-level_num-1]; |
||
1374 | else //normal level |
||
1375 | filename = Level_names[level_num-1]; |
||
1376 | |||
1377 | #if defined(DXX_BUILD_DESCENT_I) |
||
1378 | if (!convert_ext(filename,"end")) |
||
1379 | return; |
||
1380 | #elif defined(DXX_BUILD_DESCENT_II) |
||
1381 | if (!convert_ext(filename,"END")) |
||
1382 | Error("Error converting filename <%s> for endlevel data\n",static_cast<const char *>(filename)); |
||
1383 | #endif |
||
1384 | |||
1385 | auto ifile = PHYSFSX_openReadBuffered(filename); |
||
1386 | |||
1387 | if (!ifile) { |
||
1388 | |||
1389 | convert_ext(filename,"txb"); |
||
1390 | if (!strcmp(filename, Briefing_text_filename) || |
||
1391 | !strcmp(filename, Ending_text_filename)) |
||
1392 | return; // Don't want to interpret the briefing as an end level sequence! |
||
1393 | |||
1394 | ifile = PHYSFSX_openReadBuffered(filename); |
||
1395 | |||
1396 | if (!ifile) { |
||
1397 | if (level_num==1) { |
||
1398 | #if defined(DXX_BUILD_DESCENT_II) |
||
1399 | con_printf(CON_DEBUG, "Cannot load file text of binary version of <%s>",static_cast<const char *>(filename)); |
||
1400 | endlevel_data_loaded = 0; // won't be able to play endlevel sequence |
||
1401 | #endif |
||
1402 | return; |
||
1403 | } |
||
1404 | else { |
||
1405 | level_num = 1; |
||
1406 | goto try_again; |
||
1407 | } |
||
1408 | } |
||
1409 | |||
1410 | have_binary = 1; |
||
1411 | } |
||
1412 | |||
1413 | //ok...this parser is pretty simple. It ignores comments, but |
||
1414 | //everything else must be in the right place |
||
1415 | |||
1416 | var = 0; |
||
1417 | |||
1418 | PHYSFSX_gets_line_t<LINE_LEN> line; |
||
1419 | while (PHYSFSX_fgets(line,ifile)) { |
||
1420 | |||
1421 | if (have_binary) |
||
1422 | decode_text_line (line); |
||
1423 | |||
1424 | if ((p=strchr(line,';'))!=NULL) |
||
1425 | *p = 0; //cut off comment |
||
1426 | |||
1427 | for (p = line; isspace(static_cast<unsigned>(*p)); ++p) |
||
1428 | ; |
||
1429 | if (!*p) //empty line |
||
1430 | continue; |
||
1431 | auto ns = p; |
||
1432 | for (auto p2 = p; *p2; ++p2) |
||
1433 | if (!isspace(static_cast<unsigned>(*p2))) |
||
1434 | ns = p2; |
||
1435 | *++ns = 0; |
||
1436 | |||
1437 | switch (var) { |
||
1438 | |||
1439 | case 0: { //ground terrain |
||
1440 | int iff_error; |
||
1441 | palette_array_t pal; |
||
1442 | terrain_bm_instance.reset(); |
||
1443 | iff_error = iff_read_bitmap(p, terrain_bm_instance, &pal); |
||
1444 | if (iff_error != IFF_NO_ERROR) { |
||
1445 | con_printf(CON_DEBUG, "Can't load exit terrain from file %s: IFF error: %s", |
||
1446 | p, iff_errormsg(iff_error)); |
||
1447 | endlevel_data_loaded = 0; // won't be able to play endlevel sequence |
||
1448 | return; |
||
1449 | } |
||
1450 | terrain_bitmap = &terrain_bm_instance; |
||
1451 | gr_remap_bitmap_good(terrain_bm_instance, pal, iff_transparent_color, -1); |
||
1452 | break; |
||
1453 | } |
||
1454 | |||
1455 | case 1: //height map |
||
1456 | |||
1457 | load_terrain(p); |
||
1458 | break; |
||
1459 | |||
1460 | |||
1461 | case 2: |
||
1462 | |||
1463 | sscanf(p,"%d,%d",&exit_point_bmx,&exit_point_bmy); |
||
1464 | break; |
||
1465 | |||
1466 | case 3: //exit heading |
||
1467 | |||
1468 | exit_angles.h = i2f(atoi(p))/360; |
||
1469 | break; |
||
1470 | |||
1471 | case 4: { //planet bitmap |
||
1472 | int iff_error; |
||
1473 | palette_array_t pal; |
||
1474 | satellite_bm_instance.reset(); |
||
1475 | iff_error = iff_read_bitmap(p, satellite_bm_instance, &pal); |
||
1476 | if (iff_error != IFF_NO_ERROR) { |
||
1477 | con_printf(CON_DEBUG, "Can't load exit satellite from file %s: IFF error: %s", |
||
1478 | p, iff_errormsg(iff_error)); |
||
1479 | endlevel_data_loaded = 0; // won't be able to play endlevel sequence |
||
1480 | return; |
||
1481 | } |
||
1482 | |||
1483 | satellite_bitmap = &satellite_bm_instance; |
||
1484 | gr_remap_bitmap_good(satellite_bm_instance, pal, iff_transparent_color, -1); |
||
1485 | |||
1486 | break; |
||
1487 | } |
||
1488 | |||
1489 | case 5: //earth pos |
||
1490 | case 7: { //station pos |
||
1491 | vms_angvec ta; |
||
1492 | int pitch,head; |
||
1493 | |||
1494 | sscanf(p,"%d,%d",&head,&pitch); |
||
1495 | |||
1496 | ta.h = i2f(head)/360; |
||
1497 | ta.p = -i2f(pitch)/360; |
||
1498 | ta.b = 0; |
||
1499 | |||
1500 | const auto &&tm = vm_angles_2_matrix(ta); |
||
1501 | |||
1502 | if (var==5) |
||
1503 | satellite_pos = tm.fvec; |
||
1504 | //vm_vec_copy_scale(&satellite_pos,&tm.fvec,SATELLITE_DIST); |
||
1505 | else |
||
1506 | station_pos = tm.fvec; |
||
1507 | |||
1508 | break; |
||
1509 | } |
||
1510 | |||
1511 | case 6: //planet size |
||
1512 | satellite_size = i2f(atoi(p)); |
||
1513 | break; |
||
1514 | } |
||
1515 | |||
1516 | var++; |
||
1517 | |||
1518 | } |
||
1519 | |||
1520 | Assert(var == NUM_VARS); |
||
1521 | |||
1522 | |||
1523 | // OK, now the data is loaded. Initialize everything |
||
1524 | |||
1525 | //find the exit sequence by searching all segments for a side with |
||
1526 | //children == -2 |
||
1527 | |||
1528 | const auto &&exit_segside = find_exit_segment_side(vcsegptridx); |
||
1529 | const icsegidx_t &exit_segnum = exit_segside.first; |
||
1530 | const auto &exit_side = exit_segside.second; |
||
1531 | |||
1532 | PlayerUniqueEndlevelState.exit_segnum = exit_segnum; |
||
1533 | if (exit_segnum == segment_none) |
||
1534 | return; |
||
1535 | |||
1536 | const auto &&exit_seg = vmsegptr(exit_segnum); |
||
1537 | auto &vcvertptr = Vertices.vcptr; |
||
1538 | compute_segment_center(vcvertptr, mine_exit_point, exit_seg); |
||
1539 | extract_orient_from_segment(vcvertptr, mine_exit_orient, exit_seg); |
||
1540 | compute_center_point_on_side(vcvertptr, mine_side_exit_point, exit_seg, exit_side); |
||
1541 | |||
1542 | vm_vec_scale_add(mine_ground_exit_point,mine_exit_point,mine_exit_orient.uvec,-i2f(20)); |
||
1543 | |||
1544 | //compute orientation of surface |
||
1545 | { |
||
1546 | auto &&exit_orient = vm_angles_2_matrix(exit_angles); |
||
1547 | vm_transpose_matrix(exit_orient); |
||
1548 | vm_matrix_x_matrix(surface_orient,mine_exit_orient,exit_orient); |
||
1549 | |||
1550 | vms_matrix tm = vm_transposed_matrix(surface_orient); |
||
1551 | const auto tv0 = vm_vec_rotate(station_pos,tm); |
||
1552 | vm_vec_scale_add(station_pos,mine_exit_point,tv0,STATION_DIST); |
||
1553 | |||
1554 | const auto tv = vm_vec_rotate(satellite_pos,tm); |
||
1555 | vm_vec_scale_add(satellite_pos,mine_exit_point,tv,SATELLITE_DIST); |
||
1556 | |||
1557 | const auto tm2 = vm_vector_2_matrix(tv,&surface_orient.uvec,nullptr); |
||
1558 | vm_vec_copy_scale(satellite_upvec,tm2.uvec,SATELLITE_HEIGHT); |
||
1559 | |||
1560 | |||
1561 | } |
||
1562 | endlevel_data_loaded = 1; |
||
1563 | } |
||
1564 | } |