mirror of
https://github.com/Cesarus85/ringfall-mjolnir-vr.git
synced 2026-10-17 19:00:25 +02:00
The player's own biped is drawn in first person among the world's objects (it sorts against the world by depth; the first-person weapon and arms, drawn first into the stencil, stay over it), posed from a copy of its node matrices once a frame (port/linux/game/vr_body.c): turned to the torso's heading, the neck below and behind the eyes as the head turns about it, the hips down with the physical crouch and part of the way with a lean, the spine upright to the neck, the feet planted while standing and stepping after the body as it turns, the knees bent by the arms' two-bone IK. The head and the third-person arms are shrunk to nothing, the weapon and what the biped carries left out; the first-person arms hang from the body's shoulders (mirrored with the weapon left-handed). A passenger in an open vehicle sits as the seat's animation has it; at a wheel or a gun the body is drawn as before, in a closed seat none. Nothing of the game's own pose, hit boxes or network messages changes. The neck pivot, the planted feet and the leg's clamped reach follow HaloCE-Quest-VR (CC0), rebuilt for this port's skeleton and caches. vr.body gets "full" (VR options, Body: "Full body"); "arms" stays the default. vr.log_performance reports the body's pose. debug.vr_body shows the body flat (the emulator): from the eyes, looking down, or from the front, side or back, the head turning, crouching or leaning on its own. Checked in the emulator: the cyborg's 19 nodes found, the body posed standing, crouched, leaning and turning (feet stepping), 0.006 ms a pose; two-minute bot games on Blood Gulch with it on and off, no errors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1048 lines
31 KiB
C
1048 lines
31 KiB
C
/*
|
|
RENDER.C
|
|
|
|
symbols in this file:
|
|
001743B0 0020:
|
|
_render_effects (0000)
|
|
001743D0 0010:
|
|
_render_initialize (0000)
|
|
001743E0 0010:
|
|
_render_initialize_for_new_map (0000)
|
|
001743F0 0010:
|
|
_render_dispose_from_old_map (0000)
|
|
00174400 0010:
|
|
_render_dispose (0000)
|
|
00174410 0110:
|
|
_render_nonplayer_frame (0000)
|
|
00174520 00f0:
|
|
_render_frame_pregame (0000)
|
|
00174610 0020:
|
|
_render_frame_present (0000)
|
|
00174630 0070:
|
|
_render_location_visible (0000)
|
|
001746A0 0050:
|
|
_rendered_cluster_get (0000)
|
|
001746F0 03f0:
|
|
_render_window (0000)
|
|
00174AE0 03f0:
|
|
_render_player_frame (0000)
|
|
00174ED0 00f0:
|
|
_render_frame (0000)
|
|
0029F44C 001f:
|
|
??_C@_0BP@JNDFGHKA@c?3?2halo?2SOURCE?2render?2render?4c?$AA@ (0000)
|
|
0029F470 0061:
|
|
??_C@_0GB@NHJDHKGO@location?9?$DOcluster_index?$DO?$DN0?5?$CG?$CG?5lo@ (0000)
|
|
0029F4D8 0052:
|
|
??_C@_0FC@DLFICDJP@rendered_cluster_index?$DO?$DN0?5?$CG?$CG?5ren@ (0000)
|
|
0029F530 0052:
|
|
??_C@_0FC@NBMIPGBN@window?9?$DOrender_camera?4viewport_b@ (0000)
|
|
0029F588 0051:
|
|
??_C@_0FB@IDPOBHAJ@window?9?$DOrender_camera?4viewport_b@ (0000)
|
|
0029F5DC 002c:
|
|
??_C@_0CM@LKHLGDKF@window?9?$DOrender_camera?4viewport_b@ (0000)
|
|
0029F608 002c:
|
|
??_C@_0CM@HBCHLAAA@window?9?$DOrender_camera?4viewport_b@ (0000)
|
|
0029F638 006d:
|
|
??_C@_0GN@PKELCNNH@?$CBmemcmp?$CI?$CGwindow?9?$DOrender_camera?4w@ (0000)
|
|
0029F6A8 0071:
|
|
??_C@_0HB@BCLIBPGM@?$CBmemcmp?$CI?$CGwindow?9?$DOrender_camera?4v@ (0000)
|
|
0029F720 0048:
|
|
??_C@_0EI@NGOLLMDP@?$CD?$CD?$CD?5ERROR?5something?5is?5wrong?5wit@ (0000)
|
|
0030D4D2 0004:
|
|
_render_contrails_enabled (0000)
|
|
_render_particles_enabled (0001)
|
|
_render_particle_systems_enabled (0002)
|
|
_render_weather_particle_systems_enabled (0003)
|
|
004B8B22 0001:
|
|
_render_invalid_fog_warning_displayed (0000)
|
|
*/
|
|
|
|
/* ---------- headers */
|
|
|
|
#include "cseries.h"
|
|
#include "cseries/errors.h"
|
|
#include "render.h"
|
|
#include "render_cameras_internal.h"
|
|
#include "render_particles.h"
|
|
#include "objects.h"
|
|
#include "render_sprite.h"
|
|
#include "scenario.h"
|
|
#include "structure_bsp_definitions.h"
|
|
#include "effects/player_effects.h"
|
|
#include "structures/structure_visibility.h"
|
|
#include "rasterizer.h"
|
|
#include "rasterizer/rasterizer_lights.h"
|
|
#include "profile.h"
|
|
#include "progress_bar.h"
|
|
#include "ui_widget.h"
|
|
#include "bink_playback.h"
|
|
#include "game.h"
|
|
#include "game_engine.h"
|
|
#include "interface/first_person_weapons.h"
|
|
#include "interface/interface.h"
|
|
#include "editor_stubs.h"
|
|
#include "render_debug.h"
|
|
#include "objects/object_lights_rendering.h"
|
|
#include "effects/particle_systems.h"
|
|
#include "effects/weather_particle_systems.h"
|
|
#ifdef HALO_ANDROID
|
|
#include "cutscene/cinematics.h"
|
|
#include "game/players.h"
|
|
#include "interface/hud.h"
|
|
#include "physics/collisions.h"
|
|
#include "units/units.h"
|
|
#include "halo_vr.h"
|
|
|
|
/* port/linux/game/vr_graphics.c */
|
|
void vr_graphics_update(void);
|
|
/* port/linux/game/vr_body.c: debug.vr_body, flat */
|
|
boolean vr_body_debug_camera(short local_player_index, struct render_camera *render_camera,
|
|
struct render_camera *rasterizer_camera);
|
|
#endif
|
|
#include "main/main.h"
|
|
#include "structures/structures.h"
|
|
/* port: static transparent part links for the newly loaded model tags. */
|
|
#include "models/models.h"
|
|
|
|
/* ---------- constants */
|
|
|
|
enum
|
|
{
|
|
_render_target_primary = 0,
|
|
_render_target_secondary,
|
|
};
|
|
|
|
enum
|
|
{
|
|
_decal_layer_primary = 0,
|
|
_decal_layer_secondary,
|
|
_decal_layer_light,
|
|
_decal_layer_alpha_tested,
|
|
_decal_layer_water,
|
|
};
|
|
|
|
enum
|
|
{
|
|
_render_planar_fog_mode_fully_fogged = 2,
|
|
};
|
|
|
|
/* ---------- macros */
|
|
|
|
#define RASTERIZER_TARGET_RENDER_PRIMARY_WIDTH halo_screen_width()
|
|
#define RASTERIZER_TARGET_RENDER_PRIMARY_HEIGHT 480
|
|
|
|
/* ---------- structures */
|
|
|
|
/* ---------- prototypes */
|
|
|
|
static void render_nonplayer_frame(
|
|
const struct render_window *window,
|
|
long window_type);
|
|
static void render_window(
|
|
short local_player_index,
|
|
const struct render_camera *camera,
|
|
const struct render_frustum *frustum,
|
|
const struct render_camera *rasterizer_camera,
|
|
const struct render_frustum *rasterizer_frustum,
|
|
short rasterizer_target,
|
|
boolean has_mirror);
|
|
static void render_player_frame(
|
|
struct render_window *window,
|
|
const point2d *screenshot_combined_index);
|
|
#ifdef HALO_ANDROID
|
|
static void render_vr_player_frame(
|
|
struct render_window *window);
|
|
static void render_vr_overlay(
|
|
const struct render_window *window);
|
|
static void render_vr_laser(
|
|
short local_player_index);
|
|
static void render_vr_scope(
|
|
struct render_window *window);
|
|
static boolean render_vr_body_debug_frame(
|
|
struct render_window *window);
|
|
#endif
|
|
|
|
void render_sky(
|
|
void);
|
|
|
|
/* ---------- globals */
|
|
|
|
struct render_globals render;
|
|
|
|
static boolean render_invalid_fog_warning_displayed;
|
|
|
|
extern short global_screenshot_count;
|
|
|
|
boolean render_contrails_enabled = TRUE;
|
|
boolean render_particles_enabled = TRUE;
|
|
boolean render_particle_systems_enabled = TRUE;
|
|
boolean render_weather_particle_systems_enabled = TRUE;
|
|
|
|
#ifdef HALO_ANDROID
|
|
/* VR (render_vr_player_frame): drawing an eye, which leaves out the HUD and
|
|
the menus, and that eye's frustum bounds */
|
|
static boolean render_vr_eye_pass = FALSE;
|
|
static const real_rectangle2d *render_vr_frustum_bounds = NULL;
|
|
#endif
|
|
|
|
/* ---------- public code */
|
|
|
|
void render_effects(
|
|
boolean enable)
|
|
{
|
|
render_weather_particle_systems_enabled = enable;
|
|
render_particle_systems_enabled = enable;
|
|
render_particles_enabled = enable;
|
|
render_contrails_enabled = enable;
|
|
}
|
|
|
|
void render_initialize(
|
|
void)
|
|
{
|
|
render_objects_initialize();
|
|
}
|
|
|
|
void render_initialize_for_new_map(
|
|
void)
|
|
{
|
|
/* port: preserve retail's energy-then-two-sided-glass compositing. */
|
|
models_fix_transparent_part_links();
|
|
render_objects_initialize_for_new_map();
|
|
}
|
|
|
|
void render_dispose_from_old_map(
|
|
void)
|
|
{
|
|
render_objects_dispose_from_old_map();
|
|
}
|
|
|
|
void render_dispose(
|
|
void)
|
|
{
|
|
render_objects_dispose();
|
|
}
|
|
|
|
static void render_nonplayer_frame(
|
|
const struct render_window *window,
|
|
long window_type)
|
|
{
|
|
struct rasterizer_window_begin_parameters parameters;
|
|
|
|
profile_render_window_start(FALSE);
|
|
memset(¶meters, 0, sizeof(parameters));
|
|
|
|
render.camera = window->render_camera;
|
|
render_camera_build_frustum(&render.camera, NULL, &render.frustum, TRUE);
|
|
|
|
parameters.camera = window->rasterizer_camera;
|
|
render_camera_build_frustum(¶meters.camera, NULL, ¶meters.frustum, TRUE);
|
|
|
|
parameters.rasterizer_target = _render_target_primary;
|
|
parameters.suppress_clear = window_type == 0;
|
|
parameters.window_index = NONE;
|
|
rasterizer_window_begin(¶meters);
|
|
|
|
switch (window_type)
|
|
{
|
|
case 0:
|
|
/* letterbox bars and split screen dividers: laid out from the
|
|
viewport, so not centered like the menus on a wide screen */
|
|
interface_draw_fullscreen_overlays();
|
|
rasterizer_debug_draw();
|
|
break;
|
|
|
|
case 1:
|
|
halo_screen_ui_offset(TRUE);
|
|
game_engine_nonplayer_post_rasterize();
|
|
halo_screen_ui_offset(FALSE);
|
|
break;
|
|
|
|
default:
|
|
match_assert("c:\\halo\\SOURCE\\render\\render.c", 287, !"unreachable");
|
|
break;
|
|
}
|
|
|
|
rasterizer_window_end();
|
|
profile_render_window_end();
|
|
|
|
return;
|
|
}
|
|
|
|
void render_frame_pregame(
|
|
struct render_window const *window,
|
|
struct bitmap_data *bitmap)
|
|
{
|
|
struct rasterizer_frame_begin_parameters parameters;
|
|
struct rasterizer_window_begin_parameters rasterizer_parameters;
|
|
|
|
render.frame_index++;
|
|
|
|
rasterizer_frame_begin(¶meters);
|
|
rasterizer_windows_begin();
|
|
profile_render_window_start(FALSE);
|
|
|
|
memset(&rasterizer_parameters, 0, sizeof(rasterizer_parameters));
|
|
|
|
render.camera = window->render_camera;
|
|
render_camera_build_frustum(&render.camera, NULL, &render.frustum, TRUE);
|
|
|
|
rasterizer_parameters.camera = window->rasterizer_camera;
|
|
render_camera_build_frustum(&rasterizer_parameters.camera, NULL, &rasterizer_parameters.frustum, TRUE);
|
|
|
|
rasterizer_parameters.rasterizer_target = 0;
|
|
rasterizer_window_begin(&rasterizer_parameters);
|
|
|
|
halo_screen_ui_offset(TRUE);
|
|
render_ui_widgets(0, &window->rasterizer_camera.viewport_bounds);
|
|
halo_screen_ui_offset(FALSE);
|
|
bink_playback_render();
|
|
|
|
{
|
|
real progress;
|
|
if (game_map_loading_in_progress(&progress))
|
|
{
|
|
progress_bar_display(progress);
|
|
}
|
|
}
|
|
|
|
rasterizer_window_end();
|
|
profile_render_window_end();
|
|
rasterizer_windows_end();
|
|
rasterizer_frame_end();
|
|
|
|
return;
|
|
}
|
|
|
|
void render_frame_present(
|
|
const point2d *screenshot_index,
|
|
struct bitmap_data *bitmap)
|
|
{
|
|
rasterizer_present(bitmap, screenshot_index);
|
|
|
|
return;
|
|
}
|
|
|
|
boolean render_location_visible(
|
|
struct location *location)
|
|
{
|
|
match_assert("c:\\halo\\SOURCE\\render\\render.c", 584, location->cluster_index>=0 && location->cluster_index<global_structure_bsp_get()->clusters.count);
|
|
return TEST_FLAG(render.visible_cluster_flags[location->cluster_index>>5], location->cluster_index&31);
|
|
}
|
|
|
|
struct rendered_cluster *rendered_cluster_get(
|
|
short rendered_cluster_index)
|
|
{
|
|
match_assert("c:\\halo\\SOURCE\\render\\render.c", 592, rendered_cluster_index>=0 && rendered_cluster_index<render.rendered_cluster_count);
|
|
return &render.rendered_clusters[rendered_cluster_index];
|
|
}
|
|
|
|
static void render_window(
|
|
short local_player_index,
|
|
const struct render_camera *camera,
|
|
const struct render_frustum *frustum,
|
|
const struct render_camera *rasterizer_camera,
|
|
const struct render_frustum *rasterizer_frustum,
|
|
short rasterizer_target,
|
|
boolean has_mirror)
|
|
{
|
|
struct rasterizer_window_begin_parameters parameters;
|
|
short rendered_cluster_index;
|
|
|
|
profile_render_window_start(TRUE);
|
|
render.scene_index++;
|
|
memset(¶meters, 0, sizeof(parameters));
|
|
|
|
render.local_player_index = local_player_index;
|
|
render.camera = *camera;
|
|
render.frustum = *frustum;
|
|
parameters.camera = *rasterizer_camera;
|
|
parameters.frustum = *rasterizer_frustum;
|
|
parameters.rasterizer_target = rasterizer_target;
|
|
parameters.has_mirror = has_mirror;
|
|
parameters.window_index = render.window_index;
|
|
parameters.fog = render.fog;
|
|
|
|
structure_visibility_compute();
|
|
player_effect_get_screen_flash(local_player_index, ¶meters.screen_flash);
|
|
rasterizer_window_begin(¶meters);
|
|
|
|
if (!bink_playback_in_progress())
|
|
{
|
|
build_sprite_prepare_for_window();
|
|
#ifdef HALO_ANDROID
|
|
halo_vr_multiview_sky(TRUE);
|
|
#endif
|
|
render_sky();
|
|
#ifdef HALO_ANDROID
|
|
halo_vr_multiview_sky(FALSE);
|
|
#endif
|
|
first_person_weapon_render_update();
|
|
lights_preprocess_scene();
|
|
render_objects();
|
|
structure_render_preprocess();
|
|
structure_render_lightmaps();
|
|
rasterizer_lens_flares_submit_occlusion_tests();
|
|
render_object_shadows();
|
|
lights_render_diffuse();
|
|
|
|
rasterizer_decals_begin(_decal_layer_light);
|
|
for (rendered_cluster_index = 0;
|
|
rendered_cluster_index < render.rendered_cluster_count;
|
|
rendered_cluster_index++)
|
|
{
|
|
rasterizer_decals_draw(rendered_cluster_get(rendered_cluster_index)->cluster_index);
|
|
}
|
|
rasterizer_decals_end();
|
|
|
|
rasterizer_decals_begin(_decal_layer_alpha_tested);
|
|
for (rendered_cluster_index = 0;
|
|
rendered_cluster_index < render.rendered_cluster_count;
|
|
rendered_cluster_index++)
|
|
{
|
|
rasterizer_decals_draw(rendered_cluster_get(rendered_cluster_index)->cluster_index);
|
|
}
|
|
rasterizer_decals_end();
|
|
|
|
structure_render_diffuse_texture();
|
|
|
|
rasterizer_decals_begin(_decal_layer_primary);
|
|
for (rendered_cluster_index = 0;
|
|
rendered_cluster_index < render.rendered_cluster_count;
|
|
rendered_cluster_index++)
|
|
{
|
|
rasterizer_decals_draw(rendered_cluster_get(rendered_cluster_index)->cluster_index);
|
|
}
|
|
rasterizer_decals_end();
|
|
|
|
rasterizer_decals_begin(_decal_layer_secondary);
|
|
for (rendered_cluster_index = 0;
|
|
rendered_cluster_index < render.rendered_cluster_count;
|
|
rendered_cluster_index++)
|
|
{
|
|
rasterizer_decals_draw(rendered_cluster_get(rendered_cluster_index)->cluster_index);
|
|
}
|
|
rasterizer_decals_end();
|
|
|
|
lights_render_specular();
|
|
structure_render_specular_lightmaps();
|
|
structure_render_reflection_lightmap_masks();
|
|
structure_render_reflection_mirrors();
|
|
structure_render_reflections();
|
|
structure_render_transparent_geometry();
|
|
structure_render_fog();
|
|
game_engine_post_rasterize_objects();
|
|
weather_particle_systems_render();
|
|
render_particles();
|
|
particle_systems_render();
|
|
render_contrails_normal();
|
|
rasterizer_transparent_geometry_draw(TRUE);
|
|
|
|
rasterizer_decals_begin(_decal_layer_water);
|
|
for (rendered_cluster_index = 0;
|
|
rendered_cluster_index < render.rendered_cluster_count;
|
|
rendered_cluster_index++)
|
|
{
|
|
rasterizer_decals_draw(rendered_cluster_get(rendered_cluster_index)->cluster_index);
|
|
}
|
|
rasterizer_decals_end();
|
|
|
|
structure_render_detail_objects();
|
|
rasterizer_transparent_geometry_draw(FALSE);
|
|
rasterizer_transparent_geometry_stop();
|
|
structure_render_fog_screen();
|
|
rasterizer_lens_flares_draw();
|
|
#ifdef HALO_ANDROID
|
|
if (render_vr_eye_pass)
|
|
{
|
|
/* the HUD and the menus go on the overlay (render_vr_overlay);
|
|
no antialiasing pass on the eyes' pictures */
|
|
rasterizer_screen_flash();
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
/* port: the 3D view antialiased (display.anti_aliasing), before the
|
|
HUD and menus are drawn over it */
|
|
if (rasterizer_target == _render_target_primary)
|
|
{
|
|
halo_screen_anti_alias(
|
|
rasterizer_camera->viewport_bounds.x0,
|
|
rasterizer_camera->viewport_bounds.y0,
|
|
rasterizer_camera->viewport_bounds.x1,
|
|
rasterizer_camera->viewport_bounds.y1);
|
|
}
|
|
interface_draw_screen();
|
|
rasterizer_screen_flash();
|
|
halo_screen_ui_offset(TRUE);
|
|
render_ui_widgets(local_player_index, &rasterizer_camera->viewport_bounds);
|
|
halo_screen_ui_offset(FALSE);
|
|
}
|
|
}
|
|
|
|
bink_playback_render();
|
|
#ifdef HALO_ANDROID
|
|
if (render_vr_eye_pass)
|
|
render_vr_laser(local_player_index);
|
|
#endif
|
|
render_camera_debug_frustum(&render.camera, &render.frustum);
|
|
render_debug();
|
|
editor_render();
|
|
rasterizer_debug_draw();
|
|
rasterizer_window_end();
|
|
profile_render_window_end();
|
|
|
|
return;
|
|
}
|
|
|
|
static void render_player_frame(
|
|
struct render_window *window,
|
|
const point2d *screenshot_combined_index)
|
|
{
|
|
struct render_camera *camera;
|
|
boolean has_mirror;
|
|
real_rectangle2d frustum_bounds;
|
|
struct render_frustum frustum;
|
|
struct render_frustum rasterizer_frustum;
|
|
struct render_mirror mirror;
|
|
|
|
camera = &window->render_camera;
|
|
has_mirror = FALSE;
|
|
|
|
structure_visibility_find_camera(camera);
|
|
render.fog.runtime_flags = 0;
|
|
scenario_get_atmospheric_fog(
|
|
window->local_player_index,
|
|
(word)render.visible_sky_index,
|
|
&camera->position,
|
|
&render.fog);
|
|
structure_get_planar_fog((short)render.cluster_index, &render.fog);
|
|
|
|
if (render.fog.atmospheric_maximum_distance != 0.0f &&
|
|
render.visible_sky_index == NONE &&
|
|
render.fog.planar_maximum_distance > render.fog.atmospheric_maximum_distance)
|
|
{
|
|
render.fog.planar_maximum_distance = render.fog.atmospheric_maximum_distance;
|
|
}
|
|
|
|
if (render.fog.atmospheric_maximum_density == 1.0f &&
|
|
render.fog.atmospheric_maximum_distance != 0.0f)
|
|
{
|
|
window->render_camera.z_far = MIN(
|
|
window->render_camera.z_far,
|
|
render.fog.atmospheric_maximum_distance);
|
|
}
|
|
|
|
if (render.fog.planar_mode == _render_planar_fog_mode_fully_fogged &&
|
|
render.fog.planar_maximum_distance != 0.0f)
|
|
{
|
|
window->render_camera.z_far = MIN(
|
|
window->render_camera.z_far,
|
|
render.fog.planar_maximum_distance);
|
|
}
|
|
|
|
if (window->render_camera.z_far <= window->render_camera.z_near)
|
|
{
|
|
if (!render_invalid_fog_warning_displayed)
|
|
{
|
|
error(2, "### ERROR something is wrong with the fog in the sky tag or the fog tag");
|
|
render_invalid_fog_warning_displayed = TRUE;
|
|
}
|
|
|
|
window->render_camera.z_far = window->render_camera.z_near + 0.01f;
|
|
}
|
|
|
|
match_assert(
|
|
"c:\\halo\\SOURCE\\render\\render.c",
|
|
187,
|
|
!memcmp(&window->render_camera.viewport_bounds,
|
|
&window->rasterizer_camera.viewport_bounds,
|
|
sizeof(rectangle2d)));
|
|
match_assert(
|
|
"c:\\halo\\SOURCE\\render\\render.c",
|
|
188,
|
|
!memcmp(&window->render_camera.window_bounds,
|
|
&window->rasterizer_camera.window_bounds,
|
|
sizeof(rectangle2d)));
|
|
|
|
render_camera_build_frustum_bounds(camera, &frustum_bounds);
|
|
#ifdef HALO_ANDROID
|
|
if (render_vr_frustum_bounds)
|
|
frustum_bounds = *render_vr_frustum_bounds;
|
|
#endif
|
|
|
|
if (screenshot_combined_index != NULL)
|
|
{
|
|
long tile_count;
|
|
|
|
tile_count = global_screenshot_count * global_screenshot_size;
|
|
if (tile_count > 0)
|
|
{
|
|
real tile_width;
|
|
real tile_height;
|
|
real_rectangle2d adjusted_bounds;
|
|
|
|
tile_width = (frustum_bounds.x1 - frustum_bounds.x0) / (real)tile_count;
|
|
tile_height = (frustum_bounds.y1 - frustum_bounds.y0) / (real)tile_count;
|
|
adjusted_bounds.x0 =
|
|
(real)screenshot_combined_index->x * tile_width + frustum_bounds.x0;
|
|
adjusted_bounds.y0 =
|
|
(real)(tile_count - screenshot_combined_index->y - 1) * tile_height + frustum_bounds.y0;
|
|
adjusted_bounds.x1 = adjusted_bounds.x0 + tile_width;
|
|
adjusted_bounds.y1 = adjusted_bounds.y0 + tile_height;
|
|
frustum_bounds = adjusted_bounds;
|
|
}
|
|
}
|
|
|
|
render_camera_build_frustum(camera, &frustum_bounds, &frustum, TRUE);
|
|
render_camera_build_frustum(
|
|
&window->rasterizer_camera,
|
|
&frustum_bounds,
|
|
&rasterizer_frustum,
|
|
TRUE);
|
|
|
|
if (main_get_window_count() == 1)
|
|
{
|
|
if (structure_visibility_find_mirror(camera, &frustum, &mirror))
|
|
{
|
|
short saved_cluster_index;
|
|
struct render_camera mirror_camera;
|
|
struct render_frustum mirror_frustum;
|
|
|
|
saved_cluster_index = (short)render.cluster_index;
|
|
match_assert("c:\\halo\\SOURCE\\render\\render.c", 225, window->render_camera.viewport_bounds.x0==0);
|
|
match_assert("c:\\halo\\SOURCE\\render\\render.c", 226, window->render_camera.viewport_bounds.y0==0);
|
|
match_assert("c:\\halo\\SOURCE\\render\\render.c", 227, window->render_camera.viewport_bounds.x1==RASTERIZER_TARGET_RENDER_PRIMARY_WIDTH);
|
|
match_assert("c:\\halo\\SOURCE\\render\\render.c", 228, window->render_camera.viewport_bounds.y1==RASTERIZER_TARGET_RENDER_PRIMARY_HEIGHT);
|
|
|
|
render_camera_mirror(camera, &mirror, &mirror_camera);
|
|
render_camera_build_frustum(
|
|
&mirror_camera,
|
|
&frustum_bounds,
|
|
&mirror_frustum,
|
|
TRUE);
|
|
|
|
rasterizer_profile_enable(FALSE);
|
|
render.cluster_index = mirror.cluster_index;
|
|
render_window(
|
|
NONE,
|
|
&mirror_camera,
|
|
&mirror_frustum,
|
|
&mirror_camera,
|
|
&mirror_frustum,
|
|
_render_target_secondary,
|
|
FALSE);
|
|
render.cluster_index = saved_cluster_index;
|
|
rasterizer_profile_enable(TRUE);
|
|
has_mirror = TRUE;
|
|
}
|
|
}
|
|
|
|
render_window(
|
|
window->local_player_index,
|
|
camera,
|
|
&frustum,
|
|
&window->rasterizer_camera,
|
|
&rasterizer_frustum,
|
|
_render_target_primary,
|
|
has_mirror);
|
|
|
|
return;
|
|
}
|
|
|
|
#ifdef HALO_ANDROID
|
|
/* debug.vr_body (flat, vr_body.c): the player's own body as VR draws it,
|
|
the camera moved to look at it; FALSE when the view is the player's own */
|
|
static boolean render_vr_body_debug_frame(
|
|
struct render_window *window)
|
|
{
|
|
struct render_window debug_window = *window;
|
|
|
|
if (!vr_body_debug_camera(window->local_player_index, &debug_window.render_camera,
|
|
&debug_window.rasterizer_camera))
|
|
{
|
|
return FALSE;
|
|
}
|
|
render_player_frame(&debug_window, NULL);
|
|
return TRUE;
|
|
}
|
|
|
|
static void render_vr_camera_set(
|
|
struct render_camera *camera,
|
|
const float position[3],
|
|
const float forward[3],
|
|
const float up[3],
|
|
real vertical_field_of_view)
|
|
{
|
|
camera->position.x = position[0];
|
|
camera->position.y = position[1];
|
|
camera->position.z = position[2];
|
|
camera->forward.i = forward[0];
|
|
camera->forward.j = forward[1];
|
|
camera->forward.k = forward[2];
|
|
camera->up.i = up[0];
|
|
camera->up.j = up[1];
|
|
camera->up.k = up[2];
|
|
camera->vertical_field_of_view = vertical_field_of_view;
|
|
}
|
|
|
|
/* A player's view in VR (port/linux/include/halo_vr.h): drawn once per eye
|
|
from the headset's eye poses, placed at the game's camera, each copied to
|
|
its eye image; then the HUD and the menus, over nothing, for the overlay
|
|
the headset shows in front of the eyes. */
|
|
static void render_vr_player_frame(
|
|
struct render_window *window)
|
|
{
|
|
float game_position[3];
|
|
short eye;
|
|
|
|
game_position[0] = window->render_camera.position.x;
|
|
game_position[1] = window->render_camera.position.y;
|
|
game_position[2] = window->render_camera.position.z;
|
|
{
|
|
/* seated, the vehicle's heading as this frame draws it (between
|
|
ticks): the view turns with it smoothly */
|
|
long player_index = local_player_get_player_index(window->local_player_index);
|
|
long unit_index = player_index == NONE ? NONE : player_get(player_index)->unit_index;
|
|
long vehicle_index = unit_index == NONE ? NONE : object_get(unit_index)->object.parent_object_index;
|
|
real_matrix4x3 *nodes = vehicle_index == NONE ? NULL : render_interpolation_object_node_matrices(vehicle_index);
|
|
|
|
real_point3d left_hand;
|
|
real_point3d right_hand;
|
|
|
|
if (nodes)
|
|
halo_vr_set_seat_frame(window->local_player_index, (float)atan2(nodes[0].forward.j, nodes[0].forward.i));
|
|
if (nodes && render_object_vr_wheel_hands(unit_index, &left_hand, &right_hand))
|
|
{
|
|
/* (at a wheel: the hands that hold it) */
|
|
float left[3] = { left_hand.x - game_position[0], left_hand.y - game_position[1],
|
|
left_hand.z - game_position[2] };
|
|
float right[3] = { right_hand.x - game_position[0], right_hand.y - game_position[1],
|
|
right_hand.z - game_position[2] };
|
|
|
|
halo_vr_set_wheel(window->local_player_index, left, right);
|
|
}
|
|
else
|
|
{
|
|
halo_vr_set_wheel(window->local_player_index, NULL, NULL);
|
|
}
|
|
}
|
|
if (halo_vr_multiview())
|
|
{
|
|
/* both eyes at once, from between them */
|
|
struct render_window eye_window = *window;
|
|
float position[3], forward[3], up[3], bounds[4];
|
|
float vertical_field_of_view;
|
|
real_rectangle2d frustum_bounds;
|
|
|
|
halo_vr_center_camera(game_position, position, forward, up, &vertical_field_of_view, bounds);
|
|
render_vr_camera_set(&eye_window.render_camera, position, forward, up, vertical_field_of_view);
|
|
render_vr_camera_set(&eye_window.rasterizer_camera, position, forward, up, vertical_field_of_view);
|
|
frustum_bounds.x0 = bounds[0];
|
|
frustum_bounds.x1 = bounds[1];
|
|
frustum_bounds.y0 = bounds[2];
|
|
frustum_bounds.y1 = bounds[3];
|
|
{
|
|
/* the depth of what the game projects itself (its sprites) */
|
|
struct render_frustum frustum;
|
|
|
|
render_camera_build_frustum(&eye_window.rasterizer_camera, &frustum_bounds, &frustum, TRUE);
|
|
halo_vr_multiview_projection(frustum.projection_matrix[2][2], frustum.projection_matrix[3][2]);
|
|
}
|
|
render_vr_eye_pass = TRUE;
|
|
render_vr_frustum_bounds = &frustum_bounds;
|
|
render_player_frame(&eye_window, NULL);
|
|
render_vr_frustum_bounds = NULL;
|
|
render_vr_eye_pass = FALSE;
|
|
halo_vr_capture_multiview();
|
|
}
|
|
else for (eye = 0; eye < 2; eye++)
|
|
{
|
|
struct render_window eye_window = *window;
|
|
float position[3], forward[3], up[3], bounds[4];
|
|
float vertical_field_of_view;
|
|
real_rectangle2d frustum_bounds;
|
|
|
|
halo_vr_eye_camera(eye, game_position, position, forward, up, &vertical_field_of_view, bounds);
|
|
render_vr_camera_set(&eye_window.render_camera, position, forward, up, vertical_field_of_view);
|
|
render_vr_camera_set(&eye_window.rasterizer_camera, position, forward, up, vertical_field_of_view);
|
|
frustum_bounds.x0 = bounds[0];
|
|
frustum_bounds.x1 = bounds[1];
|
|
frustum_bounds.y0 = bounds[2];
|
|
frustum_bounds.y1 = bounds[3];
|
|
|
|
render_vr_eye_pass = TRUE;
|
|
render_vr_frustum_bounds = &frustum_bounds;
|
|
render_player_frame(&eye_window, NULL);
|
|
render_vr_frustum_bounds = NULL;
|
|
render_vr_eye_pass = FALSE;
|
|
halo_vr_capture_eye(eye);
|
|
}
|
|
render_vr_scope(window);
|
|
render_vr_overlay(window);
|
|
|
|
return;
|
|
}
|
|
|
|
/* The scope's view (halo_vr_scope): zoomed in, the player's view once more,
|
|
from the weapon along its aim through the zoom's field of view, into a
|
|
small square at the back buffer's top left (a sixteenth of it: the disc
|
|
is a few centimetres across), which becomes the disc over the weapon. */
|
|
#define RENDER_VR_SCOPE_SIZE 120
|
|
|
|
static void render_vr_scope(
|
|
struct render_window *window)
|
|
{
|
|
short zoom_level = player_control_get_zoom_level(window->local_player_index);
|
|
long player_index = local_player_get_player_index(window->local_player_index);
|
|
float position[3], forward[3], up[3];
|
|
float vertical_field_of_view;
|
|
real magnification;
|
|
struct render_window scope_window;
|
|
|
|
if (zoom_level == NONE || player_index == NONE || player_get(player_index)->unit_index == NONE)
|
|
return;
|
|
magnification = unit_get_zoom_magnification(player_get(player_index)->unit_index, zoom_level);
|
|
{
|
|
/* (a weapon whose body stands higher over the hand than a rifle's
|
|
has its scope's disc higher: the rocket launcher's tube) */
|
|
struct unit_datum *unit = unit_get(player_get(player_index)->unit_index);
|
|
long weapon_index = unit->unit.current_weapon_index >= 0 ?
|
|
unit->unit.weapon_object_indices[unit->unit.current_weapon_index] : NONE;
|
|
char const *name = weapon_index != NONE ? tag_get_name(object_get(weapon_index)->definition_index) : NULL;
|
|
|
|
halo_vr_set_scope_raise(name && strstr(name, "rocket launcher") ? 0.18f : 0.0f);
|
|
}
|
|
if (!halo_vr_scope(window->local_player_index, magnification, position, forward, up, &vertical_field_of_view))
|
|
return;
|
|
scope_window = *window;
|
|
render_vr_camera_set(&scope_window.render_camera, position, forward, up, vertical_field_of_view);
|
|
render_vr_camera_set(&scope_window.rasterizer_camera, position, forward, up, vertical_field_of_view);
|
|
scope_window.render_camera.viewport_bounds.x0 = scope_window.render_camera.viewport_bounds.y0 = 0;
|
|
scope_window.render_camera.viewport_bounds.x1 = scope_window.render_camera.viewport_bounds.y1 = RENDER_VR_SCOPE_SIZE;
|
|
scope_window.render_camera.window_bounds = scope_window.render_camera.viewport_bounds;
|
|
scope_window.rasterizer_camera.viewport_bounds = scope_window.render_camera.viewport_bounds;
|
|
scope_window.rasterizer_camera.window_bounds = scope_window.render_camera.viewport_bounds;
|
|
|
|
render_vr_eye_pass = TRUE;
|
|
render_player_frame(&scope_window, NULL);
|
|
render_vr_eye_pass = FALSE;
|
|
halo_vr_capture_scope(0.f, 0.f, (float)RENDER_VR_SCOPE_SIZE / RASTERIZER_TARGET_RENDER_PRIMARY_WIDTH,
|
|
(float)RENDER_VR_SCOPE_SIZE / RASTERIZER_TARGET_RENDER_PRIMARY_HEIGHT);
|
|
|
|
return;
|
|
}
|
|
|
|
/* where a ray from the weapon, as far as the longest-ranged weapons reach,
|
|
hits something, or its end */
|
|
static void render_vr_ray_end(
|
|
float const start[3],
|
|
float const direction[3],
|
|
long unit_index,
|
|
real_point3d *origin,
|
|
real_point3d *end)
|
|
{
|
|
real_vector3d ray;
|
|
struct collision_result collision;
|
|
|
|
origin->x = start[0];
|
|
origin->y = start[1];
|
|
origin->z = start[2];
|
|
ray.i = direction[0] * 100.f;
|
|
ray.j = direction[1] * 100.f;
|
|
ray.k = direction[2] * 100.f;
|
|
if (collision_test_vector(_collision_test_for_projectiles_flags, origin, &ray, unit_index, &collision))
|
|
*end = collision.point;
|
|
else
|
|
point_from_line3d(origin, &ray, 1.f, end);
|
|
}
|
|
|
|
/* how far the weapon in the right hand aims before it hits something (the
|
|
crosshair floats there, halo_vr_set_aim_distance), and its laser up to
|
|
there, along the barrel as a reload turns it (halo_vr_laser), if vr.laser
|
|
is on, but not over the menus */
|
|
static void render_vr_laser(
|
|
short local_player_index)
|
|
{
|
|
static real_argb_color const laser_color = { 1.f, 1.f, 0.15f, 0.1f };
|
|
float start[3], direction[3];
|
|
real_point3d origin, end;
|
|
long player_index;
|
|
long unit_index = NONE;
|
|
|
|
player_index = local_player_get_player_index(local_player_index);
|
|
if (player_index != NONE)
|
|
unit_index = player_get(player_index)->unit_index;
|
|
if (!halo_vr_ready_laser(local_player_index, start, direction))
|
|
return;
|
|
/* seated, the aim passes through the player's own vehicle (a gun's
|
|
barrel and shield are in front of the hand) */
|
|
if (unit_index != NONE && object_get(unit_index)->object.parent_object_index != NONE)
|
|
unit_index = object_get(unit_index)->object.parent_object_index;
|
|
render_vr_ray_end(start, direction, unit_index, &origin, &end);
|
|
halo_vr_set_aim_distance(distance3d(&origin, &end));
|
|
|
|
if (!halo_vr_laser(local_player_index, start, direction) || ui_widgets_menus_active())
|
|
return;
|
|
render_vr_ray_end(start, direction, unit_index, &origin, &end);
|
|
render_debug_line(TRUE, &origin, &end, &laser_color);
|
|
render_debug_point(TRUE, &end, 0.02f, &laser_color);
|
|
|
|
return;
|
|
}
|
|
|
|
/* the HUD and the menus of a player's view, on the cleared back buffer */
|
|
static void render_vr_overlay(
|
|
const struct render_window *window)
|
|
{
|
|
struct rasterizer_window_begin_parameters parameters;
|
|
boolean menus_active = ui_widgets_menus_active();
|
|
|
|
halo_vr_begin_overlay(menus_active);
|
|
profile_render_window_start(FALSE);
|
|
memset(¶meters, 0, sizeof(parameters));
|
|
render.local_player_index = window->local_player_index;
|
|
render.camera = window->render_camera;
|
|
render_camera_build_frustum(&render.camera, NULL, &render.frustum, TRUE);
|
|
parameters.camera = window->rasterizer_camera;
|
|
render_camera_build_frustum(¶meters.camera, NULL, ¶meters.frustum, TRUE);
|
|
parameters.rasterizer_target = _render_target_primary;
|
|
parameters.suppress_clear = TRUE;
|
|
parameters.window_index = render.window_index;
|
|
|
|
if (!menus_active && halo_vr_names_apart())
|
|
{
|
|
/* the players' names, each to be shown over its head */
|
|
rasterizer_window_begin(¶meters);
|
|
hud_draw_player_names_vr();
|
|
rasterizer_window_end();
|
|
halo_vr_capture_names();
|
|
}
|
|
if (!menus_active && halo_vr_crosshair_apart())
|
|
{
|
|
/* the weapon's crosshair alone, taken to where the weapon aims */
|
|
rasterizer_window_begin(¶meters);
|
|
hud_draw_crosshair();
|
|
rasterizer_window_end();
|
|
halo_vr_capture_crosshair();
|
|
}
|
|
rasterizer_window_begin(¶meters);
|
|
|
|
interface_draw_screen();
|
|
halo_screen_ui_offset(TRUE);
|
|
render_ui_widgets(window->local_player_index, &window->rasterizer_camera.viewport_bounds);
|
|
if (menus_active)
|
|
{
|
|
/* the controller's pointer on the menus' screen: a dot, ringed */
|
|
int pointer_x, pointer_y;
|
|
|
|
if (halo_vr_menu_pointer_at(&pointer_x, &pointer_y))
|
|
{
|
|
rectangle2d dot;
|
|
|
|
pointer_x -= (int)(halo_screen_width() - 640) / 2;
|
|
dot.x0 = (short)(pointer_x - 5);
|
|
dot.x1 = (short)(pointer_x + 5);
|
|
dot.y0 = (short)(pointer_y - 5);
|
|
dot.y1 = (short)(pointer_y + 5);
|
|
draw_quad(&dot, 0xE0102030);
|
|
dot.x0 += 2;
|
|
dot.x1 -= 2;
|
|
dot.y0 += 2;
|
|
dot.y1 -= 2;
|
|
draw_quad(&dot, 0xFF8FD3FF);
|
|
}
|
|
}
|
|
halo_screen_ui_offset(FALSE);
|
|
|
|
rasterizer_window_end();
|
|
profile_render_window_end();
|
|
|
|
return;
|
|
}
|
|
|
|
#endif
|
|
void render_frame(
|
|
struct render_window *windoze,
|
|
short window_count,
|
|
const point2d *screenshot_page_index,
|
|
const point2d *screenshot_index,
|
|
struct bitmap_data *screenshot_bitmap,
|
|
real time_delta_since_tick_sec)
|
|
{
|
|
struct rasterizer_frame_begin_parameters parameters;
|
|
short window_index;
|
|
struct render_window *window;
|
|
point2d screenshot_combined_index;
|
|
|
|
render.frame_index++;
|
|
render.time_delta_since_tick_sec = time_delta_since_tick_sec;
|
|
#ifdef HALO_ANDROID
|
|
/* VR: the graphics preset's effects (port/linux/game/vr_graphics.c) */
|
|
if (halo_vr_active())
|
|
vr_graphics_update();
|
|
#endif
|
|
memset(¶meters, 0, sizeof(parameters));
|
|
/* continuous between ticks (render_interpolation.c) */
|
|
parameters.game_time_sec = render_interpolation_game_time_sec(game_time_get());
|
|
rasterizer_frame_begin(¶meters);
|
|
rasterizer_windows_begin();
|
|
|
|
for (window_index = 0; window_index < window_count; window_index++)
|
|
{
|
|
long window_type;
|
|
|
|
window = &windoze[window_index];
|
|
render.window_index = window_index;
|
|
if (window->console_window)
|
|
{
|
|
window_type = 0;
|
|
}
|
|
#ifdef HALO_ANDROID
|
|
else if (window->local_player_index != NONE && screenshot_index == NULL &&
|
|
halo_vr_stereo_player(window->local_player_index, cinematic_in_progress()))
|
|
{
|
|
render_vr_player_frame(window);
|
|
continue;
|
|
}
|
|
#endif
|
|
#ifdef HALO_ANDROID
|
|
else if (window->local_player_index != NONE && screenshot_index == NULL &&
|
|
render_vr_body_debug_frame(window))
|
|
{
|
|
continue;
|
|
}
|
|
#endif
|
|
else if (window->local_player_index != NONE)
|
|
{
|
|
if (screenshot_index != NULL && screenshot_page_index != NULL)
|
|
{
|
|
screenshot_combined_index.x =
|
|
screenshot_page_index->x * global_screenshot_size + screenshot_index->x;
|
|
screenshot_combined_index.y =
|
|
screenshot_page_index->y * global_screenshot_size + screenshot_index->y;
|
|
}
|
|
|
|
render_player_frame(
|
|
window,
|
|
screenshot_index != NULL ? &screenshot_combined_index : NULL);
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
window_type = 1;
|
|
}
|
|
|
|
render_nonplayer_frame(window, window_type);
|
|
}
|
|
|
|
halo_screen_ui_offset(TRUE);
|
|
progress_bar_eachframe();
|
|
halo_screen_ui_offset(FALSE);
|
|
rasterizer_windows_end();
|
|
rasterizer_frame_end();
|
|
|
|
return;
|
|
}
|
|
|
|
/* ---------- private code */
|