/* BREAKABLE_SURFACES.C */ /* ---------- headers */ #include "cseries.h" #include "breakable_surfaces.h" #include "collision_bsp_definitions.h" #include "bitmaps/bitmaps.h" #include "cseries/errors.h" #include "effects/material_effect_definitions.h" #include "effects/particles.h" #include "game/game_globals.h" #include "math/geometry.h" #include "math/real_math.h" #include "objects/damage.h" #include "objects/damage_effect_definitions.h" #include "objects/objects.h" #include "saved games/game_state.h" #include "scenario/scenario.h" #include "sound/game_sound.h" #include "structures/structure_bsp_definitions.h" #include "tag_files/tag_groups.h" /* ---------- structures */ struct breakable_surface_globals { boolean enabled; byte breakable_surface_flags[MAXIMUM_STRUCTURE_BSPS_PER_SCENARIO][32]; struct breakable_surface_datum breakable_surfaces[MAXIMUM_STRUCTURE_BSPS_PER_SCENARIO][MAXIMUM_BREAKABLE_SURFACES_PER_MAP]; }; /* ---------- prototypes */ static void breakable_surface_effect( short breakable_surface_index, const struct damage_data *damage_data, long seed_surface_index); /* ---------- globals */ static struct breakable_surface_globals *globals; boolean breakable_surface_effect_enabled = TRUE; /* ---------- public code */ struct breakable_surface_datum *breakable_surface_get( short breakable_surface_index) { match_assert("c:\\halo\\SOURCE\\physics\\breakable_surfaces.c", 61, globals); match_assert("c:\\halo\\SOURCE\\physics\\breakable_surfaces.c", 62, global_structure_bsp_index>=0 && global_structure_bsp_index=0 && breakable_surface_indexbreakable_surfaces[global_structure_bsp_index][breakable_surface_index]; } void breakable_surfaces_initialize( void) { match_assert("c:\\halo\\SOURCE\\physics\\breakable_surfaces.c", 73, !globals); globals = (struct breakable_surface_globals *)game_state_malloc("breakable surface globals", 0, sizeof(struct breakable_surface_globals)); return; } void breakable_surfaces_dispose( void) { return; } void breakable_surfaces_initialize_for_new_map( void) { short bsp_index; match_assert("c:\\halo\\SOURCE\\physics\\breakable_surfaces.c", 90, globals); globals->enabled = TRUE; for (bsp_index = 0; bsp_indexbreakable_surface_flags[bsp_index], NONE, sizeof(globals->breakable_surface_flags[bsp_index])); for (surface_index = 0; surface_index < MAXIMUM_BREAKABLE_SURFACES_PER_MAP; ++surface_index) { globals->breakable_surfaces[bsp_index][surface_index].vitality = 1.f; } } return; } void breakable_surfaces_dispose_from_old_map( void) { return; } void breakable_surfaces_enable( boolean state) { match_assert("c:\\halo\\SOURCE\\physics\\breakable_surfaces.c", 120, globals); globals->enabled = state; return; } void breakable_surfaces_reset( void) { breakable_surfaces_initialize_for_new_map(); return; } byte *breakable_surface_flags_get( void) { match_assert("c:\\halo\\SOURCE\\physics\\breakable_surfaces.c", 138, globals); match_assert("c:\\halo\\SOURCE\\physics\\breakable_surfaces.c", 139, global_structure_bsp_index>=0 && global_structure_bsp_indexbreakable_surface_flags[global_structure_bsp_index]; } boolean breakable_surface_extant( short breakable_surface_index) { boolean result; match_assert("c:\\halo\\SOURCE\\physics\\breakable_surfaces.c", 147, breakable_surface_index==NONE || (breakable_surface_index>=0 && breakable_surface_indexenabled) { if (breakable_surface_index != NONE && damage_data->definition_index != NONE && damage_data->material_type != NONE) { struct breakable_surface_datum *surface = breakable_surface_get(breakable_surface_index); if (surface->vitality > 0.f) { struct material_definition *material = scenario_material_definition_get(damage_data->material_type); if (material) { if (material->breakable_surface.maximum_vitality > 0.f) { struct damage_effect_definition *definition = damage_effect_definition_get(damage_data->definition_index); struct damage_definition *damage = &definition->damage; real damage_amount = real_random_range(damage->damage_lower_bound, damage->damage_upper_bound); real damage_fraction = ((damage_amount - damage->damage_minimum) * damage_data->scale + damage->damage_minimum); damage_fraction *= damage->material_modifiers[damage_data->material_type]; damage_fraction /= material->breakable_surface.maximum_vitality; surface->vitality -= damage_fraction; if (surface->vitality <= 0.f) { BIT_VECTOR_SET_FLAG((long *)breakable_surface_flags_get(), breakable_surface_index, FALSE); breakable_surface_effect(breakable_surface_index, damage_data, seed_surface_index); } } } } } } return; } void breakable_surface_damage_area_of_effect( const struct damage_data *damage_data) { struct structure_bsp *structure_bsp = global_structure_bsp_get(); struct damage_effect_definition *damage_effect_definition = damage_effect_definition_get(damage_data->definition_index); if (globals->enabled && (damage_effect_definition->damage.damage_lower_bound!= 0.f || damage_effect_definition->damage.damage_upper_bound!=0.f)) { short breakable_surface_index; real cutoff_radius = damage_effect_definition->cutoff_radius; if (cutoff_radius > 4.0f) { error(_error_silent, "WARNING: area of effect breakable surface damage with radius %d", cutoff_radius); } for (breakable_surface_index = 0; breakable_surface_index < structure_bsp->breakable_surfaces.count; breakable_surface_index++) { if (breakable_surface_extant(breakable_surface_index)) { struct structure_breakable_surface const *breakable_surface = TAG_BLOCK_GET_ELEMENT(&structure_bsp->breakable_surfaces, breakable_surface_index, struct structure_breakable_surface); real radius; radius = breakable_surface->bounding_radius; radius += cutoff_radius; if (point_in_sphere(&breakable_surface->centroid, &damage_data->epicenter, radius)) { breakable_surface_get(breakable_surface_index)->vitality = 0.f; BIT_VECTOR_SET_FLAG((long *)breakable_surface_flags_get(), breakable_surface_index, FALSE); breakable_surface_effect(breakable_surface_index, damage_data, breakable_surface->collision_surface_index); } } } } return; } /* ---------- private code */ static void breakable_surface_effect( short breakable_surface_index, const struct damage_data *damage_data, long seed_surface_index) { struct breakable_surface const *breakable_surface; short surface_queue_read_index; short surface_queue_write_index; boolean total_bounds_valid; real_rectangle3d total_bounds; long surface_queue[MAXIMUM_BREAKABLE_SURFACE_QUEUE_SIZE]; struct collision_surface const *collision_surface; long surface_index; real_plane3d s_plane; real_point3d origin; real_plane3d surface_plane; real_rectangle2d surface_bounds; real_plane3d t_plane; real_point3d surface_vertices3d[MAXIMUM_VERTICES_PER_COLLISION_SURFACE]; real_point2d surface_vertices2d[MAXIMUM_VERTICES_PER_COLLISION_SURFACE]; real_vector3d t_normal; real_vector3d s_normal; real_point3d position; real_point2d position_2d_test; real_vector3d velocity; struct new_particle_data particle; struct structure_bsp *structure_bsp = global_structure_bsp_get(); struct collision_bsp *collision_bsp = global_collision_bsp_get(); match_assert("c:\\halo\\SOURCE\\physics\\breakable_surfaces.c", 251, damage_data); if (breakable_surface_effect_enabled) { short surface_queue_read_index; long material_type; struct material_definition *material_def; collision_surface = TAG_BLOCK_GET_ELEMENT(&collision_bsp->surfaces, seed_surface_index, struct collision_surface); material_type = TAG_BLOCK_GET_ELEMENT(&structure_bsp->collision_materials, collision_surface->material_index, struct structure_collision_material)->runtime_physics_material_type; material_def = TAG_BLOCK_GET_ELEMENT(&scenario_get_game_globals()->materials, material_type, struct material_definition); breakable_surface = &material_def->breakable_surface; total_bounds_valid = FALSE; surface_queue_read_index = 0; surface_queue_write_index = 0; match_assert("c:\\halo\\SOURCE\\physics\\breakable_surfaces.c", 266, collision_surface->breakable_surface_index==breakable_surface_index); surface_queue[surface_queue_write_index++] = seed_surface_index; while (surface_queue_read_indexsurfaces, surface_index, struct collision_surface); edge_index = surface->first_edge_index; surface_vertex_index = 0; bsp3d_get_plane_from_designator(&collision_bsp->bsp3d, surface->plane_designator, &surface_plane); projection_axis = projection_from_vector3d(&surface_plane.n); projection_sign = projection_sign_from_vector3d(&surface_plane.n, projection_axis); do { struct collision_edge const *collision_edge = TAG_BLOCK_GET_ELEMENT(&collision_bsp->edges, edge_index, struct collision_edge); boolean reverse = (collision_edge->surface_indices[1] == surface_index); struct collision_vertex const *vertex = TAG_BLOCK_GET_ELEMENT(&collision_bsp->vertices, collision_edge->vertex_indices[!reverse], struct collision_vertex); long adjacent_surface_index = collision_edge->surface_indices[!reverse]; if (surface_vertex_index==0) { struct collision_vertex const *next_vertex = TAG_BLOCK_GET_ELEMENT(&collision_bsp->vertices, collision_edge->vertex_indices[reverse], struct collision_vertex); if (surface_index==seed_surface_index) { real_point2d projected_epicenter; project_point3d(&damage_data->epicenter, projection_axis, projection_sign, &projected_epicenter); project_point2d(&projected_epicenter, &surface_plane, projection_axis, projection_sign, &origin); } else { origin = vertex->point; } vector_from_points3d(&vertex->point, &next_vertex->point, &s_normal); normalize3d(&s_normal); cross_product3d(&s_normal, &surface_plane.n, &t_normal); plane3d_from_point_and_normal(&s_plane, &origin, &s_normal); plane3d_from_point_and_normal(&t_plane, &origin, &t_normal); surface_bounds.x1 = plane3d_distance_to_point(&s_plane, &vertex->point); surface_bounds.x0 = surface_bounds.x1; surface_bounds.y1 = plane3d_distance_to_point(&t_plane, &vertex->point); surface_bounds.y0 = surface_bounds.y1; } else { real s = plane3d_distance_to_point(&s_plane, &vertex->point); real t = plane3d_distance_to_point(&t_plane, &vertex->point); surface_bounds.x0 = MIN(s, surface_bounds.x0); surface_bounds.y0 = MIN(t, surface_bounds.y0); surface_bounds.x1 = MIN(s, surface_bounds.x1); surface_bounds.y1 = MIN(t, surface_bounds.y1); } match_assert("c:\\halo\\SOURCE\\physics\\breakable_surfaces.c", 348, surface_vertex_indexpoint; project_point3d(&vertex->point, projection_axis, projection_sign, &surface_vertices2d[surface_vertex_index]); if (total_bounds_valid) { total_bounds.x0 = MIN(total_bounds.x0, vertex->point.x); total_bounds.y0 = MIN(total_bounds.y0, vertex->point.y); total_bounds.z0 = MIN(total_bounds.z0, vertex->point.z); total_bounds.x1 = MAX(total_bounds.x1, vertex->point.x); total_bounds.y1 = MAX(total_bounds.y1, vertex->point.y); total_bounds.z1 = MAX(total_bounds.z1, vertex->point.z); } else { total_bounds.x1 = vertex->point.x; total_bounds.x0 = total_bounds.x1; total_bounds.y1 = vertex->point.y; total_bounds.y0 = total_bounds.y1; total_bounds.z1 = vertex->point.z; total_bounds.z0 = total_bounds.z1; total_bounds_valid = TRUE; } for (surface_index = 0; adjacent_surface_index!=NONE && surface_indexsurfaces, adjacent_surface_index, struct collision_surface); if (adjacent->breakable_surface_index==breakable_surface_index && adjacent->material_index==collision_surface->material_index) { match_assert("c:\\halo\\SOURCE\\physics\\breakable_surfaces.c", 388, surface_queue_write_indexedge_indices[reverse]; ++surface_vertex_index; } while (edge_index!=surface->first_edge_index); for (particle_index = 0; particle_index < breakable_surface->particle_effects.count; ++particle_index) { struct breakable_surface_particle_effect const *particle_effect = TAG_BLOCK_GET_ELEMENT(&breakable_surface->particle_effects, particle_index, struct breakable_surface_particle_effect); if (particle_effect->particle.index!=NONE) { word s_min; word s_max; word t_min; word t_max; word t_index; if (particle_effect->density==0.f) { s_min = (word)ceil(PIN(surface_bounds.x0 / particle_effect->density, -1000.f, 1000.f)); t_min = (word)ceil(PIN(surface_bounds.y0 / particle_effect->density, -1000.f, 1000.f)); s_max = (word)floor(PIN(surface_bounds.x1 / particle_effect->density, -1000.f, 1000.f)); t_max = (word)floor(PIN(surface_bounds.y1 / particle_effect->density, -1000.f, 1000.f)); } else { if (surface_index==seed_surface_index) { t_max = 0; s_max = 0; t_min = 0; s_min = 0; } else { t_min = 1; s_min = 1; t_max = 0; s_max = 0; } } for (t_index = t_min; t_index<=t_max; ++t_index) { short s_index; for (s_index = s_min; s_index<=s_max; ++s_index) { position = origin; { real s_offset = ((real)s_index + real_local_random_range(-0.75f, 0.75f)) * particle_effect->density; real t_offset = ((real)t_index + real_local_random_range(-0.75f, 0.75f)) * particle_effect->density; point_from_line3d(&position, &s_plane.n, s_offset, &position); point_from_line3d(&position, &t_plane.n, t_offset, &position); } project_point3d(&position, projection_axis, projection_sign, &position_2d_test); if (convex_hull2d_test_point(surface_vertex_index, surface_vertices2d, &position_2d_test, 0.f)) { struct damage_breaking_effect_definition const *breaking_effect; real_vector3d outward; real distance; velocity = *global_zero_vector3d; breaking_effect = &damage_effect_definition_get(damage_data->definition_index)->breaking_effect; vector_from_points3d(&position, &damage_data->epicenter, &outward); distance = normalize3d(&outward); if (breaking_effect->outward_radius>0.f) { real factor = PIN(1.f - distance / breaking_effect->outward_radius, 0.f, 1.f); if (breaking_effect->outward_exponent != 0.f) { factor = power(factor, breaking_effect->outward_exponent); } factor *= breaking_effect->outward_velocity; point_from_line3d((real_point3d *)&velocity, &outward, factor, (real_point3d *)&velocity); } if (breaking_effect->forward_radius > 0.f) { real factor = PIN(1.f - distance / breaking_effect->forward_radius, 0.f, 1.f); if (breaking_effect->forward_exponent != 0.f) { factor = power(factor, breaking_effect->forward_exponent); } factor *= breaking_effect->forward_velocity; point_from_line3d((real_point3d *)&velocity, &damage_data->direction, factor, (real_point3d *)&velocity); } if (particle_effect->velocity_scale_upper_bound > 0.f) { real scale = real_local_random_range(particle_effect->velocity_scale_lower_bound, particle_effect->velocity_scale_upper_bound); scale_vector3d(&velocity, scale, &velocity); } particle.definition_index = particle_effect->particle.index; particle.object_index = NONE; particle.node_index = NONE; particle.local_player_index = NONE; particle.attached_to_local_player = FALSE; particle.dont_draw_first_person = FALSE; particle.dont_draw_third_person = FALSE; particle.position = position; particle.velocity = velocity; particle.direction = velocity; particle.initial_impulse = *global_zero_vector3d; particle.rotation = real_local_random_range(0.f, 2*M_PI); particle.angular_velocity = real_local_random_range(particle_effect->angular_velocity_lower_bound, particle_effect->angular_velocity_upper_bound); particle.radius = real_local_random_range(particle_effect->radius_lower_bound, particle_effect->radius_upper_bound); rgb_colors_interpolate( &particle.color.rgb, (particle_effect->flags & FLAG(_breakable_surface_particle_effect_color_interpolate_in_hsv_bit) | FLAG(_breakable_surface_particle_effect_color_interpolate_along_farthest_hue_path_bit) ) != 0, &particle_effect->tint_lower_bound.rgb, &particle_effect->tint_upper_bound.rgb, real_local_random() ); particle.color.alpha = PIN(particle_effect->tint_lower_bound.alpha + real_local_random() * (particle_effect->tint_upper_bound.alpha - particle_effect->tint_lower_bound.alpha), 0.f, 1.f); if (normalize3d(&particle.direction)==0.f) { local_random_direction3d(&particle.direction); } particle_new(&particle); } } } } } } if (breakable_surface->sound.index != NONE && total_bounds_valid) { struct sound_location sound_location; set_real_point3d(&sound_location.position, (total_bounds.x0 + total_bounds.x1) * 0.5f, (total_bounds.y0 + total_bounds.y1) * 0.5f, (total_bounds.z0 + total_bounds.z1) * 0.5f); sound_location.forward = *global_forward3d; sound_location.translational_velocity = *global_zero_vector3d; sound_location.game_location = damage_data->location; unattached_impulse_sound_new(breakable_surface->sound.index, &sound_location, 1.f); } } return; }