Files
Stefan MaierandClaude Opus 5.5 b6a943f1a6 Merge upstream 2026-10-10: network version 25, touch controls, in-game settings, FOV options, voice chat, votekick
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>
2026-10-10 08:51:46 +02:00

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(&region->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(
&region->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,
&centroid);
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,
&centroid,
/* 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(
&region->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;
}