mirror of
https://github.com/Cesarus85/ringfall-mjolnir-vr.git
synced 2026-10-17 13:00:25 +02:00
248 OpenCE commits (network version 25): touch controls and touch menus for phones, single-player in-game Settings, opt-in FOV/viewmodel settings, voice chat and votes to kick, lens flare culling, memory leak fixes (render targets, gamepads, the Android audio stream), profiling builds, the HALO_ANDROID split into HALO_ARM64_GUEST / HALO_GLES / HALO_ANDROID. Conflicts and how they were resolved: - README.md: ours kept whole. Upstream's README changes (portable build, profiling builds) went into docs/OPENCE_README.md: upstream's new text with our header note, links rewritten to ../ and our "made from your own copy" wording. - port/android/README.md: both merged; upstream's touch, WSL and save notes taken, with our application id in every adb path; our VR section, signing.properties and wording kept; the stale "How it works" list dropped as upstream dropped it. - Launcher icons (mipmap-*/ic_launcher_foreground.png, art/android-icon.png): ours (the Ringfall art). Upstream's helmet monochrome layers, drawable/ic_launcher_background.xml and its ic_launcher.xml were left out; our ic_launcher.xml, values/ic_launcher_background.xml and tools/android_icon.py stay. (port/assets/icon and tools/app_icon.py come in for the desktop builds' window icon only; do not run app_icon.py, it would write the helmet into the Android resources.) - port/android/host/host_main.c: both config helpers kept, and upstream's emulator write tracking (native_bridge_active); upstream's display width block dropped in favour of ours (640 in VR) and its display density for the touch controls moved into our flat (non-VR) branch. - port/linux/game/menu_functions.c: platform_text_field(typing, password) from upstream; the VR keyboard launch for text fields kept after it. - port/linux/game/network_distributed.c: upstream's profile_scope wrappers, with network_vr_tick and bots_tick kept in their places. - port/linux/src/dsound_sdl.c: both (our debug.audio_log, upstream's audio.output_device). - port/linux/src/port_config.c: our temp-and-rename config write kept, under upstream's HALO_ARM64_GUEST (it is guest file I/O). - port/linux/src/sdl_platform.c/.h: upstream's (Android menu pointer from the touchscreen, Steam keyboard, scoreboard pointer); our quit marker for the Steam Frame launcher, clipboard_seen and VR frame hooks kept. Our flat-mouse menu pointer (an emulator aid) is replaced by upstream's touch pointer, which also drives the menus on the emulator. - port/linux/src/d3d8_gl.c: halo_ui_pointer_update is upstream's, with the VR controller ray taking over while VR is active (the touchscreen then is not the pointer); our persistent-buffer writes kept for stream and index uploads, now writing upstream's exact length and under HALO_GLES like the code around them (stream_reserve, index_upload, mirror_refresh too); multiview uniforms stay under HALO_ANDROID, the counter binding under HALO_GLES. Visibility counters: ours kept (the snapshot ring with multiview views, host_gl_counters_snapshot) over upstream's per-frame glCopyBufferSubData into counter_snapshots: ours reads finished frames without stalling and counts both eyes' samples once; upstream's desktop query batches come in unchanged. - port/linux/src/xinput_sdl.c: both (VR controllers and touch controls). In VR the touch controls neither show nor act (touch_input_gamepad, touch_input_controls, touch move/look skipped); rumble goes to the controllers in VR, to the phone otherwise. - source/interface/first_person_weapons.c: both (VR scope pass check and display.viewmodel_visible). - source/interface/ui_widget.c: upstream's reworked menu targets taken (ui_mouse_fit_button_targets, merged rows, widened values), which do what our icon_x0 key picking and the 12-unit value arrows did; our VR label distance (25) kept, and a VR ray counts as a coarse pointer, so it gets the taller legend areas and the widened values (not the merged rows: a row's label still presses A). Our join code and VR options panels kept; their early return now forgets the frame's targets too. The on-screen keyboard: upstream's virtual_keyboard_click for taps and the mouse, ours (hover highlights the key) for the VR ray only. - source/main/main.c: both (VR phases around upstream's profile scopes). VR guards for new upstream features: - view_fov.c: display.fov, display.viewmodel_fov and display.viewmodel_visible do nothing while VR is active (the headset gives each eye's frustum; the weapon is in the hand). - Touch controls: off in VR (xinput_sdl.c), the menus' touch pointer too (d3d8_gl.c); the overlay stays hidden as the game never reports a scene. - Lens flare culling (744040c8,b1a71709) uses global_window_parameters' frustum, which in VR is each eye's (per-eye passes) or the union frustum of the multiview centre camera: no change needed. - In-game Settings: VR options still open with Y on the main and pause menus (checked flat with debug.vr_options). - The back button change (enableOnBackInvokedCallback=false) keeps the legacy KEYCODE_BACK path, which targetSdk 35 already had: no change on Quest or Frame. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1498 lines
46 KiB
C
1498 lines
46 KiB
C
/*
|
|
MODELS.C
|
|
|
|
symbols in this file:
|
|
00112DB0 0430:
|
|
_render_model_parts (0000)
|
|
001131E0 0110:
|
|
_model_interpolate_node_orientations (0000)
|
|
001132F0 0090:
|
|
_model_get_node_orientations (0000)
|
|
00113380 0140:
|
|
_model_get_node_matrices (0000)
|
|
001134C0 0110:
|
|
_model_node_matrices_from_orientations (0000)
|
|
001135D0 00a0:
|
|
_model_find_marker (0000)
|
|
00113670 0030:
|
|
_model_get_default_inverse_matrix (0000)
|
|
001136A0 0070:
|
|
_model_find_node (0000)
|
|
00113710 0010:
|
|
_model_geometry_part_build_tangent_matrices (0000)
|
|
00113720 0860:
|
|
_render_model (0000)
|
|
00113F80 01d0:
|
|
_model_get_marker_by_name (0000)
|
|
00114150 0070:
|
|
_model_build_tangent_matrices (0000)
|
|
0027F98C 000d:
|
|
??_C@_0N@PBHIAIOK@render_model?$AA@ (0000)
|
|
0027F9A0 0061:
|
|
??_C@_0GB@CAHPCM@part?9?$DOcentroid_secondary_node_in@ (0000)
|
|
0027FA08 005d:
|
|
??_C@_0FN@MLJHGCND@part?9?$DOcentroid_primary_node_inde@ (0000)
|
|
0027FA68 002c:
|
|
??_C@_0CM@MBIKHGBC@?$CBTEST_FLAG?$CIflags?0?5_render_model_@ (0000)
|
|
0027FA94 001f:
|
|
??_C@_0BP@PJEILNMK@c?3?2halo?2SOURCE?2models?2models?4c?$AA@ (0000)
|
|
0027FAB4 001e:
|
|
??_C@_0BO@FFDGBNA@node?9?$DOparent_node_index?$CB?$DNNONE?$AA@ (0000)
|
|
0027FAD8 0064:
|
|
??_C@_0GE@EAOMEKOC@?$CIactual_detail_level_index?5?$DO?$DN?50?$CJ@ (0000)
|
|
0027FB3C 001e:
|
|
??_C@_0BO@INPHBEBB@actual_detail_level_index?5?$DO?50?$AA@ (0000)
|
|
0027FB60 0060:
|
|
??_C@_0GA@CEBIGMGH@geometry_detail_level_index?$DO?$DN0?5?$CG@ (0000)
|
|
0027FBC0 0009:
|
|
??_C@_08OEDKDIOB@lighting?$AA@ (0000)
|
|
0027FBD0 0073:
|
|
??_C@_0HD@NKPBNIND@object_marker?9?$DOnode_index?$DO?$DN0?5?$CG?$CG?5@ (0000)
|
|
0027FC44 0008:
|
|
??_C@_07LGFKJAB@markers?$AA@ (0000)
|
|
0027FC4C 000e:
|
|
??_C@_0O@BKFGBDDJ@node_matrices?$AA@ (0000)
|
|
0030A390 05f8:
|
|
_render_model_section (0000)
|
|
00456650 0088:
|
|
_default_function_values (0000)
|
|
_default_render_model_change_colors (0000)
|
|
_default_render_model_effect (0000)
|
|
_default_render_model_region_permutation_indices (0000)
|
|
*/
|
|
|
|
/* ---------- headers */
|
|
|
|
#include "cseries.h"
|
|
#include "cseries/errors.h"
|
|
#include "cseries/profile.h"
|
|
#include "models.h"
|
|
|
|
#include "model_definitions.h"
|
|
|
|
#include "game/game.h"
|
|
#include "math/real_math.h"
|
|
#include "objects/objects.h"
|
|
#include "rasterizer/rasterizer.h"
|
|
#include "rasterizer/rasterizer_geometry.h"
|
|
#include "render/render.h"
|
|
#include "render/render_debug.h"
|
|
#include "scenario/scenario.h"
|
|
#include "scenario/scenario_definitions.h"
|
|
#include "shaders/shader_definitions.h"
|
|
#include "shaders/shaders.h"
|
|
#include "rasterizer/rasterizer_console_vars.h"
|
|
#include "rasterizer/rasterizer_model_types.h"
|
|
/* port: model tags and static enclosure recognition at map initialization. */
|
|
#include "cache/cache_files.h"
|
|
#include "rasterizer/rasterizer_transparent_geometry.h"
|
|
|
|
/* ---------- constants */
|
|
|
|
enum
|
|
{
|
|
NUMBER_OF_DETAIL_LEVELS_PER_MODEL = 5,
|
|
CORTANA_MODEL_NODE_LIST_CHECKSUM = 124371095,
|
|
};
|
|
|
|
enum
|
|
{
|
|
_scenario_cortana_hack_bit = 0,
|
|
_scenario_demo_ui_bit,
|
|
NUMBER_OF_SCENARIO_FLAGS
|
|
};
|
|
|
|
enum
|
|
{
|
|
_rasterizer_geometry_no_sort_bit = 0,
|
|
_rasterizer_geometry_no_queue_bit,
|
|
_rasterizer_geometry_no_fog_bit,
|
|
_rasterizer_geometry_no_zbuffer_bit,
|
|
_rasterizer_geometry_sky_bit,
|
|
_rasterizer_geometry_viewspace_bit,
|
|
_rasterizer_geometry_atmospheric_fog_but_no_planar_fog_bit,
|
|
_rasterizer_geometry_first_person_bit,
|
|
_rasterizer_geometry_parts_define_local_nodes_bit,
|
|
NUMBER_OF_RASTERIZER_GEOMETRY_FLAGS
|
|
};
|
|
|
|
enum
|
|
{
|
|
_render_model_immediate_bit = 0,
|
|
_render_model_shadow_bit,
|
|
_render_model_no_planar_fog_bit,
|
|
_render_model_first_person_bit,
|
|
NUMBER_OF_RENDER_MODEL_FLAGS
|
|
};
|
|
|
|
enum
|
|
{
|
|
_render_model_pass_solid = 0,
|
|
_render_model_pass_decal,
|
|
_render_model_pass_transparent,
|
|
NUMBER_OF_RENDER_MODEL_PASSES
|
|
};
|
|
|
|
enum
|
|
{
|
|
_model_geometry_part_stripped_bit = 0,
|
|
_model_geometry_part_local_nodes_bit,
|
|
NUMBER_OF_MODEL_GEOMETRY_PART_FLAGS
|
|
};
|
|
|
|
enum
|
|
{
|
|
_shader_type_screen = 0,
|
|
_shader_type_effect,
|
|
_shader_type_decal,
|
|
_shader_type_environment,
|
|
_shader_type_model,
|
|
_shader_type_transparent_generic,
|
|
_shader_type_transparent_chicago,
|
|
_shader_type_transparent_water,
|
|
_shader_type_transparent_glass,
|
|
_shader_type_transparent_meter,
|
|
_shader_type_transparent_plasma,
|
|
NUMBER_OF_SHADER_TYPES
|
|
};
|
|
|
|
enum
|
|
{
|
|
_shader_model_detail_after_reflection_bit = 0,
|
|
_shader_model_two_sided_bit,
|
|
_shader_model_not_alpha_tested_bit,
|
|
_shader_model_alpha_blended_decal_bit,
|
|
_shader_model_true_atmospheric_fog_bit,
|
|
_shader_model_nocull_two_sided_bit,
|
|
NUMBER_OF_SHADER_MODEL_FLAGS
|
|
};
|
|
|
|
/* ---------- macros */
|
|
|
|
/* ---------- structures */
|
|
|
|
struct model_shader_reference
|
|
{
|
|
struct tag_reference shader;
|
|
short permutation_index;
|
|
word pad;
|
|
long unused[3];
|
|
};
|
|
|
|
struct model_geometry
|
|
{
|
|
byte reserved[0x24];
|
|
struct tag_block parts;
|
|
};
|
|
|
|
struct model_geometry_part
|
|
{
|
|
unsigned long flags;
|
|
short shader_index;
|
|
char previous_part_index;
|
|
char next_part_index;
|
|
short centroid_primary_node_index;
|
|
short centroid_secondary_node_index;
|
|
real centroid_primary_node_weight;
|
|
real centroid_secondary_node_weight;
|
|
real_point3d centroid;
|
|
struct tag_block uncompressed_vertices;
|
|
struct tag_block compressed_vertices;
|
|
struct tag_block triangles;
|
|
struct triangle_buffer triangle_buffer;
|
|
struct vertex_buffer vertex_buffer;
|
|
};
|
|
|
|
typedef char verify_model_shader_reference_size[sizeof(struct model_shader_reference) == 0x20 ? 1 : -1];
|
|
typedef char verify_model_geometry_part_size[sizeof(struct model_geometry_part) == 0x68 ? 1 : -1];
|
|
|
|
struct shader_model_definition
|
|
{
|
|
struct shader shader;
|
|
word flags;
|
|
short type;
|
|
byte reserved_before_translucency[0xC];
|
|
real translucency;
|
|
};
|
|
|
|
struct render_sort_filth
|
|
{
|
|
short *previous_group_presorted_index_reference;
|
|
short *next_group_presorted_index_reference;
|
|
short group_index;
|
|
short next_part_index;
|
|
short part_index;
|
|
word pad;
|
|
};
|
|
|
|
typedef char verify_render_model_effect_size[sizeof(struct render_model_effect) == 0x28 ? 1 : -1];
|
|
typedef char verify_rasterizer_model_begin_parameters_size[sizeof(struct rasterizer_model_begin_parameters) == 0xCC ? 1 : -1];
|
|
|
|
/* ---------- prototypes */
|
|
|
|
#include "rasterizer/rasterizer_models.h"
|
|
|
|
static void render_model_parts(
|
|
struct model const *model,
|
|
char const *region_permutation_indices,
|
|
short region_permutation_count,
|
|
struct render_skinning const *skinning,
|
|
long object_index,
|
|
short geometry_detail_level_index,
|
|
short forced_shader_permutation_index,
|
|
long flags);
|
|
static void model_geometry_part_build_tangent_matrices(
|
|
struct model_geometry_part *part);
|
|
static void model_data_error(
|
|
struct model const *model,
|
|
char const *problem);
|
|
|
|
/* ---------- globals */
|
|
|
|
extern boolean rasterizer_model_cortana_hack;
|
|
|
|
extern boolean render_model_nodes;
|
|
extern boolean render_model_markers;
|
|
extern boolean render_model_vertex_counts;
|
|
extern boolean render_model_index_counts;
|
|
extern boolean render_model_no_geometry;
|
|
|
|
static real default_function_values[MAXIMUM_FUNCTION_VALUES_PER_MODEL] = { 0 };
|
|
static real_rgb_color default_render_model_change_colors[MAXIMUM_CHANGE_COLORS_PER_MODEL] = { 0 };
|
|
static struct render_model_effect default_render_model_effect = { 0 };
|
|
static char default_render_model_region_permutation_indices[MAXIMUM_REGIONS_PER_MODEL] = { 0 };
|
|
|
|
static struct profile_section render_model_section = { "render_model", NONE, TRUE };
|
|
|
|
/* ---------- private code */
|
|
|
|
#ifdef HALO_ANDROID
|
|
/* ---------- VR: the first-person hands' skin
|
|
|
|
The VR arms bend the first-person hands model's arms far from where its
|
|
animations have them; a triangle whose corners hang from bones that move
|
|
apart (an arm's, the skeleton's root's) stretches across. Read from the
|
|
part's own buffers, as drawn. */
|
|
|
|
void platform_log(char const *format, ...);
|
|
char *tag_get_name(long tag_index);
|
|
boolean rasterizer_model_buffer_data(struct vertex_buffer const *vertices, struct triangle_buffer const *triangles,
|
|
void const **vertex_data, void const **index_data);
|
|
|
|
/* the part's vertices and indices, if they are of the kinds read here */
|
|
static boolean model_vr_part_buffers(
|
|
struct model_geometry_part const *part,
|
|
struct model_vertex_compressed const **vertices,
|
|
word const **indices)
|
|
{
|
|
if (!rasterizer_model_buffer_data(&part->vertex_buffer, &part->triangle_buffer,
|
|
(void const **)vertices, (void const **)indices))
|
|
{
|
|
return FALSE;
|
|
}
|
|
return *vertices && *indices && part->vertex_buffer.count > 0 && part->vertex_buffer.count <= 65535 &&
|
|
part->triangle_buffer.count > 0 && part->triangle_buffer.count <= 65535 &&
|
|
part->vertex_buffer.type == _rasterizer_vertex_type_model_compressed &&
|
|
(part->triangle_buffer.type == _triangle_buffer_type_precompiled_strip ||
|
|
part->triangle_buffer.type == _triangle_buffer_type_triangles);
|
|
}
|
|
|
|
/* a triangle's corners (from the strip, or the list) and the nodes they
|
|
hang from (bits, a node more than a trace of weight); FALSE if malformed */
|
|
static boolean model_vr_triangle(
|
|
struct model_geometry_part const *part,
|
|
struct model_vertex_compressed const *vertices,
|
|
word const *indices,
|
|
long triangle_index,
|
|
short node_count,
|
|
word corners[3],
|
|
unsigned long long *nodes)
|
|
{
|
|
short corner;
|
|
|
|
*nodes = 0;
|
|
for (corner = 0; corner < 3; corner++)
|
|
{
|
|
long at = part->triangle_buffer.type == _triangle_buffer_type_triangles ?
|
|
triangle_index * 3 + corner : triangle_index + corner;
|
|
struct model_vertex_compressed const *vertex;
|
|
real weight;
|
|
short influence;
|
|
|
|
corners[corner] = indices[at];
|
|
if (corners[corner] >= part->vertex_buffer.count)
|
|
return FALSE;
|
|
vertex = &vertices[corners[corner]];
|
|
/* (the weight is a signed short's fraction, as rasterizer_debug_model_vertices reads it) */
|
|
weight = (real)vertex->node_weight / 32767.0f;
|
|
if (weight < 0.0f || weight > 1.0f)
|
|
return FALSE;
|
|
for (influence = 0; influence < 2; influence++)
|
|
{
|
|
short node = (short)(vertex->nodes[influence] / 3);
|
|
real amount = influence ? 1.0f - weight : weight;
|
|
|
|
if (amount <= 0.001f)
|
|
continue;
|
|
if (vertex->nodes[influence] % 3 || node >= node_count || node >= 64)
|
|
return FALSE;
|
|
*nodes |= 1ULL << node;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/* once a model: how many triangles hang from each node, and how many of
|
|
those from its root too (debug's logcat; first_person_weapons.c, for the
|
|
hands) */
|
|
void model_vr_log_skin(
|
|
long model_index)
|
|
{
|
|
static long logged[8] = { NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE };
|
|
struct model *model;
|
|
long triangles[64];
|
|
long with_root[64];
|
|
long total = 0;
|
|
short slot;
|
|
short node_index;
|
|
short region_index;
|
|
short geometry_seen[MAXIMUM_REGIONS_PER_MODEL];
|
|
short geometry_seen_count = 0;
|
|
boolean readable = TRUE;
|
|
|
|
for (slot = 0; slot < NUMBEROF(logged); slot++)
|
|
{
|
|
if (logged[slot] == model_index)
|
|
return;
|
|
if (logged[slot] == NONE)
|
|
break;
|
|
}
|
|
if (slot == NUMBEROF(logged) || model_index == NONE)
|
|
return;
|
|
logged[slot] = model_index;
|
|
model = model_definition_get(model_index);
|
|
if (model->nodes.count <= 0 || model->nodes.count > 64)
|
|
return;
|
|
csmemset(triangles, 0, sizeof(triangles));
|
|
csmemset(with_root, 0, sizeof(with_root));
|
|
/* (each region's first permutation, in the finest detail, as drawn) */
|
|
for (region_index = 0; region_index < model->regions.count && region_index < MAXIMUM_REGIONS_PER_MODEL;
|
|
region_index++)
|
|
{
|
|
struct model_region *region = TAG_BLOCK_GET_ELEMENT(&model->regions, region_index, struct model_region);
|
|
struct model_region_permutation *permutation;
|
|
short geometry_index;
|
|
short seen;
|
|
struct model_geometry *geometry;
|
|
short part_index;
|
|
|
|
if (region->permutations.count <= 0)
|
|
continue;
|
|
permutation = TAG_BLOCK_GET_ELEMENT(®ion->permutations, 0, struct model_region_permutation);
|
|
geometry_index = permutation->geometry_indices[NUMBER_OF_DETAIL_LEVELS_PER_MODEL - 1];
|
|
for (seen = 0; seen < geometry_seen_count && geometry_seen[seen] != geometry_index; seen++);
|
|
if (geometry_index == NONE || seen < geometry_seen_count)
|
|
continue;
|
|
geometry_seen[geometry_seen_count++] = geometry_index;
|
|
geometry = TAG_BLOCK_GET_ELEMENT(&model->geometries, geometry_index, struct model_geometry);
|
|
for (part_index = 0; part_index < geometry->parts.count; part_index++)
|
|
{
|
|
struct model_geometry_part *part = TAG_BLOCK_GET_ELEMENT(&geometry->parts, part_index,
|
|
struct model_geometry_part);
|
|
struct model_vertex_compressed const *vertices;
|
|
word const *indices;
|
|
long triangle_index;
|
|
|
|
if (!model_vr_part_buffers(part, &vertices, &indices))
|
|
{
|
|
readable = FALSE;
|
|
continue;
|
|
}
|
|
for (triangle_index = 0; triangle_index < part->triangle_buffer.count; triangle_index++)
|
|
{
|
|
word corners[3];
|
|
unsigned long long nodes;
|
|
|
|
if (!model_vr_triangle(part, vertices, indices, triangle_index, model->nodes.count, corners, &nodes))
|
|
{
|
|
readable = FALSE;
|
|
break;
|
|
}
|
|
if (corners[0] == corners[1] || corners[1] == corners[2] || corners[0] == corners[2])
|
|
continue;
|
|
total++;
|
|
for (node_index = 0; node_index < model->nodes.count; node_index++)
|
|
{
|
|
if (!(nodes & (1ULL << node_index)))
|
|
continue;
|
|
triangles[node_index]++;
|
|
if (nodes & 1)
|
|
with_root[node_index]++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
platform_log("VR skin of %s: %ld triangles%s; %ld hang from its root '%s'", tag_get_name(model_index), total,
|
|
readable ? "" : " (some parts unreadable)", triangles[0],
|
|
TAG_BLOCK_GET_ELEMENT(&model->nodes, 0, struct model_node)->name);
|
|
for (node_index = 1; node_index < model->nodes.count; node_index++)
|
|
{
|
|
if (triangles[node_index])
|
|
{
|
|
platform_log("VR skin: node %d '%s': %ld triangles, %ld with the root", node_index,
|
|
TAG_BLOCK_GET_ELEMENT(&model->nodes, node_index, struct model_node)->name, triangles[node_index],
|
|
with_root[node_index]);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void render_model_parts(
|
|
struct model const *model,
|
|
char const *region_permutation_indices,
|
|
short region_permutation_count,
|
|
struct render_skinning const *skinning,
|
|
long object_index,
|
|
short geometry_detail_level_index,
|
|
short forced_shader_permutation_index,
|
|
long flags)
|
|
{
|
|
boolean immediate = TEST_FLAG(flags, _render_model_immediate_bit);
|
|
short last_pass = TEST_FLAG(flags, _render_model_shadow_bit) ? _render_model_pass_solid : _render_model_pass_transparent;
|
|
struct render_sort_filth sort_filth[MAXIMUM_PARTS_PER_MODEL_GEOMETRY];
|
|
real_point3d centroid;
|
|
short pass;
|
|
|
|
for (pass = _render_model_pass_solid; pass<=last_pass; pass++)
|
|
{
|
|
short sort_filth_count = 0;
|
|
short region_index;
|
|
short i, j;
|
|
/* port: no more regions than the permutations passed in hold: an
|
|
object's MAXIMUM_REGIONS_PER_OBJECT, or the default's
|
|
MAXIMUM_REGIONS_PER_MODEL (a map's count; retail models have 8 at
|
|
most) */
|
|
short region_count = (short)MIN(model->regions.count, region_permutation_count);
|
|
|
|
for (region_index = 0; region_index<region_count; region_index++)
|
|
{
|
|
struct model_region *region = TAG_BLOCK_GET_ELEMENT(&model->regions, region_index, struct model_region);
|
|
char permutation_index = region_permutation_indices[region_index];
|
|
|
|
if (permutation_index!=NONE)
|
|
{
|
|
struct model_region_permutation *permutation;
|
|
short geometry_index;
|
|
|
|
/* port: a permutation the region has, and a geometry the model
|
|
has (a map's indices); a bad one draws nothing */
|
|
if (!VALID_INDEX(permutation_index, region->permutations.count))
|
|
{
|
|
model_data_error(model, "region permutation");
|
|
continue;
|
|
}
|
|
permutation = TAG_BLOCK_GET_ELEMENT(
|
|
®ion->permutations,
|
|
permutation_index,
|
|
struct model_region_permutation);
|
|
geometry_index = permutation->geometry_indices[geometry_detail_level_index];
|
|
if (geometry_index!=NONE && !VALID_INDEX(geometry_index, model->geometries.count))
|
|
{
|
|
model_data_error(model, "geometry");
|
|
continue;
|
|
}
|
|
|
|
if (!render_model_no_geometry && geometry_index!=NONE)
|
|
{
|
|
struct model_geometry *geometry = TAG_BLOCK_GET_ELEMENT(&model->geometries, geometry_index, struct model_geometry);
|
|
short part_index;
|
|
|
|
for (part_index = 0; part_index<geometry->parts.count; part_index++)
|
|
{
|
|
struct model_geometry_part *part = TAG_BLOCK_GET_ELEMENT(&geometry->parts, part_index, struct model_geometry_part);
|
|
struct model_shader_reference *shader_reference;
|
|
struct shader *shader;
|
|
|
|
/* port: a shader the model has (a map's index); a part
|
|
without one isn't drawn */
|
|
if (!VALID_INDEX(part->shader_index, model->shaders.count))
|
|
{
|
|
model_data_error(model, "shader");
|
|
continue;
|
|
}
|
|
shader_reference = TAG_BLOCK_GET_ELEMENT(
|
|
&model->shaders,
|
|
part->shader_index,
|
|
struct model_shader_reference);
|
|
shader = shader_definition_get(shader_reference->shader.index);
|
|
|
|
if (shader_type_is_valid_for_model(shader->base.type) &&
|
|
!TEST_FLAG(part->flags, _model_geometry_part_stripped_bit))
|
|
{
|
|
if (shader_type_is_transparent(shader->base.type))
|
|
{
|
|
if (pass==_render_model_pass_transparent)
|
|
{
|
|
/* port: the root's matrix for a node the model
|
|
doesn't have (a map's index) */
|
|
short centroid_node_index = VALID_INDEX(part->centroid_primary_node_index, skinning->node_matrix_count) ?
|
|
part->centroid_primary_node_index :
|
|
0;
|
|
|
|
match_assert("c:\\halo\\SOURCE\\models\\models.c", 442, !TEST_FLAG(flags, _render_model_shadow_bit));
|
|
match_assert(
|
|
"c:\\halo\\SOURCE\\models\\models.c",
|
|
445,
|
|
part->centroid_primary_node_index>=0 && part->centroid_primary_node_index<model->nodes.count);
|
|
match_assert(
|
|
"c:\\halo\\SOURCE\\models\\models.c",
|
|
446,
|
|
part->centroid_secondary_node_index>=0 && part->centroid_secondary_node_index<model->nodes.count);
|
|
|
|
matrix4x3_transform_point(
|
|
&skinning->node_matrices[centroid_node_index],
|
|
&part->centroid,
|
|
¢roid);
|
|
rasterizer_model_transparent_geometry_submit(
|
|
shader,
|
|
forced_shader_permutation_index ? forced_shader_permutation_index : shader_reference->permutation_index,
|
|
&part->triangle_buffer,
|
|
NONE,
|
|
part->triangle_buffer.count,
|
|
&part->vertex_buffer,
|
|
NONE,
|
|
¢roid,
|
|
/* port: no sort record once all of sort_filth is
|
|
used (it wrote one past it) */
|
|
sort_filth_count<MAXIMUM_PARTS_PER_MODEL_GEOMETRY ? &sort_filth[sort_filth_count] : NULL);
|
|
|
|
if (sort_filth_count<MAXIMUM_PARTS_PER_MODEL_GEOMETRY &&
|
|
sort_filth[sort_filth_count].group_index!=NONE &&
|
|
!immediate &&
|
|
/* port: part zero can head a link
|
|
(models_fix_transparent_part_links); no stock
|
|
model's part has a previous part of 0 */
|
|
(part->next_part_index>0 || part->previous_part_index>=0))
|
|
{
|
|
sort_filth[sort_filth_count].part_index = part_index;
|
|
sort_filth[sort_filth_count].next_part_index = part->next_part_index;
|
|
sort_filth_count++;
|
|
}
|
|
}
|
|
}
|
|
else if (shader->base.type==_shader_type_model &&
|
|
TEST_FLAG(((struct shader_model_definition *)shader_get_and_verify_type(shader, _shader_type_model))->flags, _shader_model_alpha_blended_decal_bit))
|
|
{
|
|
if (pass==_render_model_pass_decal)
|
|
{
|
|
match_assert("c:\\halo\\SOURCE\\models\\models.c", 491, !TEST_FLAG(flags, _render_model_shadow_bit));
|
|
|
|
rasterizer_model_draw(
|
|
shader,
|
|
forced_shader_permutation_index ? forced_shader_permutation_index : shader_reference->permutation_index,
|
|
&part->triangle_buffer,
|
|
NONE,
|
|
part->triangle_buffer.count,
|
|
&part->vertex_buffer,
|
|
NONE);
|
|
}
|
|
}
|
|
else if (pass==_render_model_pass_solid)
|
|
{
|
|
if (TEST_FLAG(flags, _render_model_shadow_bit))
|
|
{
|
|
rasterizer_environment_shadow_model_draw(
|
|
shader,
|
|
forced_shader_permutation_index ? forced_shader_permutation_index : shader_reference->permutation_index,
|
|
&part->triangle_buffer,
|
|
&part->vertex_buffer);
|
|
}
|
|
else
|
|
{
|
|
rasterizer_model_draw(
|
|
shader,
|
|
forced_shader_permutation_index ? forced_shader_permutation_index : shader_reference->permutation_index,
|
|
&part->triangle_buffer,
|
|
NONE,
|
|
part->triangle_buffer.count,
|
|
&part->vertex_buffer,
|
|
NONE);
|
|
rasterizer_debug_model_vertices(object_index, skinning, part);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 0; i<sort_filth_count; i++)
|
|
{
|
|
for (j = 0; j<sort_filth_count; j++)
|
|
{
|
|
if (sort_filth[i].next_part_index==sort_filth[j].part_index && sort_filth[i].next_part_index>0)
|
|
{
|
|
*sort_filth[i].next_group_presorted_index_reference = sort_filth[j].group_index;
|
|
*sort_filth[j].previous_group_presorted_index_reference = sort_filth[i].group_index;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* ---------- public code */
|
|
|
|
/* port: a pickup's energy inside its two-sided glass shell is drawn before
|
|
the shell with the engine's own part links (render_model_parts), as Halo CE
|
|
Restored's tags link them, so that the centroid sort cannot put it after the
|
|
glass; models with links of their own, skinned ones and those with other
|
|
transparent parts are left as they are */
|
|
static void model_geometry_fix_transparent_part_links(
|
|
struct model const *model, struct model_geometry *geometry)
|
|
{
|
|
short i, glass = NONE, energy = NONE, transparent_count = 0;
|
|
struct model_geometry_part *parts = geometry->parts.address;
|
|
|
|
if (model->nodes.count != 1 || geometry->parts.count < 2 ||
|
|
geometry->parts.count > MAXIMUM_PARTS_PER_MODEL_GEOMETRY || !parts)
|
|
{
|
|
return;
|
|
}
|
|
for (i = 0; i < geometry->parts.count; ++i)
|
|
{
|
|
struct shader *shader;
|
|
struct model_shader_reference const *reference;
|
|
|
|
if (parts[i].flags || parts[i].previous_part_index != NONE || parts[i].next_part_index != NONE ||
|
|
!VALID_INDEX(parts[i].shader_index, model->shaders.count))
|
|
{
|
|
return;
|
|
}
|
|
reference = TAG_BLOCK_GET_ELEMENT(&model->shaders, parts[i].shader_index, struct model_shader_reference);
|
|
shader = shader_definition_get(reference->shader.index);
|
|
if (shader_type_is_transparent(shader->base.type))
|
|
{
|
|
++transparent_count;
|
|
if (shader->base.type == _shader_type_transparent_glass)
|
|
{
|
|
glass = i;
|
|
}
|
|
else if (shader->base.type == _shader_type_transparent_generic)
|
|
{
|
|
energy = i;
|
|
}
|
|
}
|
|
}
|
|
if (transparent_count == 2 && glass != NONE && energy != NONE &&
|
|
rasterizer_transparent_geometry_is_enclosure(
|
|
shader_definition_get(TAG_BLOCK_GET_ELEMENT(&model->shaders, parts[glass].shader_index, struct model_shader_reference)->shader.index),
|
|
&parts[glass].vertex_buffer, &parts[glass].triangle_buffer,
|
|
shader_definition_get(TAG_BLOCK_GET_ELEMENT(&model->shaders, parts[energy].shader_index, struct model_shader_reference)->shader.index),
|
|
&parts[energy].vertex_buffer))
|
|
{
|
|
/* (a link to part zero is none: with the glass part zero, the two
|
|
parts, which have no links of their own, change places) */
|
|
if (glass == 0)
|
|
{
|
|
struct model_geometry_part swap = parts[glass];
|
|
parts[glass] = parts[energy];
|
|
parts[energy] = swap;
|
|
glass = energy;
|
|
energy = 0;
|
|
}
|
|
parts[energy].next_part_index = (char)glass;
|
|
parts[glass].previous_part_index = (char)energy;
|
|
}
|
|
}
|
|
|
|
/* port: the links of every model the map loaded (at its start: the meshes
|
|
do not change, and the next map's tags are loaded afresh) */
|
|
void models_fix_transparent_part_links(void)
|
|
{
|
|
struct tag_iterator iterator;
|
|
long index;
|
|
|
|
tag_iterator_new(&iterator, MODELS_GROUP_TAG);
|
|
while ((index = tag_iterator_next(&iterator)) != NONE)
|
|
{
|
|
struct model *model = model_definition_get(index);
|
|
long geometry;
|
|
for (geometry = 0; geometry < model->geometries.count; ++geometry)
|
|
{
|
|
model_geometry_fix_transparent_part_links(model,
|
|
TAG_BLOCK_GET_ELEMENT(&model->geometries, geometry, struct model_geometry));
|
|
}
|
|
}
|
|
}
|
|
|
|
void model_interpolate_node_orientations(
|
|
struct model const *model,
|
|
struct real_orientation *original_node_orientations,
|
|
struct real_orientation *target_node_orientations,
|
|
short frame_index,
|
|
short frame_count)
|
|
{
|
|
real fraction = (real)(frame_index + 1) / (real)frame_count;
|
|
real inverse_fraction = 1.f - fraction;
|
|
short node_index;
|
|
/* port: no more nodes than the engine's arrays hold (a map's count) */
|
|
short node_count = (short)MIN(model->nodes.count, MAXIMUM_NODES_PER_MODEL);
|
|
|
|
match_assert(
|
|
"c:\\halo\\SOURCE\\models\\models.c",
|
|
579,
|
|
frame_count>0);
|
|
match_assert(
|
|
"c:\\halo\\SOURCE\\models\\models.c",
|
|
580,
|
|
frame_index<frame_count);
|
|
|
|
for (node_index = 0; node_index < node_count; node_index++)
|
|
{
|
|
struct real_orientation *target = &target_node_orientations[node_index];
|
|
struct real_orientation *original = &original_node_orientations[node_index];
|
|
|
|
target->scale = original->scale * inverse_fraction + target->scale * fraction;
|
|
quaternions_interpolate_and_normalize(
|
|
&original->rotation,
|
|
&target->rotation,
|
|
fraction,
|
|
&target->rotation);
|
|
target->translation.x = original->translation.x * inverse_fraction + target->translation.x * fraction;
|
|
target->translation.y = original->translation.y * inverse_fraction + target->translation.y * fraction;
|
|
target->translation.z = original->translation.z * inverse_fraction + target->translation.z * fraction;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void model_get_node_orientations(
|
|
struct model const *model,
|
|
real_orientation *node_orientations)
|
|
{
|
|
short node_index;
|
|
/* port: no more nodes than the engine's arrays hold (a map's count) */
|
|
short node_count = (short)MIN(model->nodes.count, MAXIMUM_NODES_PER_MODEL);
|
|
|
|
for (node_index = 0; node_index<node_count; node_index++)
|
|
{
|
|
struct model_node *node = TAG_BLOCK_GET_ELEMENT(&model->nodes, node_index, struct model_node);
|
|
|
|
node_orientations[node_index].rotation = node->default_rotation;
|
|
node_orientations[node_index].translation = node->default_translation;
|
|
node_orientations[node_index].scale = 1.f;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void model_get_node_matrices(
|
|
struct model const *model,
|
|
real_matrix4x3 *node_matrices,
|
|
real_point3d const *origin,
|
|
real_vector3d const *forward,
|
|
real_vector3d const *up)
|
|
{
|
|
short node_queue[MAXIMUM_NODES_PER_MODEL];
|
|
short read_index, write_index;
|
|
/* port: the nodes the queue and the matrices hold (a map's count) */
|
|
short node_count = (short)MIN(model->nodes.count, MAXIMUM_NODES_PER_MODEL);
|
|
|
|
/* port: a model without nodes has no matrices */
|
|
if (node_count<=0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
node_queue[0] = 0;
|
|
read_index = 0;
|
|
write_index = 1;
|
|
|
|
while (read_index!=write_index)
|
|
{
|
|
short node_index = node_queue[read_index++];
|
|
struct model_node *node = TAG_BLOCK_GET_ELEMENT(&model->nodes, node_index, struct model_node);
|
|
real_matrix4x3 node_matrix;
|
|
|
|
matrix4x3_from_point_and_quaternion(&node_matrix, &node->default_translation, &node->default_rotation);
|
|
|
|
if (node_index == 0)
|
|
{
|
|
matrix4x3_from_point_and_vectors(
|
|
&node_matrices[node_index],
|
|
origin ? origin : global_origin3d,
|
|
forward ? forward : global_forward3d,
|
|
up ? up : global_up3d);
|
|
matrix4x3_multiply(&node_matrices[node_index], &node_matrix, &node_matrices[node_index]);
|
|
}
|
|
else
|
|
{
|
|
short parent_node_index = node->parent_node_index;
|
|
|
|
match_assert("c:\\halo\\SOURCE\\models\\models.c", 650, node->parent_node_index!=NONE);
|
|
/* port: a parent the model doesn't have is the root (a map's index) */
|
|
if (!VALID_INDEX(parent_node_index, node_count))
|
|
{
|
|
model_data_error(model, "node's parent");
|
|
parent_node_index = 0;
|
|
}
|
|
matrix4x3_multiply(&node_matrices[parent_node_index], &node_matrix, &node_matrices[node_index]);
|
|
}
|
|
|
|
/* port: only nodes the model has, and no more than the queue holds (a
|
|
map's links, which could go in a loop) */
|
|
if (node->next_sibling_node_index!=NONE)
|
|
{
|
|
if (VALID_INDEX(node->next_sibling_node_index, node_count) && write_index<MAXIMUM_NODES_PER_MODEL)
|
|
{
|
|
node_queue[write_index++] = node->next_sibling_node_index;
|
|
}
|
|
else
|
|
{
|
|
model_data_error(model, "node's sibling");
|
|
}
|
|
}
|
|
if (node->first_child_node_index!=NONE)
|
|
{
|
|
if (VALID_INDEX(node->first_child_node_index, node_count) && write_index<MAXIMUM_NODES_PER_MODEL)
|
|
{
|
|
node_queue[write_index++] = node->first_child_node_index;
|
|
}
|
|
else
|
|
{
|
|
model_data_error(model, "node's child");
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void model_node_matrices_from_orientations(
|
|
struct model const *model,
|
|
real_matrix4x3 *node_matrices,
|
|
real_orientation const *node_orientations,
|
|
real_point3d const *origin,
|
|
real_vector3d const *forward,
|
|
real_vector3d const *up)
|
|
{
|
|
real_matrix4x3 root_matrix;
|
|
/* port: the nodes the queue and the matrices hold (a map's count) */
|
|
short node_count = (short)MIN(model->nodes.count, MAXIMUM_NODES_PER_MODEL);
|
|
|
|
matrix4x3_from_point_and_vectors(&root_matrix, origin, forward, up);
|
|
|
|
if (node_count>0)
|
|
{
|
|
short node_queue[MAXIMUM_NODES_PER_MODEL];
|
|
short read_index = 0;
|
|
short write_index = 1;
|
|
|
|
node_queue[0] = 0;
|
|
|
|
do
|
|
{
|
|
short node_index = node_queue[read_index++];
|
|
struct model_node *node = TAG_BLOCK_GET_ELEMENT(&model->nodes, node_index, struct model_node);
|
|
real_matrix4x3 const *parent_matrix = node_index==0 ? &root_matrix : &node_matrices[node->parent_node_index];
|
|
real_matrix4x3 node_matrix;
|
|
|
|
/* port: a parent the model doesn't have is the root (a map's index) */
|
|
if (node_index!=0 && !VALID_INDEX(node->parent_node_index, node_count))
|
|
{
|
|
model_data_error(model, "node's parent");
|
|
parent_matrix = &node_matrices[0];
|
|
}
|
|
|
|
matrix4x3_from_orientation(&node_matrix, &node_orientations[node_index]);
|
|
matrix4x3_multiply(parent_matrix, &node_matrix, &node_matrices[node_index]);
|
|
|
|
/* port: only nodes the model has, and no more than the queue holds
|
|
(a map's links, which could go in a loop) */
|
|
if (node->next_sibling_node_index!=NONE)
|
|
{
|
|
if (VALID_INDEX(node->next_sibling_node_index, node_count) && write_index<MAXIMUM_NODES_PER_MODEL)
|
|
{
|
|
node_queue[write_index++] = node->next_sibling_node_index;
|
|
}
|
|
else
|
|
{
|
|
model_data_error(model, "node's sibling");
|
|
}
|
|
}
|
|
if (node->first_child_node_index!=NONE)
|
|
{
|
|
if (VALID_INDEX(node->first_child_node_index, node_count) && write_index<MAXIMUM_NODES_PER_MODEL)
|
|
{
|
|
node_queue[write_index++] = node->first_child_node_index;
|
|
}
|
|
else
|
|
{
|
|
model_data_error(model, "node's child");
|
|
}
|
|
}
|
|
}
|
|
while (read_index!=write_index);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
short model_find_marker(
|
|
long model_index,
|
|
char const *name)
|
|
{
|
|
if (model_index != NONE && name && *name)
|
|
{
|
|
struct model *model = model_definition_get(model_index);
|
|
short lower_bound = 0;
|
|
short upper_bound = (short)model->markers.count - 1;
|
|
|
|
while (lower_bound <= upper_bound)
|
|
{
|
|
short marker_index = (short)((lower_bound + upper_bound) / 2);
|
|
struct model_marker *marker = TAG_BLOCK_GET_ELEMENT(
|
|
&model->markers,
|
|
marker_index,
|
|
struct model_marker);
|
|
long comparison = _stricmp(name, marker->name);
|
|
|
|
if (comparison == 0)
|
|
{
|
|
return marker_index;
|
|
}
|
|
if (comparison < 0)
|
|
{
|
|
upper_bound = marker_index - 1;
|
|
}
|
|
else
|
|
{
|
|
lower_bound = marker_index + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NONE;
|
|
}
|
|
|
|
real_matrix4x3 *model_get_default_inverse_matrix(
|
|
struct model *model,
|
|
short node_index)
|
|
{
|
|
struct model_node *node = TAG_BLOCK_GET_ELEMENT(&model->nodes, node_index, struct model_node);
|
|
|
|
return &node->runtime_default_inverse_matrix;
|
|
}
|
|
|
|
short model_find_node(
|
|
long model_index,
|
|
char const *name)
|
|
{
|
|
if (model_index!=NONE)
|
|
{
|
|
short node_index;
|
|
struct model *model = model_definition_get(model_index);
|
|
|
|
for (node_index = 0; node_index<model->nodes.count; node_index++)
|
|
{
|
|
struct model_node *node = TAG_BLOCK_GET_ELEMENT(&model->nodes, node_index, struct model_node);
|
|
|
|
if (!csstrcmp(node->name, name))
|
|
{
|
|
return node_index;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NONE;
|
|
}
|
|
|
|
short model_get_marker_by_name(
|
|
long model_index,
|
|
char const *name,
|
|
byte const *region_permutations,
|
|
short const *node_remapping_table,
|
|
short node_count,
|
|
real_matrix4x3 const *node_matrices,
|
|
boolean mirrored_flag,
|
|
struct object_marker *markers,
|
|
short maximum_marker_count)
|
|
{
|
|
short result = 0;
|
|
short marker_index = model_find_marker(model_index, name);
|
|
|
|
match_assert("c:\\halo\\SOURCE\\models\\models.c", 760, node_matrices);
|
|
match_assert("c:\\halo\\SOURCE\\models\\models.c", 761, markers);
|
|
|
|
if (marker_index!=NONE)
|
|
{
|
|
short i;
|
|
|
|
struct model *model = model_definition_get(model_index);
|
|
struct model_marker* marker = TAG_BLOCK_GET_ELEMENT(&model->markers, marker_index, struct model_marker);
|
|
/* port: the nodes the matrices passed in hold (a map's counts) */
|
|
short matrix_count = node_remapping_table ?
|
|
(short)MIN(node_count, MAXIMUM_NODES_PER_MODEL) :
|
|
(short)MIN(model->nodes.count, MAXIMUM_NODES_PER_MODEL);
|
|
|
|
for (i =0; i<marker->instances.count; i++)
|
|
{
|
|
struct model_marker_instance* instance = TAG_BLOCK_GET_ELEMENT(&marker->instances, i, struct model_marker_instance);
|
|
|
|
/* port: a marker on a region or node the model doesn't have is
|
|
skipped (a map's indices); the permutations passed in are an
|
|
object's (retail's markers are on regions 5 at most) */
|
|
if ((region_permutations && instance->region_index>=MAXIMUM_REGIONS_PER_OBJECT) ||
|
|
instance->node_index>=MIN(model->nodes.count, MAXIMUM_NODES_PER_MODEL))
|
|
{
|
|
model_data_error(model, "marker");
|
|
continue;
|
|
}
|
|
|
|
if (!region_permutations ||
|
|
region_permutations[instance->region_index]==instance->permutation_index)
|
|
{
|
|
struct object_marker *object_marker;
|
|
short marker_node_index;
|
|
|
|
if (result>=maximum_marker_count)
|
|
{
|
|
break;
|
|
}
|
|
|
|
marker_node_index = node_remapping_table ? node_remapping_table[instance->node_index] : instance->node_index;
|
|
if (!VALID_INDEX(marker_node_index, matrix_count))
|
|
{
|
|
model_data_error(model, "marker");
|
|
continue;
|
|
}
|
|
|
|
object_marker = &markers[result++];
|
|
object_marker->node_index = marker_node_index;
|
|
matrix4x3_from_point_and_quaternion(&object_marker->node_matrix, &instance->translation, &instance->rotation);
|
|
match_assert(
|
|
"c:\\halo\\SOURCE\\models\\models.c",
|
|
785,
|
|
object_marker->node_index>=0 && object_marker->node_index<(node_remapping_table ? node_count : model->nodes.count));
|
|
|
|
matrix4x3_multiply(&node_matrices[object_marker->node_index], &object_marker->node_matrix, &object_marker->matrix);
|
|
if (mirrored_flag)
|
|
{
|
|
negate_vector3d(&object_marker->matrix.left, &object_marker->matrix.left);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void model_build_tangent_matrices(
|
|
struct model *model)
|
|
{
|
|
short geometry_index;
|
|
|
|
for (geometry_index = 0; geometry_index < model->geometries.count; geometry_index++)
|
|
{
|
|
struct model_geometry *geometry = TAG_BLOCK_GET_ELEMENT(
|
|
&model->geometries,
|
|
geometry_index,
|
|
struct model_geometry);
|
|
short part_index;
|
|
|
|
for (part_index = 0; part_index < geometry->parts.count; part_index++)
|
|
{
|
|
model_geometry_part_build_tangent_matrices(TAG_BLOCK_GET_ELEMENT(
|
|
&geometry->parts,
|
|
part_index,
|
|
struct model_geometry_part));
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void model_geometry_part_build_tangent_matrices(
|
|
struct model_geometry_part *part)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* port: a map's model with an index past what it has */
|
|
static void model_data_error(
|
|
struct model const *model,
|
|
char const *problem)
|
|
{
|
|
if (model_data_report_once(model))
|
|
{
|
|
error(
|
|
_error_silent,
|
|
"### ERROR a model (%ld nodes) has a bad %s index; it is skipped",
|
|
model->nodes.count,
|
|
problem);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void render_model(
|
|
long model_index,
|
|
real level_of_detail_pixels,
|
|
real_matrix4x3 const *node_matrices,
|
|
char const *region_permutation_indices,
|
|
real_rgb_color const *change_colors,
|
|
real const *function_values,
|
|
struct render_lighting const *lighting,
|
|
real_point3d const *centroid,
|
|
real radius,
|
|
struct render_model_effect const *model_effect,
|
|
long unique_identifier,
|
|
short forced_shader_permutation_index,
|
|
unsigned long flags)
|
|
{
|
|
struct model *model = model_definition_get(model_index);
|
|
|
|
profile_enter(render_model_section);
|
|
|
|
match_assert("c:\\halo\\SOURCE\\models\\models.c", 82, lighting);
|
|
|
|
if (model->node_list_checksum==CORTANA_MODEL_NODE_LIST_CHECKSUM &&
|
|
TEST_FLAG(global_scenario_get()->flags, _scenario_cortana_hack_bit))
|
|
{
|
|
rasterizer_model_cortana_hack = TRUE;
|
|
}
|
|
else
|
|
{
|
|
rasterizer_model_cortana_hack = FALSE;
|
|
}
|
|
|
|
if (level_of_detail_pixels>=model->detail_cutoff_pixels[0] || TEST_FLAG(flags, _render_model_shadow_bit))
|
|
{
|
|
real_matrix4x3 relative_node_matrices[MAXIMUM_NODES_PER_MODEL];
|
|
struct rasterizer_model_begin_parameters model_parameters;
|
|
short geometry_detail_level_index;
|
|
short node_index;
|
|
/* port: how many regions the permutations passed in hold (an
|
|
object's; the default's otherwise) */
|
|
short region_permutation_count = region_permutation_indices ?
|
|
MAXIMUM_REGIONS_PER_OBJECT :
|
|
MAXIMUM_REGIONS_PER_MODEL;
|
|
/* port: no more nodes than relative_node_matrices holds (a map's count) */
|
|
short node_count = (short)MIN(model->nodes.count, MAXIMUM_NODES_PER_MODEL);
|
|
|
|
if (!region_permutation_indices)
|
|
{
|
|
region_permutation_indices = default_render_model_region_permutation_indices;
|
|
}
|
|
if (!model_effect)
|
|
{
|
|
model_effect = &default_render_model_effect;
|
|
}
|
|
if (!change_colors)
|
|
{
|
|
change_colors = default_render_model_change_colors;
|
|
}
|
|
if (!function_values)
|
|
{
|
|
function_values = default_function_values;
|
|
}
|
|
if (!centroid)
|
|
{
|
|
centroid = &node_matrices->position;
|
|
}
|
|
|
|
if (node_matrices)
|
|
{
|
|
for (node_index = 0; node_index<node_count; node_index++)
|
|
{
|
|
struct model_node *node = TAG_BLOCK_GET_ELEMENT(&model->nodes, node_index, struct model_node);
|
|
|
|
matrix4x3_multiply(
|
|
&node_matrices[node_index],
|
|
&node->runtime_default_inverse_matrix,
|
|
&relative_node_matrices[node_index]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (node_index = 0; node_index<node_count; node_index++)
|
|
{
|
|
relative_node_matrices[node_index] = render.frustum.world_to_view;
|
|
}
|
|
}
|
|
|
|
geometry_detail_level_index = NUMBER_OF_DETAIL_LEVELS_PER_MODEL-1;
|
|
while (geometry_detail_level_index>0 &&
|
|
level_of_detail_pixels<model->detail_cutoff_pixels[geometry_detail_level_index])
|
|
{
|
|
geometry_detail_level_index--;
|
|
}
|
|
if (rasterizer_debug_options.debug_model_lod!=NONE)
|
|
{
|
|
geometry_detail_level_index = PIN(rasterizer_debug_options.debug_model_lod, 0, NUMBER_OF_DETAIL_LEVELS_PER_MODEL-1);
|
|
}
|
|
match_assert(
|
|
"c:\\halo\\SOURCE\\models\\models.c",
|
|
169,
|
|
geometry_detail_level_index>=0 && geometry_detail_level_index<NUMBER_OF_DETAIL_LEVELS_PER_MODEL);
|
|
|
|
if (!TEST_FLAG(flags, _render_model_shadow_bit))
|
|
{
|
|
if (render_model_nodes)
|
|
{
|
|
for (node_index = 0; node_index<node_count; node_index++)
|
|
{
|
|
struct model_node *node = TAG_BLOCK_GET_ELEMENT(&model->nodes, node_index, struct model_node);
|
|
|
|
/* port: and the parent is one the model has (a map's index) */
|
|
if (VALID_INDEX(node->parent_node_index, node_count))
|
|
{
|
|
render_debug_line(
|
|
TRUE,
|
|
&node_matrices[node_index].position,
|
|
&node_matrices[node->parent_node_index].position,
|
|
global_real_argb_white);
|
|
}
|
|
render_debug_matrix(TRUE, &node_matrices[node_index], 0.05f);
|
|
}
|
|
}
|
|
|
|
if (render_model_markers)
|
|
{
|
|
short marker_index;
|
|
|
|
for (marker_index = 0; marker_index<model->markers.count; marker_index++)
|
|
{
|
|
struct model_marker *marker = TAG_BLOCK_GET_ELEMENT(&model->markers, marker_index, struct model_marker);
|
|
short instance_index;
|
|
|
|
for (instance_index = 0; instance_index<marker->instances.count; instance_index++)
|
|
{
|
|
struct model_marker_instance *instance = TAG_BLOCK_GET_ELEMENT(
|
|
&marker->instances,
|
|
instance_index,
|
|
struct model_marker_instance);
|
|
|
|
/* port: and the region and node are ones the model has room
|
|
for (a map's indices) */
|
|
if (instance->region_index<region_permutation_count &&
|
|
instance->node_index<node_count &&
|
|
region_permutation_indices[instance->region_index]==instance->permutation_index)
|
|
{
|
|
real_matrix4x3 marker_matrix;
|
|
|
|
matrix4x3_from_point_and_quaternion(&marker_matrix, &instance->translation, &instance->rotation);
|
|
matrix4x3_multiply(&node_matrices[instance->node_index], &marker_matrix, &marker_matrix);
|
|
render_debug_matrix(FALSE, &marker_matrix, 0.05f);
|
|
render_debug_string_at_point(FALSE, &marker_matrix.position, marker->name, global_real_argb_white);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (render_model_vertex_counts || render_model_index_counts)
|
|
{
|
|
short maximum_actual_detail_level_index = geometry_detail_level_index;
|
|
boolean has_unstripped_parts = FALSE;
|
|
short vertex_count = 0;
|
|
short index_count = 0;
|
|
short region_index;
|
|
real distance;
|
|
|
|
/* port: the regions and permutations render_model_parts draws (a
|
|
map's counts and indices) */
|
|
for (region_index = 0; region_index<MIN(model->regions.count, region_permutation_count); region_index++)
|
|
{
|
|
struct model_region *region = TAG_BLOCK_GET_ELEMENT(&model->regions, region_index, struct model_region);
|
|
char permutation_index = region_permutation_indices[region_index];
|
|
|
|
if (VALID_INDEX(permutation_index, region->permutations.count))
|
|
{
|
|
struct model_region_permutation *permutation = TAG_BLOCK_GET_ELEMENT(
|
|
®ion->permutations,
|
|
permutation_index,
|
|
struct model_region_permutation);
|
|
short actual_detail_level_index;
|
|
short geometry_index;
|
|
|
|
for (actual_detail_level_index = geometry_detail_level_index+1;
|
|
actual_detail_level_index<NUMBER_OF_DETAIL_LEVELS_PER_MODEL;
|
|
actual_detail_level_index++)
|
|
{
|
|
if (permutation->geometry_indices[actual_detail_level_index]!=
|
|
permutation->geometry_indices[geometry_detail_level_index])
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
match_assert("c:\\halo\\SOURCE\\models\\models.c", 247, actual_detail_level_index > 0);
|
|
actual_detail_level_index--;
|
|
match_assert(
|
|
"c:\\halo\\SOURCE\\models\\models.c",
|
|
249,
|
|
(actual_detail_level_index >= 0) && (actual_detail_level_index < NUMBER_OF_DETAIL_LEVELS_PER_MODEL));
|
|
maximum_actual_detail_level_index = MAX(maximum_actual_detail_level_index, actual_detail_level_index);
|
|
|
|
geometry_index = permutation->geometry_indices[geometry_detail_level_index];
|
|
if (VALID_INDEX(geometry_index, model->geometries.count))
|
|
{
|
|
struct model_geometry *geometry = TAG_BLOCK_GET_ELEMENT(&model->geometries, geometry_index, struct model_geometry);
|
|
short part_index;
|
|
|
|
for (part_index = 0; part_index<geometry->parts.count; part_index++)
|
|
{
|
|
struct model_geometry_part *part = TAG_BLOCK_GET_ELEMENT(&geometry->parts, part_index, struct model_geometry_part);
|
|
|
|
vertex_count+= part->vertex_buffer.count;
|
|
switch (part->triangle_buffer.type)
|
|
{
|
|
case _triangle_buffer_type_triangles:
|
|
index_count+= 3*part->triangle_buffer.count;
|
|
has_unstripped_parts = TRUE;
|
|
break;
|
|
case _triangle_buffer_type_precompiled_strip:
|
|
index_count+= part->triangle_buffer.count+2;
|
|
break;
|
|
default:
|
|
match_assert("c:\\halo\\SOURCE\\models\\models.c", 276, !"unreachable");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
distance = fabs(
|
|
render.frustum.world_to_view.forward.k*centroid->x +
|
|
render.frustum.world_to_view.left.k*centroid->y +
|
|
render.frustum.world_to_view.up.k*centroid->z +
|
|
render.frustum.world_to_view.position.z);
|
|
level_of_detail_pixels = distance/render.frustum.projection_world_to_screen.j*level_of_detail_pixels*0.5f;
|
|
if (level_of_detail_pixels>0.0001f)
|
|
{
|
|
real_argb_color const *detail_level_colors[NUMBER_OF_DETAIL_LEVELS_PER_MODEL];
|
|
real_argb_color const *color;
|
|
char string[256];
|
|
real_point3d point;
|
|
|
|
detail_level_colors[0] = global_real_argb_blue;
|
|
detail_level_colors[1] = global_real_argb_green;
|
|
detail_level_colors[2] = global_real_argb_yellow;
|
|
detail_level_colors[3] = global_real_argb_orange;
|
|
detail_level_colors[4] = global_real_argb_red;
|
|
color = detail_level_colors[maximum_actual_detail_level_index];
|
|
if (has_unstripped_parts && (game_time_get()+model_index)%30<15)
|
|
{
|
|
color = global_real_argb_white;
|
|
}
|
|
|
|
csstrcpy(string, "");
|
|
if (render_model_vertex_counts)
|
|
{
|
|
_snprintf(string+csstrlen(string), sizeof(string)-csstrlen(string), "%d", vertex_count);
|
|
}
|
|
if (render_model_vertex_counts && render_model_index_counts)
|
|
{
|
|
_snprintf(string+csstrlen(string), sizeof(string)-csstrlen(string), "/");
|
|
}
|
|
if (render_model_index_counts)
|
|
{
|
|
_snprintf(string+csstrlen(string), sizeof(string)-csstrlen(string), "%d", index_count);
|
|
}
|
|
|
|
set_real_point3d(&point, centroid->x, centroid->y, centroid->z+level_of_detail_pixels);
|
|
render_debug_string_at_point(FALSE, &point, string, color);
|
|
}
|
|
}
|
|
}
|
|
|
|
model_parameters.unique_identifier = unique_identifier;
|
|
model_parameters.lighting = *lighting;
|
|
model_parameters.centroid = *centroid;
|
|
model_parameters.radius = radius;
|
|
model_parameters.effect = *model_effect;
|
|
model_parameters.animation.colors = change_colors;
|
|
model_parameters.animation.values = function_values;
|
|
model_parameters.skinning.node_matrices = relative_node_matrices;
|
|
model_parameters.skinning.node_matrix_count = node_count;
|
|
model_parameters.geometry_flags = 0;
|
|
model_parameters.base_map_scale = model->base_map_scale;
|
|
|
|
if (TEST_FLAG(flags, _render_model_immediate_bit))
|
|
{
|
|
model_parameters.geometry_flags = FLAG(_rasterizer_geometry_no_sort_bit) |
|
|
FLAG(_rasterizer_geometry_no_queue_bit) |
|
|
FLAG(_rasterizer_geometry_no_fog_bit) |
|
|
FLAG(_rasterizer_geometry_no_zbuffer_bit) |
|
|
FLAG(_rasterizer_geometry_sky_bit);
|
|
}
|
|
SET_FLAG(model_parameters.geometry_flags, _rasterizer_geometry_atmospheric_fog_but_no_planar_fog_bit, TEST_FLAG(flags, _render_model_no_planar_fog_bit));
|
|
SET_FLAG(model_parameters.geometry_flags, _rasterizer_geometry_first_person_bit, TEST_FLAG(flags, _render_model_first_person_bit));
|
|
|
|
if (TEST_FLAG(flags, _render_model_shadow_bit))
|
|
{
|
|
rasterizer_environment_shadow_model_begin(&model_parameters);
|
|
}
|
|
else
|
|
{
|
|
rasterizer_model_begin(&model_parameters, FALSE);
|
|
}
|
|
render_model_parts(
|
|
model,
|
|
region_permutation_indices,
|
|
region_permutation_count,
|
|
&model_parameters.skinning,
|
|
unique_identifier,
|
|
geometry_detail_level_index,
|
|
forced_shader_permutation_index,
|
|
flags);
|
|
if (TEST_FLAG(flags, _render_model_shadow_bit))
|
|
{
|
|
rasterizer_environment_shadow_model_end();
|
|
}
|
|
else
|
|
{
|
|
rasterizer_model_end();
|
|
}
|
|
}
|
|
|
|
rasterizer_model_cortana_hack = FALSE;
|
|
|
|
profile_exit(render_model_section);
|
|
|
|
return;
|
|
}
|
|
|
|
/* port: TRUE the first time a map's model, animation or other tag data is
|
|
found bad. The checks run every frame; the report goes out once. */
|
|
boolean model_data_report_once(
|
|
void const *data)
|
|
{
|
|
static void const *reported_data[32];
|
|
static long next_reported_index = 0;
|
|
/* (and no more than this many reports in all: with more bad tags than
|
|
the list holds, each would push another out and be reported again
|
|
every frame) */
|
|
static long report_count = 0;
|
|
long reported_index;
|
|
|
|
for (reported_index = 0; reported_index<(long)NUMBEROF(reported_data); reported_index++)
|
|
{
|
|
if (reported_data[reported_index]==data)
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
if (report_count >= 4 * (long)NUMBEROF(reported_data))
|
|
{
|
|
return FALSE;
|
|
}
|
|
report_count++;
|
|
reported_data[next_reported_index] = data;
|
|
next_reported_index = (next_reported_index+1)%(long)NUMBEROF(reported_data);
|
|
|
|
return TRUE;
|
|
}
|