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