mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-10 04:31:34 -10:00
* Shadow overhaul round 2: PCSS, dynamic cascades, cockpit/instanced shadows Squashed snapshot of lafiel/shadow_overhaul_2's continuation work (d3922aa87..010f9d2d9), which has no separate upstream PR of its own. * Add Vulkan rendering backend Squashed snapshot of the Vulkan backend as vendored into this branch (ce2d1e85f..6ce539463, authored by Mara van der Laan with build/CI fixes by Taylor Richards). This work has its own upstream PR #7553 (notimaginative:vulkan-pr-new) and is not otherwise original to true-hdr. * Add true HDR output, RT shadows, and PCSS shadows for Vulkan Squashed snapshot of true-hdr's own integration work: true HDR framebuffer output, wiring the shadow overhaul into the Vulkan backend (raytraced + cascaded shadow maps), logging refactors, log filter UI, and CI/build fixes. * refactor: update Vulkan logging calls to use `nprintf` for scoped logging * fix: correct shadow cascade count in Vulkan logging calls * fix: fall back to PATH-resolved ccache when CCACHE_PATH is stale CI hardcodes /usr/local/bin/ccache, but macOS runners on Apple Silicon hosts install Homebrew (and ccache) under /opt/homebrew instead, causing all four Mac configs to fail with "No such file or directory". * Fix vulkan texture readback to correctly handle transparent pipelines * Put the pixel swizzles into a util file * OOB safety check * Fix fog handling * Fix MSAA in Vulkan * fix: enable SMAA for vulkan * adapt the post-processing AA shaders to hdr * fix clang-tidy warnings: concatenate nested namespaces, static methods pixel_swizzle.{h,cpp}: collapse `namespace graphics { namespace util {` into `namespace graphics::util {` (modernize-concat-nested-namespaces). VulkanState.h/VulkanDraw.h: getClearColor()/setClearColor() only touch gr_screen and other globals, no instance state, so make them static (readability-convert-member-functions-to-static) and drop the top-level const on getClearColor()'s return type (readability-const-return-type). * fix clang-tidy warning * Extend raytraced shadows to all directional lights RT shadows previously only shadowed the first directional light in the scene, even though multi-sun missions push more than one directional light into the engine. The TLAS raytraced shadows query against is already light-agnostic, so this extends both the deferred lighting pass and the forward MODEL shader to shadow every directional light (capped by a new Max Raytraced Shadow Lights option, configurable via the in-game Options menu or the Lab's Scene rendering options). Cascaded shadow maps are untouched: they're built for a single light only, so the deferred pass still limits itself to one shadowed directional light whenever CSM (not raytracing) is the active method. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_016DA6JHJ3twz2oHGQcZ1Udc * refactor: improve shader flag handling and render target finalization in Vulkan backend - Consolidate shader flag setup logic in VulkanPostProcessingLighting to simplify configuration. - Fix dead field usage in VulkanDraw by ensuring correct image view selection for environment maps. - Add mip chain regeneration in VulkanRenderer::endRenderTarget to ensure correct mip levels for cubemaps. * fix: ensure correct mip chain initialization in cubemap rendering - Add mip chain generation to VulkanDraw for cubemaps with multiple mip levels. - Fix potential issue with uninitialized mip levels above level 0 in deferred-f.sdr sampling paths. * fix: don't crash on startup when environment locale is unavailable std::locale("") pulls in the process's environment locale categories (LC_CTYPE, LC_MONETARY, LC_PAPER, etc.), and throws std::runtime_error if any of them names a locale not installed on the host (e.g. mismatched regional-format overrides). Since SCP_default_locale is constructed during static initialization, before main() and before any of our own exception handling exists, an uncaught throw here aborted the whole process before the game even started. Fall back to the classic "C" locale instead, which is sufficient since this locale is only ever used for ASCII case-folding (SCP_toupper/tolower/isdigit). Co-Authored-By: Claude Sonnet 5 <[email protected]> * `feat: add raytraced shadows for local lights and optimize TLAS management` * Make the Commandline authoritative for -res and -vulkan / -opengl * address BMagnu's comments on the original PR * update prebuilt version hash to 62c89ef6 * refactor: standardize graphics API handling and document Vulkan limitations in qtFRED - Updated `AboutDialogModel.cpp` to use `GraphicsAPI` enum for better clarity. - Ensured qtFRED falls back to OpenGL when Vulkan is requested. - Added detailed documentation in `qtfred/README.md` regarding Vulkan backend limitations and requirements. * chore: include cmdline library in main and mission management modules * docs: add known issue for blank render viewport under Wayland in README.md * fix CI issues * chore: add `GR_DEBUG_SCOPE` markers across Vulkan post-processing and rendering paths for improved debugging * feat: improve Vulkan HDR pipeline with separate encode paths for SDR and HDR10 - Added an HDR10-specific output encode pass in `VulkanRenderer` and `VulkanPostProcessor` that applies PQ/BT.2020 transfer. - Introduced an optimized SDR fallback path with direct blit support for devices that can skip shader draws. - Updated `m_sceneLdrCompressed` to provide [0,1]-clamped proxies for HDR edge detection. - Refactored `copyImageToImage` and added `blitImageToImage` to allow format conversions and optimized transfers. - Replaced `hdr_mode` with explicit HDR10 flags and data structures for clearer state handling. * refactor: encapsulate SMAA processing and refactor VulkanPostProcessor - Moved SMAA-specific logic into `VulkanSMAA` class for better separation of concerns. - Simplified `initSMAA`, `shutdownSMAA`, and `executeSMAA` logic by leveraging `VulkanLDR` and `PostProcessContext` abstractions. - Improved initialization and cleanup code with clearer resource lifecycle management. - Updated debug markers and messages to reference `VulkanSMAA`. - Enhanced flexibility of fullscreen triangle rendering API with additional parameters (`sampleCount`, `bindGlobalSet`). * Update Vulkan porting credits in AboutDialogModel * fix CI issues again * fix: correct Y-flip in viewport setup for Vulkan light volumes - Adjusted viewport height to negative value to account for G-buffer render pass's negative Y-axis convention. - Ensured proper alignment of point and tube light volumes with actual light positions. * feat: add user-adjustable gamma correction to SDR and HDR10 output paths - Added gamma correction to HDR10 path, applying user settings before PQ/BT.2020 encode. - Replaced the SDR blit-only path with an encode pass that applies gamma correction, ensuring consistent behavior across devices. - Refactored VulkanPostProcessor and associated shaders to support gamma slider adjustments for both SDR and HDR output. * fix: ensure proper uniform and cull mode setup for Vulkan sphere rendering - Added uniform buffer setup and default material uniforms in `vulkan_draw_sphere`. - Disabled face culling in material definition for sphere rendering to align behavior with OpenGL. * Ensure memory flushes after UBO updates and add explicit pipeline barrier for MoltenVK compatibility. * fix: add clear pass for SMAA edge detection to prevent persistent artifacts * feat: add Vulkan debug-utils messenger, opt-in sync validation, frame diagnostics Groundwork for the renderer-review fix series (.agents/plans/ vulkan-renderer-review-fixes.md, Phase 0): - Register a VK_EXT_debug_utils messenger (preferred) instead of only the deprecated VK_EXT_debug_report callback, which is kept as a fallback for loaders without debug-utils. The messenger config is also chained into instance creation so vkCreateInstance-time messages are captured. Validation errors/warnings now go to the main log via mprintf. - New -gr_sync_validation cmdline flag (implies -gr_debug): chains VkValidationFeaturesEXT with synchronization validation into instance creation when VK_LAYER_KHRONOS_validation is available. This is the regression net for the synchronization fixes in the following commits. - Frame stats now report descriptor sets/writes allocated per frame and the total pipeline count (descriptor-manager counters; DescriptorWriter::flush moved out-of-line to report into the manager). Co-Authored-By: Claude Fable 5 <[email protected]> * refactor: unify Vulkan render-pass begins, clamp in-pass clears, add per-frame UBO ring Phase 1 of the renderer-review fix series: shared helpers that the correctness fixes build on. - New VulkanRenderer::beginTrackedRenderPass(PassBeginDesc): every render-pass begin on the frame command buffer now records vkCmdBeginRenderPass and updates the state tracker (pass, color attachment count, sample count, render area, viewport) in one place. Converted: setupFrame, beginSceneRendering (both branches), resumeSceneRendering, endSceneRendering, beginRenderTarget (viewport kept for gr_set_viewport), resumeSwapChainPass, plus new resumeScenePassAfterCopy() shared by copyEffectTexture/copySceneDepthForParticles and rebeginSwapChainPassAfterReadback() shared by both readbackFramebuffer exits (~200 lines of duplicated begin/clear/viewport blocks removed). - The state tracker records the active render area; all four vkCmdClearAttachments sites clamp their rects against it and skip empty results. Previously gr_screen-sized clear rects could exceed a smaller off-screen render target's render area, which is invalid API usage. - New PerFrameUboRing utility (VulkanPerFrameUbo.h/.cpp): a persistently mapped uniform buffer with one slot region per frame in flight and a self-flushing alloc(). Used by the next commit to fix the cross-frame UBO races. Deliberate behavior deltas (all safe-direction): the helper always sets colorAttachmentCount/sampleCount (some sites previously inherited stale values), and resumeSwapChainPass/resumeSceneRendering now also reset the standard flipped viewport instead of leaving the previous one. Co-Authored-By: Claude Fable 5 <[email protected]> * fix: eliminate Vulkan cross-frame UBO/buffer races, make gr_sync honest Phase 2 of the renderer-review fix series: with two frames in flight, several host-visible resources were rewritten by the CPU while the previous frame's GPU work could still be reading them. - The post-processing scratch UBO is now a PerFrameUboRing (per-frame-in- flight slot regions, persistently mapped, self-flushing alloc). All the per-pass map/unmap/cursor bookkeeping in bloom/LDR/fog/SMAA is deleted; the cursor resets exactly once per frame via the new VulkanPostProcessor::beginFrame() hook in setupFrame. This also fixes two latent bugs: (a) fog consumed scratch slots mid-scene and bloom's later same-frame cursor reset overwrote them before submission whenever fog+bloom were both active; (b) volumetric_fog_data (288 bytes) overflowed the 256-byte slots - the memcpy spilled into the neighboring slot and the shader's bound range ended before the aspect/fov/noise fields. Slots are now 512 bytes (static_assert at the fog call site) and the per-frame budget is 32 slots. - The dedicated tonemap, output-encode, and MSAA-resolve UBOs (each a single instance rewritten per frame, i.e. racy; the MSAA one also never flushed) are deleted entirely - those passes build their structs on the stack and allocate from the scratch ring. - Static-buffer rewrites orphan instead of racing: getVkBuffer() stamps a last-used frame number, and a full-content updateBufferData() on a Static buffer used within the last MAX_FRAMES_IN_FLIGHT frames defers the old VkBuffer to the deletion queue and writes a fresh one. Offset writes (GPU-heap appends to fresh regions) and PersistentMapping buffers (the engine holds their mapped pointer and syncs via gr_sync) are exempt. - gr_sync_wait now honors its contract: VulkanRenderFrame::waitForFinish takes a timeout and reports expiry without consuming the fence; VulkanRenderer::waitForFrame returns false for fences taken during the still-recording frame (previously it waited on a stale or idle slot and reported success) and resolves the frame->slot mapping correctly. This makes UniformBufferManager's segment-fence throttling actually protective on Vulkan for the first time. Co-Authored-By: Claude Fable 5 <[email protected]> * Refactor framebuffer and target lifecycle: centralized creation/resizing logic, improved resource management across Vulkan post-processing subsystems. * Improve Vulkan cross-frame sync with explicit access masks and validation updates - Unified Vulkan validation flag handling in setup, separating engine debug output from synchronization validation. - Specified access masks and stage flags to address cross-frame WRITE_AFTER_WRITE vulnerabilities, notably in G-buffer, post-processing, and MSAA resolve paths. - Adjusted subpass dependencies and image layout transitions to prevent read-after-write and write-after-write hazards, detected using `-gr_sync_validation`. - Fixed uniform buffer and barrier transitions to ensure proper ordering in render passes across frames. * Refactor Vulkan render-pass dependencies and sync stages for clarity and future testing - Clarified dependency logic for cross-pass and cross-frame synchronization, ensuring explicit stage and access flag comments match observed behavior. - Adjusted subpass dependencies to decouple render-pass compatibility definitions from dependency assumptions. - Highlighted MoltenVK-specific issues and retained workarounds with notes for re-testing on macOS hardware. - Updated synchronization barriers and validation notes to address historical inconsistencies across platforms. * Remove transfer queue support; consolidate all operations into the graphics queue - Removed transfer queue handling, simplifying buffer and queue logic to use the graphics queue for all transfer operations. - Updated buffer sharing modes and removed related code paths and parameters for transfer queue families. - Introduced exclusive buffer access, noting async transfer and queue ownership transfers are future work. - Limited enabled device features to only those actively used by the renderer, reducing unnecessary feature requests (e.g., robustBufferAccess). * Refactor frame advancement and descriptor management in VulkanRenderer - Removed `endFrame()` from `VulkanDescriptorManager`, replacing it with `setCurrentFrame(frameIndex)` for explicit frame synchronization. - Centralized frame index advancement within `VulkanRenderer` to ensure consistency across components. - Invalidated cached descriptor set bindings at render-pass boundaries to handle raw recorder staleness correctly. - Optimized pipeline descriptor rebind logic by removing redundant clears during pipeline changes. * Refactor `PipelineConfig::hash` and Global descriptor set management for improved hashing and memoization efficiency - Replaced inefficient shift-based hashing in `PipelineConfig::hash` with a robust `hashCombine` strategy to minimize bucket collisions. - Introduced `maskBits` helper to streamline bitmask creation for boolean fields. - Implemented memoization for Global (Set 0) descriptor sets, reducing redundant rebuilds across frames and improving performance. - Added dirty state tracking (`m_globalSetDirty`) and invalidation logic for Global set updates following relevant input changes (e.g., TLAS or UBO modifications). * Refactor Vulkan post-processing output logic: extracted shared `encodeToSwapChainPass` method to streamline HDR and SDR output paths. * Optimize Vulkan descriptor set management with per-frame memoization - Added memoization for Material (Set 1) and PerDraw (Set 2) descriptor sets to reuse cached sets across frames if inputs remain unchanged. - Implemented precise input tracking for Material and PerDraw descriptor sets to detect and rebuild only when necessary. - Reduced redundant Vulkan descriptor allocations and writes, improving performance on repeated or batched draw calls. * Fix Vulkan access masks for G-buffer and scene pass transitions - Added `eColorAttachmentRead` to access masks in multiple barriers to ensure proper synchronization for load operations (e.g., `loadOp=eLoad`) across render passes. - Prevented `READ_AFTER_WRITE` synchronization validation issues in deferred and multi-pass rendering scenarios. - Updated comments to clarify the reasoning behind the access mask changes and load dependencies. * Refactor Vulkan resource management and indexed draw logic - Consolidated destruction queue methods in `VulkanDeletionQueue` using a shared `queueHandle` template to reduce redundancy. - Introduced `ModelDrawParams` structure and `computeModelDrawParams` function to unify indexed draw parameters for models and shadows. - Simplified draw logic by eliminating repeated calculation of indexed draw parameters and improving code reuse. * Add `releaseAnimationSlotRef` for ref-count handling of shared animation texture arrays - Introduced `releaseAnimationSlotRef` to manage reference counting for shared animation texture-array slots, preventing premature destruction of shared resources. - Updated `flushTextures` and related methods to integrate `releaseAnimationSlotRef` for shared slot cleanup. - Refactored staging buffer creation into `createStagingBuffer` for 3D texture uploads and shared path consistency. - Enhanced texture upload logic with improved tracking and logging for re-upload cases, aiding preloading gap analysis. - Added instrumentation for on-demand mid-frame texture uploads in `FrameStats`. * Refactor Vulkan resource and texture management, extend debug logging - Updated smart pointer usage for consistency across Vulkan resource managers (`std::make_unique`). - Removed redundant `cubeImageView` member and related cleanup logic; unified sampling view management. - Added per-instance debug log counters (avoiding static state) for Vulkan texture uploads and bindings, improving visibility during initialization and run-time. - Optimized `VulkanRenderer` property/feature getters by caching physical device properties and features during device creation. - Improved memory region flushing logic for out-of-date offsets, aligning with Vulkan Memory Allocator behavior. - Refactored render pass state tracking to reapply dynamic state reliably after mid-frame render passes. - Enhanced validation and error handling for mismatched update texture formats. * fix CI build warning * Fix on-demand YUV texture locking and set alpha uniform for movie rendering in Vulkan - Corrected bitmap lock type from `BMP_TEX_OTHER` to `BMP_AABITMAP` for YUV textures during on-demand loading. - Updated movie rendering to bind alpha from the `MovieData` UBO (set 2, binding 4) for Vulkan, aligning behavior with OpenGL implementation. * revert inadvertent changes introduced by a squash commit * address ci issues * remove wip stuff from code comments that make no sense to people who aren't me * Remove unused LD_LIBRARY_PATH export and fix shader shadow map sampling. * Refactor batched rendering uniform setup in Vulkan - Removed unused fields from `genericData` uniform block, simplifying shader definitions. - Updated Vulkan material uniform handling to align with OpenGL's batched rendering logic. - Introduced `batched_data` struct for streamlined UBO updates, improving maintainability and clarity. * Add `-hdr` command-line option for HDR nits configuration - Introduced `-hdr` parameter to set paper white and peak luminance in nits, overriding in-game HDR settings. - Added input validation with error feedback for incorrect format. * Remove unused `MODEL_SDR_FLAG_SHADOW_MAP` macro from shader flags * clean up unrelated changes * Invalidate Vulkan state tracker bindings after external command buffer modifications - Added `invalidateExternalBindings()` to `VulkanStateTracker` to handle stale pipelines, descriptors, and dynamic states caused by external Vulkan commands (e.g., ImGui renders mid-pass). - Updated ImGui rendering flow to call `invalidateExternalBindings()` post-draw, ensuring consistent pipeline state for subsequent engine renders. - Adjusted image usage flags to account for `eTransferSrc` in texture readbacks. - Fixed barrier `dstAccessMask` and pipeline stage flags to ensure proper synchronization for image transitions. * Add a few more generically useful lines to the log * Simplify Qt deployment logic and improve handling for system-installed Qt on Linux * add cmdline headers to make qtFred compile * use VK_KHR_synchronization2 for sync and barrier purposes * Simplify and clean up Qt deployment logic in CMake for qtfred * update get_bitmap_from_texture to match the new API * Enhance `-hdr` command-line option to force HDR output and support optional nits configuration with improved input validation and overrides. * Improve Vulkan robustness, add device feature checks, and refine memory/logging. - Added depth clamping support gated by device feature checks, with fallback where unsupported. - Improved deferred lighting UBO overflow handling with user-accessible warnings and buffer size adjustments. - Enhanced Vulkan logging for validation, allocation, and debugging: - Automatic logging for descriptor pool exhaustion/fragmentation. - Vertex format and resource destruction checks. - Notifications for generic layout transitions and volumetric texture uploads. - Consolidated shader logic to ensure Vulkan and OpenGL parity. * Remove HDR10 encode shader and consolidate gamma correction logic for SDR and HDR10. Replace `Verify` assertions with `Assert` * Add braces to conditional in object lighting logic for clarity * Refine Vulkan memory allocation checks with `Verification` for improved error handling and messaging. * Refine VulkanBuffer resize logic to preserve contents with CPU-side copy and add safeguards for non-coherent memory scenarios. * Add detailed comments explaining descriptor array capacity constants in VulkanDescriptorManager * Gate shadow support on depth clamp feature availability and clarify its necessity with improved comments. --------- Co-authored-by: BMagnu <[email protected]> Co-authored-by: Mara van der Laan <[email protected]> Co-authored-by: Claude <[email protected]>
3248 lines
83 KiB
C++
3248 lines
83 KiB
C++
/*
|
|
* Copyright (C) Volition, Inc. 1999. All rights reserved.
|
|
*
|
|
* All source code herein is the property of Volition, Inc. You may not sell
|
|
* or otherwise commercially exploit the source or things you created based on the
|
|
* source.
|
|
*
|
|
*/
|
|
|
|
|
|
|
|
#define MODEL_LIB
|
|
|
|
#include "bmpman/bmpman.h"
|
|
#include "cmdline/cmdline.h"
|
|
#include "debugconsole/console.h"
|
|
#include "gamesequence/gamesequence.h"
|
|
#include "gamesnd/gamesnd.h"
|
|
#include "globalincs/alphacolors.h"
|
|
#include "globalincs/linklist.h"
|
|
#include "graphics/2d.h"
|
|
#include "graphics/util/GPUMemoryHeap.h"
|
|
#include "io/key.h"
|
|
#include "io/timer.h"
|
|
#include "math/fvi.h"
|
|
#include "math/staticrand.h"
|
|
#include "mission/missionparse.h"
|
|
#include "model/modelrender.h"
|
|
#include "model/modelsinc.h"
|
|
#include "nebula/neb.h"
|
|
#include "parse/parselo.h"
|
|
#include "particle/particle.h"
|
|
#include "render/3dinternal.h"
|
|
#include "ship/ship.h"
|
|
#include "ship/shipfx.h"
|
|
#include "tracing/Monitor.h"
|
|
#include "tracing/tracing.h"
|
|
#include "utils/Random.h"
|
|
#include "weapon/shockwave.h"
|
|
|
|
#include <climits>
|
|
|
|
|
|
float model_radius = 0;
|
|
|
|
// Some debug variables used externally for displaying stats
|
|
#ifndef NDEBUG
|
|
int modelstats_num_polys = 0;
|
|
int modelstats_num_polys_drawn = 0;
|
|
int modelstats_num_verts = 0;
|
|
int modelstats_num_sortnorms = 0;
|
|
int modelstats_num_boxes = 0;
|
|
#endif
|
|
|
|
typedef struct model_light {
|
|
ubyte r, g, b;
|
|
} model_light;
|
|
|
|
// a lighting object
|
|
typedef struct model_light_object {
|
|
model_light *lights;
|
|
|
|
int objnum;
|
|
int skip;
|
|
int skip_max;
|
|
} model_light_object;
|
|
|
|
struct bsp_vertex
|
|
{
|
|
vec3d position;
|
|
vec3d normal;
|
|
uv_pair tex_coord;
|
|
ubyte r, g, b, a;
|
|
};
|
|
|
|
struct bsp_polygon
|
|
{
|
|
uint Start_index;
|
|
uint Num_verts;
|
|
|
|
int texture;
|
|
};
|
|
|
|
class bsp_polygon_data
|
|
{
|
|
SCP_vector<vec3d> Vertex_list;
|
|
SCP_vector<vec3d> Normal_list;
|
|
|
|
SCP_vector<bsp_vertex> Polygon_vertices;
|
|
SCP_vector<bsp_polygon> Polygons;
|
|
|
|
ubyte* Lights;
|
|
|
|
int Num_polies[MAX_MODEL_TEXTURES];
|
|
int Num_verts[MAX_MODEL_TEXTURES];
|
|
|
|
int Num_flat_polies;
|
|
int Num_flat_verts;
|
|
|
|
int bsp_data_size;
|
|
|
|
void process_bsp(int offset, ubyte* bsp_data);
|
|
void process_defpoints(int off, ubyte* bsp_data);
|
|
void process_sortnorm(int offset, ubyte* bsp_data);
|
|
void process_sortnorm2(int offset, ubyte* bsp_data);
|
|
void process_tmap(int offset, ubyte* bsp_data);
|
|
void process_tmap2(int offset, ubyte* bsp_data);
|
|
void process_flat(int offset, ubyte* bsp_data);
|
|
public:
|
|
bsp_polygon_data(ubyte* bsp_data, int bsp_data_size);
|
|
|
|
int get_num_triangles(int texture);
|
|
int get_num_lines(int texture);
|
|
|
|
void generate_triangles(int texture, vertex *vert_ptr, vec3d* norm_ptr);
|
|
void generate_lines(int texture, vertex *vert_ptr);
|
|
|
|
SCP_set<int> get_textures_used() const;
|
|
void replace_textures_used(const SCP_map<int, int>& replacementMap);
|
|
};
|
|
|
|
/**
|
|
* @brief Vertex structure for passing data to the GPU
|
|
*/
|
|
struct interp_vertex {
|
|
uv_pair uv;
|
|
vec3d normal;
|
|
vec4 tangent;
|
|
float modelId;
|
|
vec3d pos;
|
|
};
|
|
|
|
// -----------------------
|
|
// Local variables
|
|
//
|
|
|
|
static uint Num_interp_verts_allocated = 0;
|
|
vec3d **Interp_verts = NULL;
|
|
static vertex *Interp_points = NULL;
|
|
static uint Interp_num_verts = 0;
|
|
|
|
static float Interp_box_scale = 1.0f; // this is used to scale both detail boxes and spheres
|
|
|
|
// -------------------------------------------------------------------
|
|
// lighting save stuff
|
|
//
|
|
|
|
model_light_object Interp_lighting_temp;
|
|
model_light_object *Interp_lighting = &Interp_lighting_temp;
|
|
int Interp_use_saved_lighting = 0;
|
|
int Interp_saved_lighting_full = 0;
|
|
//
|
|
// lighting save stuff
|
|
// -------------------------------------------------------------------
|
|
|
|
|
|
static uint Num_interp_norms_allocated = 0;
|
|
static vec3d **Interp_norms = NULL;
|
|
static ubyte *Interp_light_applied = NULL;
|
|
static uint Interp_num_norms = 0;
|
|
static ubyte *Interp_lights;
|
|
|
|
// Stuff to control rendering parameters
|
|
static color Interp_outline_color;
|
|
static int Interp_detail_level_locked = -1;
|
|
static uint Interp_flags = 0;
|
|
|
|
// If non-zero, then the subobject gets scaled by Interp_thrust_scale.
|
|
int Interp_thrust_scale_subobj = 0;
|
|
float Interp_thrust_scale = 0.1f;
|
|
static float Interp_thrust_scale_x = 0.0f;//added -bobboau
|
|
static float Interp_thrust_scale_y = 0.0f;//added -bobboau
|
|
|
|
static int Interp_thrust_bitmap = -1;
|
|
static int Interp_thrust_glow_bitmap = -1;
|
|
static float Interp_thrust_glow_noise = 1.0f;
|
|
static bool Interp_afterburner = false;
|
|
|
|
// Bobboau's thruster stuff
|
|
static int Interp_secondary_thrust_glow_bitmap = -1;
|
|
static int Interp_tertiary_thrust_glow_bitmap = -1;
|
|
static int Interp_distortion_thrust_bitmap = -1;
|
|
static float Interp_thrust_glow_rad_factor = 1.0f;
|
|
static float Interp_secondary_thrust_glow_rad_factor = 1.0f;
|
|
static float Interp_tertiary_thrust_glow_rad_factor = 1.0f;
|
|
static float Interp_distortion_thrust_rad_factor = 1.0f;
|
|
static float Interp_distortion_thrust_length_factor = 1.0f;
|
|
static float Interp_thrust_glow_len_factor = 1.0f;
|
|
static vec3d Interp_thrust_rotvel = ZERO_VECTOR;
|
|
static bool Interp_draw_distortion = true;
|
|
|
|
// Bobboau's warp stuff
|
|
static float Interp_warp_scale_x = 1.0f;
|
|
static float Interp_warp_scale_y = 1.0f;
|
|
static float Interp_warp_scale_z = 1.0f;
|
|
|
|
// if != -1, use this bitmap when rendering ship insignias
|
|
static int Interp_insignia_bitmap = -1;
|
|
|
|
// if != -1, use this bitmap when rendering with a forced texture
|
|
static int Interp_forced_bitmap = -1;
|
|
|
|
// our current level of detail (LOD)
|
|
int Interp_detail_level = 0;
|
|
|
|
// forward references
|
|
int model_should_render_engine_glow(int objnum, int bank_obj);
|
|
|
|
void model_deallocate_interp_data()
|
|
{
|
|
if (Interp_verts != nullptr) {
|
|
vm_free(Interp_verts);
|
|
Interp_verts = nullptr;
|
|
}
|
|
|
|
if (Interp_points != nullptr) {
|
|
vm_free(Interp_points);
|
|
Interp_points = nullptr;
|
|
}
|
|
|
|
if (Interp_norms != nullptr) {
|
|
vm_free(Interp_norms);
|
|
Interp_norms = nullptr;
|
|
}
|
|
|
|
if (Interp_light_applied != nullptr) {
|
|
vm_free(Interp_light_applied);
|
|
Interp_light_applied = nullptr;
|
|
}
|
|
|
|
if (Interp_lighting_temp.lights != nullptr) {
|
|
vm_free(Interp_lighting_temp.lights);
|
|
Interp_lighting_temp.lights = nullptr;
|
|
}
|
|
|
|
Num_interp_verts_allocated = 0;
|
|
Num_interp_norms_allocated = 0;
|
|
}
|
|
|
|
extern void model_collide_allocate_point_list(int n_points);
|
|
extern void model_collide_free_point_list();
|
|
|
|
void model_allocate_interp_data(uint n_verts, uint n_norms)
|
|
{
|
|
static ubyte dealloc = 0;
|
|
|
|
if (!dealloc) {
|
|
atexit(model_deallocate_interp_data);
|
|
atexit(model_collide_free_point_list);
|
|
dealloc = 1;
|
|
}
|
|
|
|
Assert( (n_verts || Num_interp_verts_allocated) && (n_norms || Num_interp_norms_allocated) );
|
|
|
|
if (n_verts > Num_interp_verts_allocated) {
|
|
if (Interp_verts != NULL) {
|
|
vm_free(Interp_verts);
|
|
Interp_verts = NULL;
|
|
}
|
|
// Interp_verts can't be reliably realloc'd so free and malloc it on each resize (no data needs to be carried over)
|
|
Interp_verts = (vec3d**) vm_malloc( n_verts * sizeof(vec3d *) );
|
|
|
|
Interp_points = (vertex*) vm_realloc( Interp_points, n_verts * sizeof(vertex) );
|
|
|
|
Num_interp_verts_allocated = n_verts;
|
|
|
|
// model collide needs a similar size to resize it based on this new value
|
|
model_collide_allocate_point_list( n_verts );
|
|
}
|
|
|
|
if (n_norms > Num_interp_norms_allocated) {
|
|
if (Interp_norms != NULL) {
|
|
vm_free(Interp_norms);
|
|
Interp_norms = NULL;
|
|
}
|
|
// Interp_norms can't be reliably realloc'd so free and malloc it on each resize (no data needs to be carried over)
|
|
Interp_norms = (vec3d**) vm_malloc( n_norms * sizeof(vec3d *) );
|
|
|
|
// these next two lighting things aren't values that need to be carried over, but we need to make sure they are 0 by default
|
|
if (Interp_light_applied != NULL) {
|
|
vm_free(Interp_light_applied);
|
|
Interp_light_applied = NULL;
|
|
}
|
|
|
|
if (Interp_lighting_temp.lights != NULL) {
|
|
vm_free(Interp_lighting_temp.lights);
|
|
Interp_lighting_temp.lights = NULL;
|
|
}
|
|
|
|
Interp_light_applied = (ubyte*) vm_malloc( n_norms * sizeof(ubyte) );
|
|
Interp_lighting_temp.lights = (model_light*) vm_malloc( n_norms * sizeof(model_light) );
|
|
|
|
memset( Interp_light_applied, 0, n_norms * sizeof(ubyte) );
|
|
memset( Interp_lighting_temp.lights, 0, n_norms * sizeof(model_light) );
|
|
|
|
Num_interp_norms_allocated = n_norms;
|
|
}
|
|
|
|
Interp_num_verts = n_verts;
|
|
Interp_num_norms = n_norms;
|
|
|
|
// check that everything is still usable (works in release and debug builds)
|
|
Verify( Interp_points != NULL );
|
|
Verify( Interp_verts != NULL );
|
|
Verify( Interp_norms != NULL );
|
|
Verify( Interp_light_applied != NULL );
|
|
}
|
|
|
|
void interp_clear_instance()
|
|
{
|
|
Interp_thrust_scale = 0.1f;
|
|
Interp_thrust_scale_x = 0.0f;//added-Bobboau
|
|
Interp_thrust_scale_y = 0.0f;//added-Bobboau
|
|
Interp_thrust_bitmap = -1;
|
|
Interp_thrust_glow_bitmap = -1;
|
|
Interp_thrust_glow_noise = 1.0f;
|
|
Interp_insignia_bitmap = -1;
|
|
Interp_afterburner = false;
|
|
|
|
// Bobboau's thruster stuff
|
|
{
|
|
Interp_thrust_glow_rad_factor = 1.0f;
|
|
|
|
Interp_secondary_thrust_glow_bitmap = -1;
|
|
Interp_secondary_thrust_glow_rad_factor = 1.0f;
|
|
|
|
Interp_tertiary_thrust_glow_bitmap = -1;
|
|
Interp_tertiary_thrust_glow_rad_factor = 1.0f;
|
|
|
|
Interp_thrust_glow_len_factor = 1.0f;
|
|
|
|
vm_vec_zero(&Interp_thrust_rotvel);
|
|
}
|
|
|
|
Interp_box_scale = 1.0f;
|
|
|
|
Interp_detail_level_locked = -1;
|
|
|
|
Interp_forced_bitmap = -1;
|
|
}
|
|
|
|
/**
|
|
* Scales the engines thrusters by this much
|
|
*/
|
|
void model_set_thrust(int /*model_num*/, mst_info *mst)
|
|
{
|
|
if (mst == NULL) {
|
|
Int3();
|
|
return;
|
|
}
|
|
|
|
Interp_thrust_scale = mst->length.xyz.z;
|
|
Interp_thrust_scale_x = mst->length.xyz.x;
|
|
Interp_thrust_scale_y = mst->length.xyz.y;
|
|
|
|
CLAMP(Interp_thrust_scale, 0.1f, 1.0f);
|
|
|
|
Interp_thrust_bitmap = mst->primary_bitmap;
|
|
Interp_thrust_glow_bitmap = mst->primary_glow_bitmap;
|
|
Interp_secondary_thrust_glow_bitmap = mst->secondary_glow_bitmap;
|
|
Interp_tertiary_thrust_glow_bitmap = mst->tertiary_glow_bitmap;
|
|
Interp_distortion_thrust_bitmap = mst->distortion_bitmap;
|
|
|
|
Interp_thrust_glow_noise = mst->glow_noise;
|
|
Interp_afterburner = mst->use_ab;
|
|
|
|
Interp_thrust_rotvel = mst->rotvel;
|
|
|
|
Interp_thrust_glow_rad_factor = mst->glow_rad_factor;
|
|
Interp_secondary_thrust_glow_rad_factor = mst->secondary_glow_rad_factor;
|
|
Interp_tertiary_thrust_glow_rad_factor = mst->tertiary_glow_rad_factor;
|
|
Interp_thrust_glow_len_factor = mst->glow_length_factor;
|
|
Interp_distortion_thrust_rad_factor = mst->distortion_rad_factor;
|
|
Interp_distortion_thrust_length_factor = mst->distortion_length_factor;
|
|
|
|
Interp_draw_distortion = mst->draw_distortion;
|
|
}
|
|
|
|
float GEOMETRY_NOISE = 0.0f;
|
|
|
|
// Point list
|
|
// +0 int id
|
|
// +4 int size
|
|
// +8 int n_verts
|
|
// +12 int n_norms
|
|
// +16 int offset from start of chunk to vertex data
|
|
// +20 n_verts*char norm_counts
|
|
// +offset vertex data. Each vertex n is a point followed by norm_counts[n] normals.
|
|
void model_interp_defpoints(ubyte * p, polymodel *pm, bsp_info *sm)
|
|
{
|
|
SCP_UNUSED(pm);
|
|
SCP_UNUSED(sm);
|
|
|
|
uint i, n;
|
|
uint nverts = uw(p+8);
|
|
uint offset = uw(p+16);
|
|
uint next_norm = 0;
|
|
uint nnorms = 0;
|
|
|
|
ubyte * normcount = p+20;
|
|
vertex *dest = NULL;
|
|
vec3d *src = vp(p+offset);
|
|
|
|
// Get pointer to lights
|
|
Interp_lights = p+20+nverts;
|
|
|
|
for (i = 0; i < nverts; i++) {
|
|
nnorms += normcount[i];
|
|
}
|
|
|
|
// allocate new Interp data if size is greater than we already have ready to use
|
|
model_allocate_interp_data(nverts, nnorms);
|
|
|
|
dest = Interp_points;
|
|
|
|
Assert( dest != NULL );
|
|
|
|
#ifndef NDEBUG
|
|
modelstats_num_verts += nverts;
|
|
#endif
|
|
|
|
|
|
if (Interp_thrust_scale_subobj) {
|
|
|
|
// Only scale vertices that aren't on the "base" of
|
|
// the effect. Base is something Adam decided to be
|
|
// anything under 1.5 meters, hence the 1.5f.
|
|
float min_thruster_dist = -1.5f;
|
|
|
|
if ( Interp_flags & MR_IS_MISSILE ) {
|
|
min_thruster_dist = 0.5f;
|
|
}
|
|
|
|
for (n=0; n<nverts; n++ ) {
|
|
vec3d tmp;
|
|
|
|
Interp_verts[n] = src;
|
|
|
|
// Only scale vertices that aren't on the "base" of
|
|
// the effect. Base is something Adam decided to be
|
|
// anything under 1.5 meters, hence the 1.5f.
|
|
if ( src->xyz.z < min_thruster_dist ) {
|
|
tmp.xyz.x = src->xyz.x * 1.0f;
|
|
tmp.xyz.y = src->xyz.y * 1.0f;
|
|
tmp.xyz.z = src->xyz.z * Interp_thrust_scale;
|
|
} else {
|
|
tmp = *src;
|
|
}
|
|
|
|
g3_rotate_vertex(dest,&tmp);
|
|
|
|
src++; // move to normal
|
|
|
|
for (i=0; i<normcount[n]; i++ ) {
|
|
Interp_light_applied[next_norm] = 0;
|
|
Interp_norms[next_norm] = src;
|
|
|
|
next_norm++;
|
|
src++;
|
|
}
|
|
dest++;
|
|
}
|
|
} else if ( (Interp_warp_scale_x != 1.0f) || (Interp_warp_scale_y != 1.0f) || (Interp_warp_scale_z != 1.0f)) {
|
|
for (n=0; n<nverts; n++ ) {
|
|
vec3d tmp;
|
|
|
|
Interp_verts[n] = src;
|
|
|
|
tmp.xyz.x = (src->xyz.x) * Interp_warp_scale_x;
|
|
tmp.xyz.y = (src->xyz.y) * Interp_warp_scale_y;
|
|
tmp.xyz.z = (src->xyz.z) * Interp_warp_scale_z;
|
|
|
|
g3_rotate_vertex(dest,&tmp);
|
|
|
|
src++; // move to normal
|
|
|
|
for (i=0; i<normcount[n]; i++ ) {
|
|
Interp_light_applied[next_norm] = 0;
|
|
Interp_norms[next_norm] = src;
|
|
|
|
next_norm++;
|
|
src++;
|
|
}
|
|
dest++;
|
|
}
|
|
} else {
|
|
vec3d point;
|
|
|
|
for (n=0; n<nverts; n++ ) {
|
|
|
|
if(GEOMETRY_NOISE!=0.0f){
|
|
GEOMETRY_NOISE = model_radius / 50;
|
|
|
|
Interp_verts[n] = src;
|
|
point.xyz.x = src->xyz.x + frand_range(GEOMETRY_NOISE,-GEOMETRY_NOISE);
|
|
point.xyz.y = src->xyz.y + frand_range(GEOMETRY_NOISE,-GEOMETRY_NOISE);
|
|
point.xyz.z = src->xyz.z + frand_range(GEOMETRY_NOISE,-GEOMETRY_NOISE);
|
|
|
|
g3_rotate_vertex(dest, &point);
|
|
}else{
|
|
Interp_verts[n] = src;
|
|
g3_rotate_vertex(dest, src);
|
|
}
|
|
|
|
src++; // move to normal
|
|
|
|
for (i=0; i<normcount[n]; i++ ) {
|
|
Interp_light_applied[next_norm] = 0;
|
|
Interp_norms[next_norm] = src;
|
|
|
|
next_norm++;
|
|
src++;
|
|
}
|
|
dest++;
|
|
}
|
|
}
|
|
|
|
Interp_num_norms = next_norm;
|
|
}
|
|
|
|
void model_interp_edge_alpha( ubyte *param_r, ubyte *param_g, ubyte *param_b, vec3d *pnt, vec3d *norm, float alpha, bool invert = false)
|
|
{
|
|
vec3d r;
|
|
|
|
vm_vec_sub(&r, &View_position, pnt);
|
|
vm_vec_normalize(&r);
|
|
|
|
float d = vm_vec_dot(&r, norm);
|
|
|
|
if (d < 0.0f)
|
|
d = -d;
|
|
|
|
if (invert)
|
|
*param_r = *param_g = *param_b = ubyte( fl2i((1.0f - d) * 254.0f * alpha));
|
|
else
|
|
*param_r = *param_g = *param_b = ubyte( fl2i(d * 254.0f * alpha) );
|
|
}
|
|
|
|
int Interp_subspace = 0;
|
|
float Interp_subspace_offset_u = 0.0f;
|
|
float Interp_subspace_offset_v = 0.0f;
|
|
ubyte Interp_subspace_r = 255;
|
|
ubyte Interp_subspace_g = 255;
|
|
ubyte Interp_subspace_b = 255;
|
|
|
|
void model_draw_debug_points(const polymodel *pm, const bsp_info *submodel, uint64_t flags )
|
|
{
|
|
if ( flags & MR_SHOW_OUTLINE_PRESET ) {
|
|
return;
|
|
}
|
|
|
|
// Draw a red pivot point
|
|
gr_set_color(128,0,0);
|
|
g3_draw_sphere_ez(&vmd_zero_vector, 2.0f );
|
|
|
|
// Draw a green center of mass when drawing the hull
|
|
if ( submodel && (submodel->parent==-1) ) {
|
|
gr_set_color(0,128,0);
|
|
g3_draw_sphere_ez( &pm->center_of_mass, 1.0f );
|
|
}
|
|
|
|
if ( submodel ) {
|
|
// Draw a blue center point
|
|
gr_set_color(0,0,128);
|
|
g3_draw_sphere_ez( &submodel->geometric_center, 0.9f );
|
|
}
|
|
|
|
// Draw the bounding box
|
|
int i;
|
|
vertex pts[8];
|
|
|
|
if ( submodel ) {
|
|
for (i=0; i<8; i++ ) {
|
|
g3_rotate_vertex( &pts[i], &submodel->bounding_box[i] );
|
|
}
|
|
gr_set_color(128,128,128);
|
|
g3_draw_line( &pts[0], &pts[1] );
|
|
g3_draw_line( &pts[1], &pts[2] );
|
|
g3_draw_line( &pts[2], &pts[3] );
|
|
g3_draw_line( &pts[3], &pts[0] );
|
|
|
|
g3_draw_line( &pts[4], &pts[5] );
|
|
g3_draw_line( &pts[5], &pts[6] );
|
|
g3_draw_line( &pts[6], &pts[7] );
|
|
g3_draw_line( &pts[7], &pts[4] );
|
|
|
|
g3_draw_line( &pts[0], &pts[4] );
|
|
g3_draw_line( &pts[1], &pts[5] );
|
|
g3_draw_line( &pts[2], &pts[6] );
|
|
g3_draw_line( &pts[3], &pts[7] );
|
|
} else {
|
|
gr_set_color(0,255,0);
|
|
|
|
vec3d bounding_box[8]; // caclulated fron min/max
|
|
model_calc_bound_box(bounding_box, &pm->mins, &pm->maxs);
|
|
|
|
for (i=0; i<8; i++ ) {
|
|
g3_rotate_vertex( &pts[i], &bounding_box[i] );
|
|
}
|
|
gr_set_color(128,0,0);
|
|
g3_draw_line( &pts[0], &pts[1] );
|
|
g3_draw_line( &pts[1], &pts[2] );
|
|
g3_draw_line( &pts[2], &pts[3] );
|
|
g3_draw_line( &pts[3], &pts[0] );
|
|
|
|
g3_draw_line( &pts[4], &pts[5] );
|
|
g3_draw_line( &pts[5], &pts[6] );
|
|
g3_draw_line( &pts[6], &pts[7] );
|
|
g3_draw_line( &pts[7], &pts[4] );
|
|
|
|
g3_draw_line( &pts[0], &pts[4] );
|
|
g3_draw_line( &pts[1], &pts[5] );
|
|
g3_draw_line( &pts[2], &pts[6] );
|
|
g3_draw_line( &pts[3], &pts[7] );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Debug code to show all the paths of a model
|
|
*/
|
|
void model_draw_paths_htl( int model_num, uint64_t flags )
|
|
{
|
|
int i,j;
|
|
vec3d pnt;
|
|
vec3d prev_pnt;
|
|
polymodel * pm;
|
|
|
|
if ( flags & MR_SHOW_OUTLINE_PRESET ) {
|
|
return;
|
|
}
|
|
|
|
pm = model_get(model_num);
|
|
|
|
if (pm->n_paths<1){
|
|
return;
|
|
}
|
|
|
|
int cull = gr_set_cull(0);
|
|
for (i=0; i<pm->n_paths; i++ ) {
|
|
for (j=0; j<pm->paths[i].nverts; j++ )
|
|
{
|
|
// Rotate point into world coordinates
|
|
pnt = pm->paths[i].verts[j].pos;
|
|
|
|
// Pnt is now the x,y,z world coordinates of this vert.
|
|
// For this example, I am just drawing a sphere at that
|
|
// point.
|
|
|
|
vertex tmp;
|
|
g3_rotate_vertex(&tmp,&pnt);
|
|
|
|
if ( pm->paths[i].verts[j].nturrets > 0 ){
|
|
gr_set_color( 0, 0, 255 ); // draw points covered by turrets in blue
|
|
} else {
|
|
gr_set_color( 255, 0, 0 );
|
|
}
|
|
|
|
g3_render_sphere(&pnt, 0.5f);
|
|
|
|
if (j)
|
|
{
|
|
//g3_draw_htl_line(&prev_pnt, &pnt);
|
|
g3_render_line_3d(true, &prev_pnt, &pnt);
|
|
}
|
|
|
|
prev_pnt = pnt;
|
|
}
|
|
}
|
|
|
|
gr_set_cull(cull);
|
|
}
|
|
|
|
/**
|
|
* Fighter bay approach/departure paths (paths named $bayN)
|
|
*/
|
|
void model_draw_bay_paths_htl(int model_num)
|
|
{
|
|
int idx, s_idx;
|
|
vec3d v1, v2;
|
|
|
|
polymodel *pm = model_get(model_num);
|
|
if(pm == NULL){
|
|
return;
|
|
}
|
|
|
|
int cull = gr_set_cull(0);
|
|
|
|
// render fighter bay paths
|
|
gr_set_color(0, 255, 255);
|
|
|
|
// iterate through the paths that exist in the polymodel, searching for $bayN pathnames
|
|
for (idx = 0; idx<pm->n_paths; idx++) {
|
|
if ( !strnicmp(pm->paths[idx].name, NOX("$bay"), 4) ) {
|
|
for(s_idx=0; s_idx<pm->paths[idx].nverts-1; s_idx++){
|
|
v1 = pm->paths[idx].verts[s_idx].pos;
|
|
v2 = pm->paths[idx].verts[s_idx+1].pos;
|
|
|
|
g3_render_line_3d(true, &v1, &v2);
|
|
}
|
|
}
|
|
}
|
|
|
|
gr_set_cull(cull);
|
|
}
|
|
|
|
/**
|
|
* Docking bay slot positions and normals
|
|
*/
|
|
void model_draw_dock_points_htl(int model_num)
|
|
{
|
|
int idx, s_idx;
|
|
vec3d v1, v2;
|
|
|
|
polymodel *pm = model_get(model_num);
|
|
if(pm == nullptr){
|
|
return;
|
|
}
|
|
|
|
int cull = gr_set_cull(0);
|
|
|
|
// render docking bay normals
|
|
gr_set_color(0, 255, 0);
|
|
for(idx=0; idx<pm->n_docks; idx++){
|
|
for(s_idx=0; s_idx<pm->docking_bays[idx].num_slots; s_idx++){
|
|
v1 = pm->docking_bays[idx].pnt[s_idx];
|
|
vm_vec_scale_add(&v2, &v1, &pm->docking_bays[idx].norm[s_idx], 10.0f);
|
|
|
|
// draw the point and normal
|
|
g3_render_sphere(&v1, 2.0);
|
|
g3_render_line_3d(true, &v1, &v2);
|
|
}
|
|
}
|
|
|
|
gr_set_cull(cull);
|
|
}
|
|
|
|
void interp_generate_arc_segment(SCP_vector<vec3d> &arc_segment_points, const vec3d *v1, const vec3d *v2, ubyte depth_limit, ubyte depth)
|
|
{
|
|
float d = vm_vec_dist_quick( v1, v2 );
|
|
constexpr float scaler = 0.30f;
|
|
|
|
if ( (d < scaler) || (depth > depth_limit) ) {
|
|
arc_segment_points.push_back(*v2);
|
|
} else {
|
|
// divide in half
|
|
vec3d tmp;
|
|
vm_vec_avg( &tmp, v1, v2 );
|
|
|
|
tmp.xyz.x += (frand() - 0.5f) * d * scaler;
|
|
tmp.xyz.y += (frand() - 0.5f) * d * scaler;
|
|
tmp.xyz.z += (frand() - 0.5f) * d * scaler;
|
|
|
|
// add additional point
|
|
interp_generate_arc_segment( arc_segment_points, v1, &tmp, depth_limit, depth+1 );
|
|
interp_generate_arc_segment( arc_segment_points, &tmp, v2, depth_limit, depth+1 );
|
|
}
|
|
}
|
|
|
|
int Interp_lightning = 1;
|
|
DCF_BOOL( Arcs, Interp_lightning )
|
|
|
|
// Returns one of the following
|
|
#define IBOX_ALL_OFF 0
|
|
#define IBOX_ALL_ON 1
|
|
#define IBOX_SOME_ON_SOME_OFF 2
|
|
|
|
int interp_box_offscreen( vec3d *min, vec3d *max )
|
|
{
|
|
if ( key_is_pressed(KEY_LSHIFT) ) {
|
|
return IBOX_ALL_ON;
|
|
}
|
|
|
|
vec3d v[8];
|
|
v[0].xyz.x = min->xyz.x; v[0].xyz.y = min->xyz.y; v[0].xyz.z = min->xyz.z;
|
|
v[1].xyz.x = max->xyz.x; v[1].xyz.y = min->xyz.y; v[1].xyz.z = min->xyz.z;
|
|
v[2].xyz.x = max->xyz.x; v[2].xyz.y = max->xyz.y; v[2].xyz.z = min->xyz.z;
|
|
v[3].xyz.x = min->xyz.x; v[3].xyz.y = max->xyz.y; v[3].xyz.z = min->xyz.z;
|
|
|
|
v[4].xyz.x = min->xyz.x; v[4].xyz.y = min->xyz.y; v[4].xyz.z = max->xyz.z;
|
|
v[5].xyz.x = max->xyz.x; v[5].xyz.y = min->xyz.y; v[5].xyz.z = max->xyz.z;
|
|
v[6].xyz.x = max->xyz.x; v[6].xyz.y = max->xyz.y; v[6].xyz.z = max->xyz.z;
|
|
v[7].xyz.x = min->xyz.x; v[7].xyz.y = max->xyz.y; v[7].xyz.z = max->xyz.z;
|
|
|
|
ubyte and_codes = 0xff;
|
|
ubyte or_codes = 0xff;
|
|
int i;
|
|
|
|
for (i=0; i<8; i++ ) {
|
|
vertex tmp;
|
|
ubyte codes=g3_rotate_vertex( &tmp, &v[i] );
|
|
|
|
or_codes |= codes;
|
|
and_codes &= codes;
|
|
}
|
|
|
|
// If and_codes is set this means that all points lie off to the
|
|
// same side of the screen.
|
|
if (and_codes) {
|
|
return IBOX_ALL_OFF; //all points off screen
|
|
}
|
|
|
|
// If this is set it means at least one of the points is offscreen,
|
|
// but they aren't all off to the same side.
|
|
if (or_codes) {
|
|
return IBOX_SOME_ON_SOME_OFF;
|
|
}
|
|
|
|
// They are all onscreen.
|
|
return IBOX_ALL_ON;
|
|
}
|
|
|
|
void model_render_shields( polymodel * pm, uint64_t flags )
|
|
{
|
|
int i, j;
|
|
shield_tri *tri;
|
|
vertex pnt0, prev_pnt, tmp = vertex();
|
|
|
|
if ( flags & MR_SHOW_OUTLINE_PRESET ) {
|
|
return;
|
|
}
|
|
|
|
gr_set_color(0, 0, 200 );
|
|
|
|
// Scan all the triangles in the mesh.
|
|
for (i=0; i<pm->shield.ntris; i++ ) {
|
|
|
|
tri = &pm->shield.tris[i];
|
|
|
|
if (g3_check_normal_facing(&pm->shield.verts[tri->verts[0]].pos,&tri->norm ) ) {
|
|
|
|
// Process the vertices.
|
|
// Note this rotates each vertex each time it's needed, very dumb.
|
|
for (j=0; j<3; j++ ) {
|
|
|
|
g3_rotate_vertex(&tmp, &pm->shield.verts[tri->verts[j]].pos );
|
|
|
|
if (j)
|
|
g3_draw_line(&prev_pnt, &tmp);
|
|
else
|
|
pnt0 = tmp;
|
|
prev_pnt = tmp;
|
|
}
|
|
|
|
g3_draw_line(&pnt0, &prev_pnt);
|
|
}
|
|
}
|
|
}
|
|
|
|
int Model_texturing = 1;
|
|
int Model_polys = 1;
|
|
|
|
DCF_BOOL( model_texturing, Model_texturing )
|
|
DCF_BOOL( model_polys, Model_polys )
|
|
|
|
MONITOR( NumModelsRend )
|
|
MONITOR( NumHiModelsRend )
|
|
MONITOR( NumMedModelsRend )
|
|
MONITOR( NumLowModelsRend )
|
|
|
|
/**
|
|
* Draws a bitmap with the specified 3d width & height
|
|
* @return 1 if off screen, 0 if not
|
|
*/
|
|
int model_get_rotated_bitmap_points(vertex *pnt,float angle, float rad, vertex *v)
|
|
{
|
|
float sa, ca;
|
|
int i;
|
|
|
|
Assert( G3_count == 1 );
|
|
|
|
sa = sinf(angle);
|
|
ca = cosf(angle);
|
|
|
|
float width, height;
|
|
|
|
width = height = rad;
|
|
|
|
v[0].world.xyz.x = (-width*ca - height*sa)*Matrix_scale.xyz.x + pnt->world.xyz.x;
|
|
v[0].world.xyz.y = (-width*sa + height*ca)*Matrix_scale.xyz.y + pnt->world.xyz.y;
|
|
v[0].world.xyz.z = pnt->world.xyz.z;
|
|
v[0].screen.xyw.w = 0.0f;
|
|
v[0].texture_position.u = 0.0f;
|
|
v[0].texture_position.v = 0.0f;
|
|
|
|
v[1].world.xyz.x = (width*ca - height*sa)*Matrix_scale.xyz.x + pnt->world.xyz.x;
|
|
v[1].world.xyz.y = (width*sa + height*ca)*Matrix_scale.xyz.y + pnt->world.xyz.y;
|
|
v[1].world.xyz.z = pnt->world.xyz.z;
|
|
v[1].screen.xyw.w = 0.0f;
|
|
v[1].texture_position.u = 1.0f;
|
|
v[1].texture_position.v = 0.0f;
|
|
|
|
v[2].world.xyz.x = (width*ca + height*sa)*Matrix_scale.xyz.x + pnt->world.xyz.x;
|
|
v[2].world.xyz.y = (width*sa - height*ca)*Matrix_scale.xyz.y + pnt->world.xyz.y;
|
|
v[2].world.xyz.z = pnt->world.xyz.z;
|
|
v[2].screen.xyw.w = 0.0f;
|
|
v[2].texture_position.u = 1.0f;
|
|
v[2].texture_position.v = 1.0f;
|
|
|
|
v[3].world.xyz.x = (-width*ca + height*sa)*Matrix_scale.xyz.x + pnt->world.xyz.x;
|
|
v[3].world.xyz.y = (-width*sa - height*ca)*Matrix_scale.xyz.y + pnt->world.xyz.y;
|
|
v[3].world.xyz.z = pnt->world.xyz.z;
|
|
v[3].screen.xyw.w = 0.0f;
|
|
v[3].texture_position.u = 0.0f;
|
|
v[3].texture_position.v = 1.0f;
|
|
|
|
ubyte codes_and=0xff;
|
|
|
|
float sw,z;
|
|
z = pnt->world.xyz.z - rad / 4.0f;
|
|
if ( z < 0.0f ) z = 0.0f;
|
|
sw = 1.0f / z;
|
|
|
|
for (i=0; i<4; i++ ) {
|
|
//now code the four points
|
|
codes_and &= g3_code_vertex(&v[i]);
|
|
v[i].flags = 0; // mark as not yet projected
|
|
g3_project_vertex(&v[i]);
|
|
v[i].screen.xyw.w = sw;
|
|
}
|
|
|
|
if (codes_and)
|
|
return 1; //1 means off screen
|
|
|
|
return 0;
|
|
}
|
|
|
|
float Interp_depth_scale = 1500.0f;
|
|
|
|
DCF(model_darkening,"Makes models darker with distance")
|
|
{
|
|
if (dc_optional_string_either("help", "--help")) {
|
|
dc_printf( "Usage: model_darkening <float>\n" );
|
|
dc_printf("Sets the distance at which to start blacking out models (namely asteroids).\n");
|
|
return;
|
|
}
|
|
|
|
if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
|
|
dc_printf( "model_darkening = %.1f\n", Interp_depth_scale );
|
|
return;
|
|
}
|
|
|
|
dc_stuff_float(&Interp_depth_scale);
|
|
|
|
dc_printf("model_darkening set to %.1f\n", Interp_depth_scale);
|
|
}
|
|
|
|
// tmp_detail_level
|
|
// 0 - Max
|
|
// 1
|
|
// 2
|
|
// 3
|
|
// 4 - None
|
|
|
|
static_assert(MAX_DETAIL_VALUE == 4, "MAX_DETAIL_VALUE is assumed to be 4 in SystemVars.cpp");
|
|
|
|
|
|
/**
|
|
* Find the distance-squared from p0 to the closest point on a box. Fills in closest_pt.
|
|
* The box's dimensions are from 'min' to 'max'.
|
|
*/
|
|
float interp_closest_dist_sq_to_box( vec3d *closest_pt, const vec3d *p0, const vec3d *min, const vec3d *max )
|
|
{
|
|
auto origin = p0->a1d;
|
|
auto minB = min->a1d;
|
|
auto maxB = max->a1d;
|
|
auto coord = closest_pt->a1d;
|
|
bool inside = true;
|
|
int i;
|
|
|
|
for (i=0; i<3; i++ ) {
|
|
if ( origin[i] < minB[i] ) {
|
|
coord[i] = minB[i];
|
|
inside = false;
|
|
} else if (origin[i] > maxB[i] ) {
|
|
coord[i] = maxB[i];
|
|
inside = false;
|
|
} else {
|
|
coord[i] = origin[i];
|
|
}
|
|
}
|
|
|
|
if ( inside ) {
|
|
return 0.0f;
|
|
}
|
|
|
|
return vm_vec_dist_squared(closest_pt, p0);
|
|
}
|
|
|
|
|
|
// Finds the closest point on a model to a point in space. Actually only finds a point
|
|
// on the bounding box of the model.
|
|
// Given:
|
|
// model_num Which model
|
|
// orient Orientation of the model
|
|
// pos Position of the model
|
|
// eye_pos Point that you want to find the closest point to
|
|
// Returns:
|
|
// distance from eye_pos to closest_point. 0 means eye_pos is
|
|
// on or inside the bounding box.
|
|
// Also fills in outpnt with the actual closest point (in local coordinates).
|
|
float model_find_closest_point( vec3d *outpnt, int model_num, int submodel_num, const matrix *orient, const vec3d *pos, const vec3d *eye_pos )
|
|
{
|
|
vec3d tempv, eye_rel_pos;
|
|
|
|
polymodel *pm = model_get(model_num);
|
|
|
|
if ( submodel_num < 0 ) {
|
|
submodel_num = pm->detail[0];
|
|
}
|
|
|
|
// Rotate eye pos into object coordinates
|
|
vm_vec_sub(&tempv, pos, eye_pos);
|
|
vm_vec_rotate(&eye_rel_pos, &tempv, orient);
|
|
|
|
return fl_sqrt( interp_closest_dist_sq_to_box( outpnt, &eye_rel_pos, &pm->submodel[submodel_num].min, &pm->submodel[submodel_num].max ) );
|
|
}
|
|
|
|
// Like the above, but finds the closest two points to each other.
|
|
float model_find_closest_points(vec3d *outpnt1, int model_num1, int submodel_num1, const matrix *orient1, const vec3d *pos1, vec3d *outpnt2, int model_num2, int submodel_num2, const matrix *orient2, const vec3d *pos2)
|
|
{
|
|
polymodel *pm1 = model_get(model_num1);
|
|
if (submodel_num1 < 0)
|
|
submodel_num1 = pm1->detail[0];
|
|
|
|
polymodel *pm2 = model_get(model_num2);
|
|
if (submodel_num2 < 0)
|
|
submodel_num2 = pm2->detail[0];
|
|
|
|
// determine obj2's bounding box
|
|
vec3d bounding_box[8];
|
|
model_calc_bound_box(bounding_box, &pm2->submodel[submodel_num2].min, &pm2->submodel[submodel_num2].max);
|
|
|
|
float closest_dist_sq = -1.0f;
|
|
|
|
// check each point on it
|
|
for (const auto &pt : bounding_box)
|
|
{
|
|
vec3d temp, rel_pt;
|
|
|
|
// find world coordinates of this point
|
|
vm_vec_unrotate(&temp, &pt, orient2);
|
|
vm_vec_add(&rel_pt, &temp, pos2);
|
|
|
|
// now find coordinates relative to obj1
|
|
vm_vec_sub(&temp, pos1, &rel_pt);
|
|
vm_vec_rotate(&rel_pt, &temp, orient1);
|
|
|
|
// test this point
|
|
float dist_sq = interp_closest_dist_sq_to_box(&temp, &rel_pt, &pm1->submodel[submodel_num1].min, &pm1->submodel[submodel_num1].max);
|
|
if (closest_dist_sq < 0.0f || dist_sq < closest_dist_sq)
|
|
{
|
|
closest_dist_sq = dist_sq;
|
|
|
|
// Note: As in the other function, both of these points are
|
|
// in local coordinates relative to each of their models.
|
|
*outpnt1 = temp;
|
|
*outpnt2 = pt;
|
|
}
|
|
}
|
|
|
|
// we have now found the closest point
|
|
return fl_sqrt(closest_dist_sq);
|
|
}
|
|
|
|
int tiling = 1;
|
|
DCF(tiling, "Toggles rendering of tiled textures (default is on)")
|
|
{
|
|
if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
|
|
dc_printf("Tiled textures are %s", tiling ? "ON" : "OFF");
|
|
return;
|
|
}
|
|
|
|
tiling = !tiling;
|
|
if(tiling){
|
|
dc_printf("Tiled textures\n");
|
|
} else {
|
|
dc_printf("Non-tiled textures\n");
|
|
}
|
|
}
|
|
|
|
void moldel_calc_facing_pts( vec3d *top, vec3d *bot, vec3d *fvec, vec3d *pos, float w, float /*z_add*/, vec3d *Eyeposition )
|
|
{
|
|
vec3d uvec, rvec;
|
|
vec3d temp;
|
|
|
|
temp = *pos;
|
|
|
|
vm_vec_sub( &rvec, Eyeposition, &temp );
|
|
vm_vec_normalize( &rvec );
|
|
|
|
vm_vec_cross(&uvec,fvec,&rvec);
|
|
vm_vec_normalize(&uvec);
|
|
|
|
vm_vec_scale_add( top, &temp, &uvec, w/2.0f );
|
|
vm_vec_scale_add( bot, &temp, &uvec, -w/2.0f );
|
|
}
|
|
|
|
vec3d submodel_get_random_point(int model_num, int submodel_num, int seed)
|
|
{
|
|
polymodel *pm = model_get(model_num);
|
|
|
|
if (pm != NULL) {
|
|
if ( submodel_num < 0 ) {
|
|
submodel_num = pm->detail[0];
|
|
}
|
|
|
|
bsp_collision_tree *tree = model_get_bsp_collision_tree(pm->submodel[submodel_num].collision_tree_index);
|
|
|
|
int nv = tree->n_verts;
|
|
|
|
// this is not only because of the immediate div-0 error but also because of the less immediate expectation for at least one point (preferably two) to be found
|
|
if (nv <= 0) {
|
|
Error(LOCATION, "Model %d ('%s') must have at least one point in its collision tree!", model_num, (pm == NULL) ? "<null model?!?>" : pm->filename);
|
|
|
|
// in case people ignore the error...
|
|
return vmd_zero_vector;
|
|
}
|
|
|
|
int seed_num = seed == -1 ? Random::next() : seed;
|
|
int vn1 = static_rand(seed_num) % nv;
|
|
|
|
return tree->point_list[vn1];
|
|
} else {
|
|
Assertion(false, "submodel_get_random_point called on an invalid model!");
|
|
return vmd_zero_vector;
|
|
}
|
|
}
|
|
|
|
void submodel_get_cross_sectional_avg_pos(int model_num, int submodel_num, float z_slice_pos, vec3d* pos)
|
|
{
|
|
polymodel* pm = model_get(model_num);
|
|
|
|
if (pm != nullptr) {
|
|
if (submodel_num < 0) {
|
|
submodel_num = pm->detail[0];
|
|
}
|
|
|
|
bsp_collision_tree* tree = model_get_bsp_collision_tree(pm->submodel[submodel_num].collision_tree_index);
|
|
|
|
int nv = tree->n_verts;
|
|
|
|
// this is not only because of the immediate div-0 error but also because of the less immediate expectation for at least one point (preferably two) to be found
|
|
if (nv <= 0) {
|
|
Error(LOCATION, "Model %d ('%s') must have at least one point from submodel_get_cross_sectional_avg_pos!", model_num, (pm == nullptr) ? "<null model?!?>" : pm->filename);
|
|
|
|
// in case people ignore the error...
|
|
vm_vec_zero(pos);
|
|
return;
|
|
}
|
|
|
|
vm_vec_zero(pos);
|
|
// we take a regular average, add them all up, divide by the total number, but weighted by how close they are to the z slice
|
|
float accum_scale_factor = 0.0f;
|
|
for (int i = 0; i < tree->n_verts; i++) {
|
|
// this goes from 1 directly at our z pos, and quickly goes to 0 the further it gets
|
|
float scale_factor = 1 / ((fabs(tree->point_list[i].xyz.z - z_slice_pos) / (pm->rad / 10)) + 1);
|
|
vm_vec_scale_add(pos, pos, &tree->point_list[i], scale_factor);
|
|
// keep track of the scale factor we use because we need to divide by its total at the end
|
|
accum_scale_factor += scale_factor;
|
|
}
|
|
|
|
*pos /= accum_scale_factor;
|
|
}
|
|
}
|
|
|
|
void submodel_get_cross_sectional_random_pos(int model_num, int submodel_num, float z_slice_pos, vec3d* pos)
|
|
{
|
|
polymodel* pm = model_get(model_num);
|
|
|
|
if (pm != nullptr) {
|
|
if (submodel_num < 0) {
|
|
submodel_num = pm->detail[0];
|
|
}
|
|
|
|
// the Shivan Comm Node does not have a collision tree, for one
|
|
if (pm->submodel[submodel_num].collision_tree_index < 0) {
|
|
nprintf(("Model", "In submodel_get_cross_sectional_random_pos(), model %s does not have a collision tree!\n", pm->filename));
|
|
return;
|
|
}
|
|
|
|
bsp_collision_tree* tree = model_get_bsp_collision_tree(pm->submodel[submodel_num].collision_tree_index);
|
|
|
|
int nv = tree->n_verts;
|
|
|
|
// this is not only because of the immediate div-0 error but also because of the less immediate expectation for at least one point (preferably two) to be found
|
|
if (nv <= 0) {
|
|
Error(LOCATION, "Model %d ('%s') must have at least one point from submodel_get_cross_sectional_random_pos!", model_num, (pm == nullptr) ? "<null model?!?>" : pm->filename);
|
|
|
|
// in case people ignore the error...
|
|
vm_vec_zero(pos);
|
|
return;
|
|
}
|
|
|
|
vm_vec_zero(pos);
|
|
vec3d best1, best2;
|
|
// make random guesses a bunch of times, and average our two best guesses closest to the z pos, ez
|
|
// there are more accurate ways, but this is reasonably good and super cheap
|
|
vm_vec_make(&best1, 0, 0, 999999);
|
|
vm_vec_make(&best2, 0, 0, 999999);
|
|
for (int i = 0; i < 15; i++) {
|
|
vec3d rand_point = tree->point_list[Random::next(nv)];
|
|
if (fabs(rand_point.xyz.z - z_slice_pos) < fabs(best1.xyz.z - z_slice_pos))
|
|
best1 = rand_point;
|
|
else if (fabs(rand_point.xyz.z - z_slice_pos) < fabs(best2.xyz.z - z_slice_pos))
|
|
best2 = rand_point;
|
|
}
|
|
|
|
vm_vec_avg(pos, &best1, &best2);
|
|
}
|
|
}
|
|
|
|
// If MR_FLAG_OUTLINE bit set this color will be used for outlines.
|
|
// This defaults to black.
|
|
void model_set_outline_color(int r, int g, int b )
|
|
{
|
|
gr_init_color( &Interp_outline_color, r, g, b );
|
|
|
|
}
|
|
|
|
// IF MR_LOCK_DETAIL is set, then it will always draw detail level 'n'
|
|
// This defaults to 0. (0=highest, larger=lower)
|
|
void model_set_detail_level(int n)
|
|
{
|
|
Interp_detail_level_locked = n;
|
|
}
|
|
|
|
/**
|
|
* Returns number of tmaps & flat polys in a submodel;
|
|
*/
|
|
int submodel_get_num_polys_sub( ubyte *p )
|
|
{
|
|
int chunk_type = w(p);
|
|
int chunk_size = w(p+4);
|
|
int n = 0;
|
|
|
|
bool end = chunk_type == OP_EOF;
|
|
while (!end) {
|
|
switch (chunk_type) {
|
|
case OP_DEFPOINTS: break;
|
|
case OP_FLATPOLY: n++; break;
|
|
case OP_TMAPPOLY: n++; break;
|
|
case OP_SORTNORM: {
|
|
int frontlist = w(p+36);
|
|
int backlist = w(p+40);
|
|
int prelist = w(p+44);
|
|
int postlist = w(p+48);
|
|
int onlist = w(p+52);
|
|
n += submodel_get_num_polys_sub(p+frontlist);
|
|
n += submodel_get_num_polys_sub(p+backlist);
|
|
n += submodel_get_num_polys_sub(p+prelist);
|
|
n += submodel_get_num_polys_sub(p+postlist );
|
|
n += submodel_get_num_polys_sub(p+onlist );
|
|
}
|
|
break;
|
|
|
|
case OP_SORTNORM2: {
|
|
int frontlist = w(p + 8);
|
|
int backlist = w(p + 12);
|
|
n += submodel_get_num_polys_sub(p + frontlist);
|
|
n += submodel_get_num_polys_sub(p + backlist);
|
|
}
|
|
end = true; // should not continue after this chunk
|
|
break;
|
|
case OP_BOUNDBOX: break;
|
|
case OP_TMAP2POLY:
|
|
n++;
|
|
end = true; // should not continue after this chunk
|
|
break;
|
|
default:
|
|
mprintf(( "Bad chunk type %d, len=%d in submodel_get_num_polys\n", chunk_type, chunk_size ));
|
|
Int3(); // Bad chunk type!
|
|
return 0;
|
|
}
|
|
if (end) break;
|
|
|
|
p += chunk_size;
|
|
chunk_type = w(p);
|
|
chunk_size = w(p+4);
|
|
|
|
if (chunk_type == OP_EOF)
|
|
end = true;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
/**
|
|
* Returns number of tmaps & flat polys in a submodel
|
|
*/
|
|
int submodel_get_num_polys(int model_num, int submodel_num )
|
|
{
|
|
polymodel * pm;
|
|
|
|
pm = model_get(model_num);
|
|
|
|
return submodel_get_num_polys_sub( pm->submodel[submodel_num].bsp_data.get() );
|
|
}
|
|
|
|
/**
|
|
* See if the given texture is used by the passed model. 0 if not used, 1 if used, -1 on error
|
|
*/
|
|
int model_find_texture(int model_num, int bitmap)
|
|
{
|
|
polymodel * pm;
|
|
int idx;
|
|
|
|
// get a handle to the model
|
|
pm = model_get(model_num);
|
|
if(pm == NULL){
|
|
return -1;
|
|
}
|
|
|
|
// find the texture
|
|
for(idx=0; idx<pm->n_textures; idx++)
|
|
{
|
|
if(pm->maps[idx].FindTexture(bitmap) > -1)
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// no texture
|
|
return 0;
|
|
}
|
|
|
|
// find closest point on extended bounding box (the bounding box plus all the planes that make it up)
|
|
// returns closest distance to extended box
|
|
// positive return value means start_point is outside extended box
|
|
// displaces closest point an optional amount delta to the outside of the box
|
|
// closest_box_point can be NULL.
|
|
float get_model_closest_box_point_with_delta(vec3d *closest_box_point, const vec3d *start_point, int modelnum, int *is_inside, float delta)
|
|
{
|
|
int i, idx;
|
|
vec3d box_point, ray_direction, *extremes;
|
|
float dist, best_dist;
|
|
polymodel *pm;
|
|
int inside = 0;
|
|
int masks[6] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20};
|
|
int mask_inside = 0x3f;
|
|
|
|
best_dist = FLT_MAX;
|
|
pm = model_get(modelnum);
|
|
|
|
for (i=0; i<6; i++) {
|
|
idx = i / 2; // which row vector of Identity matrix
|
|
|
|
memcpy(&ray_direction, vmd_identity_matrix.a2d[idx], sizeof(vec3d));
|
|
|
|
// do negative, then positive plane for each axis
|
|
if (2 * idx == i) {
|
|
extremes = &pm->mins;
|
|
vm_vec_negate(&ray_direction);
|
|
} else {
|
|
extremes = &pm->maxs;
|
|
}
|
|
|
|
// a negative distance means started outside the box
|
|
dist = fvi_ray_plane(&box_point, extremes, &ray_direction, start_point, &ray_direction, 0.0f);
|
|
if (dist > 0) {
|
|
inside |= masks[i];
|
|
}
|
|
if (fabs(dist) < fabs(best_dist)) {
|
|
best_dist = dist;
|
|
if (closest_box_point) {
|
|
vm_vec_scale_add(closest_box_point, &box_point, &ray_direction, delta);
|
|
}
|
|
}
|
|
}
|
|
|
|
// is start_point inside the box
|
|
if (is_inside) {
|
|
*is_inside = (inside == mask_inside);
|
|
}
|
|
|
|
return -best_dist;
|
|
}
|
|
|
|
// find closest point on extended bounding box (the bounding box plus all the planes that make it up)
|
|
// returns closest distance to extended box
|
|
// positive return value means start_point is outside extended box
|
|
// displaces closest point an optional amount delta to the outside of the box
|
|
// closest_box_point can be NULL.
|
|
float get_world_closest_box_point_with_delta(vec3d *closest_box_point, const object *box_obj, const vec3d *start_point, int *is_inside, float delta)
|
|
{
|
|
vec3d temp, box_start;
|
|
float dist;
|
|
int modelnum;
|
|
|
|
// get modelnum
|
|
modelnum = Ship_info[Ships[box_obj->instance].ship_info_index].model_num;
|
|
|
|
// rotate start_point to box_obj RF
|
|
vm_vec_sub(&temp, start_point, &box_obj->pos);
|
|
vm_vec_rotate(&box_start, &temp, &box_obj->orient);
|
|
|
|
dist = get_model_closest_box_point_with_delta(closest_box_point, &box_start, modelnum, is_inside, delta);
|
|
|
|
// rotate closest_box_point to world RF
|
|
if (closest_box_point) {
|
|
vm_vec_unrotate(&temp, closest_box_point, &box_obj->orient);
|
|
vm_vec_add(closest_box_point, &temp, &box_obj->pos);
|
|
}
|
|
|
|
return dist;
|
|
}
|
|
|
|
/**
|
|
* Given a newly loaded model, page in all textures
|
|
*/
|
|
void model_page_in_textures(int modelnum, int ship_info_index)
|
|
{
|
|
int i, idx;
|
|
polymodel *pm = model_get(modelnum);
|
|
|
|
// bogus
|
|
if (pm == NULL)
|
|
return;
|
|
|
|
for (idx = 0; idx < pm->n_textures; idx++) {
|
|
pm->maps[idx].PageIn();
|
|
}
|
|
|
|
for (i = 0; i < pm->n_glow_point_banks; i++) {
|
|
glow_point_bank *bank = &pm->glow_point_banks[i];
|
|
|
|
bm_page_in_texture(bank->glow_bitmap);
|
|
bm_page_in_texture(bank->glow_neb_bitmap);
|
|
}
|
|
|
|
if (ship_info_index >= 0)
|
|
ship_page_in_textures(ship_info_index);
|
|
}
|
|
|
|
// unload all textures for a given model
|
|
// "release" should only be set if called from model_unload()!!!
|
|
void model_page_out_textures(int model_num, bool release)
|
|
{
|
|
if (model_num < 0)
|
|
return;
|
|
|
|
polymodel *pm = model_get(model_num);
|
|
|
|
if (pm == NULL)
|
|
return;
|
|
|
|
if (release && (pm->used_this_mission > 0))
|
|
return;
|
|
|
|
model_page_out_textures(pm, release);
|
|
}
|
|
|
|
void model_page_out_textures(polymodel* pm, bool release, const SCP_set<int>& skipTextures, const SCP_set<int>& skipGlowBanks)
|
|
{
|
|
int i, j;
|
|
for (i = 0; i < pm->n_textures; i++) {
|
|
if (skipTextures.contains(i))
|
|
continue;
|
|
pm->maps[i].PageOut(release);
|
|
}
|
|
|
|
// NOTE: "release" doesn't work here for some, as of yet unknown, reason - taylor
|
|
for (j = 0; j < pm->n_glow_point_banks; j++) {
|
|
if(skipGlowBanks.contains(j))
|
|
continue;
|
|
glow_point_bank* bank = &pm->glow_point_banks[j];
|
|
|
|
if (bank->glow_bitmap >= 0) {
|
|
// if (release) {
|
|
// bm_release(bank->glow_bitmap);
|
|
// bank->glow_bitmap = -1;
|
|
// } else {
|
|
bm_unload(bank->glow_bitmap);
|
|
// }
|
|
}
|
|
|
|
if (bank->glow_neb_bitmap >= 0) {
|
|
// if (release) {
|
|
// bm_release(bank->glow_neb_bitmap);
|
|
// bank->glow_neb_bitmap = -1;
|
|
// } else {
|
|
bm_unload(bank->glow_neb_bitmap);
|
|
// }
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//**********vertex buffer stuff**********//
|
|
int tri_count[MAX_MODEL_TEXTURES];
|
|
poly_list polygon_list[MAX_MODEL_TEXTURES];
|
|
|
|
void parse_defpoint(int off, ubyte *bsp_data)
|
|
{
|
|
uint i, n;
|
|
uint nverts = uw(off+bsp_data+8);
|
|
uint offset = uw(off+bsp_data+16);
|
|
uint next_norm = 0;
|
|
|
|
ubyte *normcount = off+bsp_data+20;
|
|
vec3d *src = vp(off+bsp_data+offset);
|
|
|
|
// Get pointer to lights
|
|
Interp_lights = off+bsp_data+20+nverts;
|
|
|
|
#ifndef NDEBUG
|
|
modelstats_num_verts += nverts;
|
|
#endif
|
|
|
|
for (n = 0; n < nverts; n++) {
|
|
Interp_verts[n] = src;
|
|
src++; // move to normal
|
|
|
|
for (i = 0; i < normcount[n]; i++) {
|
|
Interp_norms[next_norm] = src;
|
|
|
|
next_norm++;
|
|
src++;
|
|
}
|
|
}
|
|
}
|
|
|
|
int check_values(vec3d *N)
|
|
{
|
|
// Values equal to -1.#IND0
|
|
if(!is_valid_vec(N))
|
|
{
|
|
N->xyz.x = 1.0f;
|
|
N->xyz.y = 0.0f;
|
|
N->xyz.z = 0.0f;
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int Parse_normal_problem_count = 0;
|
|
|
|
void parse_tmap(int offset, ubyte *bsp_data)
|
|
{
|
|
int pof_tex = w(bsp_data+offset+TMAP_TEXNUM);
|
|
uint n_vert = uw(bsp_data+offset+TMAP_NVERTS);
|
|
ubyte *p = &bsp_data[offset+TMAP_NORMAL];
|
|
auto tverts = reinterpret_cast<model_tmap_vert_old*>(&bsp_data[offset + TMAP_VERTS]);
|
|
|
|
vertex *V;
|
|
vec3d *v;
|
|
vec3d *N;
|
|
|
|
int problem_count = 0;
|
|
|
|
for (uint i = 1; i < (n_vert-1); i++) {
|
|
V = &polygon_list[pof_tex].vert[(polygon_list[pof_tex].n_verts)];
|
|
N = &polygon_list[pof_tex].norm[(polygon_list[pof_tex].n_verts)];
|
|
v = Interp_verts[tverts[0].vertnum];
|
|
V->world.xyz.x = v->xyz.x;
|
|
V->world.xyz.y = v->xyz.y;
|
|
V->world.xyz.z = v->xyz.z;
|
|
V->texture_position.u = tverts[0].u;
|
|
V->texture_position.v = tverts[0].v;
|
|
|
|
*N = *Interp_norms[tverts[0].normnum];
|
|
|
|
if ( IS_VEC_NULL(N) )
|
|
*N = *vp(p);
|
|
|
|
problem_count += check_values(N);
|
|
vm_vec_normalize_safe(N);
|
|
|
|
V = &polygon_list[pof_tex].vert[(polygon_list[pof_tex].n_verts)+1];
|
|
N = &polygon_list[pof_tex].norm[(polygon_list[pof_tex].n_verts)+1];
|
|
v = Interp_verts[tverts[i].vertnum];
|
|
V->world.xyz.x = v->xyz.x;
|
|
V->world.xyz.y = v->xyz.y;
|
|
V->world.xyz.z = v->xyz.z;
|
|
V->texture_position.u = tverts[i].u;
|
|
V->texture_position.v = tverts[i].v;
|
|
|
|
*N = *Interp_norms[tverts[i].normnum];
|
|
|
|
if ( IS_VEC_NULL(N) )
|
|
*N = *vp(p);
|
|
|
|
problem_count += check_values(N);
|
|
vm_vec_normalize_safe(N);
|
|
|
|
V = &polygon_list[pof_tex].vert[(polygon_list[pof_tex].n_verts)+2];
|
|
N = &polygon_list[pof_tex].norm[(polygon_list[pof_tex].n_verts)+2];
|
|
v = Interp_verts[tverts[i+1].vertnum];
|
|
V->world.xyz.x = v->xyz.x;
|
|
V->world.xyz.y = v->xyz.y;
|
|
V->world.xyz.z = v->xyz.z;
|
|
V->texture_position.u = tverts[i+1].u;
|
|
V->texture_position.v = tverts[i+1].v;
|
|
|
|
*N = *Interp_norms[tverts[i+1].normnum];
|
|
|
|
if ( IS_VEC_NULL(N) )
|
|
*N = *vp(p);
|
|
|
|
problem_count += check_values(N);
|
|
vm_vec_normalize_safe(N);
|
|
|
|
polygon_list[pof_tex].n_verts += 3;
|
|
}
|
|
|
|
Parse_normal_problem_count += problem_count;
|
|
}
|
|
|
|
/**
|
|
* @brief Parses a TMAP2POLY chunk into a list of polygons.
|
|
*
|
|
* @param offset The byte offset to the current TMAP2POLY chunk within bsp_data.
|
|
* @param[in] bsp_data The byte buffer containing the BSP information for the current model.
|
|
*/
|
|
void parse_tmap2(int offset, ubyte* bsp_data)
|
|
{
|
|
int pof_tex = w(bsp_data + offset + TMAP2_TEXNUM);
|
|
uint n_vert = uw(bsp_data + offset + TMAP2_NVERTS);
|
|
|
|
ubyte* p = &bsp_data[offset + TMAP2_NORMAL];
|
|
model_tmap_vert* tverts;
|
|
|
|
vertex* V;
|
|
vec3d* v;
|
|
vec3d* N;
|
|
|
|
int problem_count = 0;
|
|
|
|
tverts = reinterpret_cast<model_tmap_vert*>(&bsp_data[offset + TMAP2_VERTS]);
|
|
|
|
for (uint i = 1; i < (n_vert - 1); i++) {
|
|
V = &polygon_list[pof_tex].vert[(polygon_list[pof_tex].n_verts)];
|
|
N = &polygon_list[pof_tex].norm[(polygon_list[pof_tex].n_verts)];
|
|
v = Interp_verts[tverts[0].vertnum];
|
|
V->world.xyz.x = v->xyz.x;
|
|
V->world.xyz.y = v->xyz.y;
|
|
V->world.xyz.z = v->xyz.z;
|
|
V->texture_position.u = tverts[0].u;
|
|
V->texture_position.v = tverts[0].v;
|
|
|
|
*N = *Interp_norms[tverts[0].normnum];
|
|
|
|
if (IS_VEC_NULL(N))
|
|
*N = *vp(p);
|
|
|
|
problem_count += check_values(N);
|
|
vm_vec_normalize_safe(N);
|
|
|
|
V = &polygon_list[pof_tex].vert[(polygon_list[pof_tex].n_verts) + 1];
|
|
N = &polygon_list[pof_tex].norm[(polygon_list[pof_tex].n_verts) + 1];
|
|
v = Interp_verts[tverts[i].vertnum];
|
|
V->world.xyz.x = v->xyz.x;
|
|
V->world.xyz.y = v->xyz.y;
|
|
V->world.xyz.z = v->xyz.z;
|
|
V->texture_position.u = tverts[i].u;
|
|
V->texture_position.v = tverts[i].v;
|
|
|
|
*N = *Interp_norms[tverts[i].normnum];
|
|
|
|
if (IS_VEC_NULL(N))
|
|
*N = *vp(p);
|
|
|
|
problem_count += check_values(N);
|
|
vm_vec_normalize_safe(N);
|
|
|
|
V = &polygon_list[pof_tex].vert[(polygon_list[pof_tex].n_verts) + 2];
|
|
N = &polygon_list[pof_tex].norm[(polygon_list[pof_tex].n_verts) + 2];
|
|
v = Interp_verts[tverts[i + 1].vertnum];
|
|
V->world.xyz.x = v->xyz.x;
|
|
V->world.xyz.y = v->xyz.y;
|
|
V->world.xyz.z = v->xyz.z;
|
|
V->texture_position.u = tverts[i + 1].u;
|
|
V->texture_position.v = tverts[i + 1].v;
|
|
|
|
*N = *Interp_norms[tverts[i + 1].normnum];
|
|
|
|
if (IS_VEC_NULL(N))
|
|
*N = *vp(p);
|
|
|
|
problem_count += check_values(N);
|
|
vm_vec_normalize_safe(N);
|
|
|
|
polygon_list[pof_tex].n_verts += 3;
|
|
}
|
|
|
|
Parse_normal_problem_count += problem_count;
|
|
}
|
|
|
|
void parse_bsp(int offset, ubyte* bsp_data);
|
|
|
|
void parse_sortnorm(int offset, ubyte* bsp_data)
|
|
{
|
|
int frontlist, backlist, prelist, postlist, onlist;
|
|
|
|
frontlist = w(bsp_data + offset + 36);
|
|
backlist = w(bsp_data + offset + 40);
|
|
prelist = w(bsp_data + offset + 44);
|
|
postlist = w(bsp_data + offset + 48);
|
|
onlist = w(bsp_data + offset + 52);
|
|
|
|
if (prelist) parse_bsp(offset + prelist, bsp_data);
|
|
if (backlist) parse_bsp(offset + backlist, bsp_data);
|
|
if (onlist) parse_bsp(offset + onlist, bsp_data);
|
|
if (frontlist) parse_bsp(offset + frontlist, bsp_data);
|
|
if (postlist) parse_bsp(offset + postlist, bsp_data);
|
|
}
|
|
|
|
/**
|
|
* @brief Parses a SORTNORM2 by recursively parsing into the two pointers it contains.
|
|
*
|
|
* @param offset The byte offset to the current SORT2NORM chunk within bsp_data.
|
|
* @param bsp_data The byte buffer containing the BSP information for the current model.
|
|
*/
|
|
void parse_sortnorm2(int offset, ubyte* bsp_data)
|
|
{
|
|
int frontlist, backlist;
|
|
|
|
frontlist = w(bsp_data + offset + 8);
|
|
backlist = w(bsp_data + offset + 12);
|
|
|
|
if (backlist) parse_bsp(offset + backlist, bsp_data);
|
|
if (frontlist) parse_bsp(offset + frontlist, bsp_data);
|
|
}
|
|
|
|
void parse_bsp(int offset, ubyte *bsp_data)
|
|
{
|
|
int id = w(bsp_data+offset);
|
|
int size = w(bsp_data+offset+4);
|
|
|
|
bool end = id == OP_EOF;
|
|
while (!end) {
|
|
switch (id)
|
|
{
|
|
case OP_DEFPOINTS:
|
|
parse_defpoint(offset, bsp_data);
|
|
break;
|
|
|
|
case OP_SORTNORM:
|
|
parse_sortnorm(offset, bsp_data);
|
|
break;
|
|
|
|
case OP_SORTNORM2:
|
|
parse_sortnorm2(offset, bsp_data);
|
|
end = true; // should not continue after this chunk
|
|
break;
|
|
|
|
case OP_FLATPOLY:
|
|
break;
|
|
|
|
case OP_TMAPPOLY:
|
|
parse_tmap(offset, bsp_data);
|
|
break;
|
|
|
|
case OP_BOUNDBOX:
|
|
break;
|
|
|
|
case OP_TMAP2POLY:
|
|
parse_tmap2(offset, bsp_data);
|
|
end = true; // should not continue after this chunk
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
if (end) break;
|
|
|
|
offset += size;
|
|
id = w(bsp_data+offset);
|
|
size = w(bsp_data+offset+4);
|
|
|
|
if (size < 1 || id == OP_EOF)
|
|
end = true;
|
|
}
|
|
}
|
|
|
|
void find_tmap(int offset, const ubyte *bsp_data, int id)
|
|
{
|
|
int pof_tex = cw(bsp_data+offset+(id == OP_TMAP2POLY ? TMAP2_TEXNUM : TMAP_TEXNUM));
|
|
uint n_vert = cuw(bsp_data+offset+ (id == OP_TMAP2POLY ? TMAP2_NVERTS : TMAP_NVERTS));
|
|
|
|
tri_count[pof_tex] += n_vert-2;
|
|
}
|
|
|
|
void find_defpoint(int off, ubyte *bsp_data)
|
|
{
|
|
uint n;
|
|
uint nverts = uw(off+bsp_data+8);
|
|
|
|
ubyte * normcount = off+bsp_data+20;
|
|
|
|
// Get pointer to lights
|
|
Interp_lights = off+bsp_data+20+nverts;
|
|
|
|
#ifndef NDEBUG
|
|
modelstats_num_verts += nverts;
|
|
#endif
|
|
|
|
int norm_num = 0;
|
|
|
|
for (n = 0; n < nverts; n++) {
|
|
norm_num += normcount[n];
|
|
}
|
|
|
|
Interp_num_verts = nverts;
|
|
Interp_num_norms = norm_num;
|
|
}
|
|
|
|
void find_tri_counts(int offset, ubyte* bsp_data);
|
|
|
|
void find_sortnorm(int offset, ubyte* bsp_data)
|
|
{
|
|
int frontlist, backlist, prelist, postlist, onlist;
|
|
|
|
frontlist = w(bsp_data + offset + 36);
|
|
backlist = w(bsp_data + offset + 40);
|
|
prelist = w(bsp_data + offset + 44);
|
|
postlist = w(bsp_data + offset + 48);
|
|
onlist = w(bsp_data + offset + 52);
|
|
|
|
if (prelist) find_tri_counts(offset + prelist, bsp_data);
|
|
if (backlist) find_tri_counts(offset + backlist, bsp_data);
|
|
if (onlist) find_tri_counts(offset + onlist, bsp_data);
|
|
if (frontlist) find_tri_counts(offset + frontlist, bsp_data);
|
|
if (postlist) find_tri_counts(offset + postlist, bsp_data);
|
|
}
|
|
|
|
void find_sortnorm2(int offset, ubyte* bsp_data)
|
|
{
|
|
int frontlist, backlist;
|
|
|
|
frontlist = w(bsp_data + offset + 8);
|
|
backlist = w(bsp_data + offset + 12);
|
|
|
|
if (backlist) find_tri_counts(offset + backlist, bsp_data);
|
|
if (frontlist) find_tri_counts(offset + frontlist, bsp_data);
|
|
}
|
|
|
|
// tri_count
|
|
void find_tri_counts(int offset, ubyte *bsp_data)
|
|
{
|
|
int id = w(bsp_data+offset);
|
|
int size = w(bsp_data+offset+4);
|
|
|
|
bool end = id == OP_EOF;
|
|
while (!end) {
|
|
switch (id)
|
|
{
|
|
case OP_DEFPOINTS:
|
|
find_defpoint(offset, bsp_data);
|
|
break;
|
|
|
|
case OP_SORTNORM:
|
|
find_sortnorm(offset, bsp_data);
|
|
break;
|
|
|
|
case OP_SORTNORM2:
|
|
find_sortnorm2(offset, bsp_data);
|
|
end = true; // should not continue after this chunk
|
|
break;
|
|
|
|
case OP_FLATPOLY:
|
|
break;
|
|
|
|
case OP_TMAPPOLY:
|
|
find_tmap(offset, bsp_data, id);
|
|
break;
|
|
case OP_TMAP2POLY:
|
|
find_tmap(offset, bsp_data, id);
|
|
end = true; // should not continue after this chunk
|
|
break;
|
|
|
|
case OP_BOUNDBOX:
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
if (end) break;
|
|
|
|
offset += size;
|
|
id = w(bsp_data+offset);
|
|
size = w(bsp_data+offset+4);
|
|
|
|
if (size < 1 || id == OP_EOF)
|
|
end = true;
|
|
}
|
|
}
|
|
|
|
void model_interp_submit_buffers(indexed_vertex_source *vert_src, size_t vertex_stride)
|
|
{
|
|
Assert(vert_src != NULL);
|
|
|
|
if ( !(vert_src->Vertex_list_size > 0 && vert_src->Index_list_size > 0 ) ) {
|
|
return;
|
|
}
|
|
|
|
if ( vert_src->Vertex_list != nullptr ) {
|
|
size_t offset;
|
|
gr_heap_allocate(GpuHeap::ModelVertex, vert_src->Vertex_list_size, vert_src->Vertex_list.get(), offset, vert_src->Vbuffer_handle);
|
|
|
|
// If this happens then someone must have allocated something from the heap with a different stride than what we
|
|
// are using.
|
|
Assertion(offset % vertex_stride == 0, "Offset returned by GPU heap allocation does not match stride value!");
|
|
vert_src->Base_vertex_offset = offset / vertex_stride;
|
|
vert_src->Vertex_offset = offset;
|
|
|
|
vert_src->Vertex_list.reset();
|
|
}
|
|
|
|
if ( vert_src->Index_list != nullptr ) {
|
|
gr_heap_allocate(GpuHeap::ModelIndex, vert_src->Index_list_size, vert_src->Index_list.get(), vert_src->Index_offset, vert_src->Ibuffer_handle);
|
|
|
|
vert_src->Index_list.reset();
|
|
}
|
|
}
|
|
|
|
bool model_interp_pack_buffer(indexed_vertex_source *vert_src, vertex_buffer *vb)
|
|
{
|
|
if ( vert_src == NULL ) {
|
|
return false;
|
|
}
|
|
|
|
Assertion(vb != nullptr, "Invalid vertex buffer specified!");
|
|
|
|
int i, n_verts = 0;
|
|
size_t j;
|
|
if ( vert_src->Vertex_list == nullptr ) {
|
|
vert_src->Vertex_list = make_shared<uint8_t[]>(vert_src->Vertex_list_size);
|
|
|
|
// return invalid if we don't have the memory
|
|
if ( vert_src->Vertex_list == nullptr ) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ( vert_src->Index_list == nullptr ) {
|
|
vert_src->Index_list = make_shared<uint8_t[]>(vert_src->Index_list_size);
|
|
|
|
// return invalid if we don't have the memory
|
|
if ( vert_src->Index_list == nullptr ) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// bump to our index in the array
|
|
auto array = reinterpret_cast<interp_vertex*>(vert_src->Vertex_list.get() + vb->vertex_offset);
|
|
|
|
// generate the vertex array
|
|
n_verts = vb->model_list->n_verts;
|
|
for ( i = 0; i < n_verts; i++ ) {
|
|
vertex *vl = &vb->model_list->vert[i];
|
|
auto outVert = &array[i];
|
|
|
|
// don't try to generate more data than what's available
|
|
Assert(((i * sizeof(interp_vertex)) + sizeof(interp_vertex)) <= (vert_src->Vertex_list_size - vb->vertex_offset));
|
|
|
|
// NOTE: UV->NORM->TSB->MODEL_ID->VERT, This array order *must* be preserved!!
|
|
|
|
// tex coords
|
|
if ( vb->flags & VB_FLAG_UV1 ) {
|
|
outVert->uv = vl->texture_position;
|
|
} else {
|
|
outVert->uv.u = 1.0f;
|
|
outVert->uv.v = 1.0f;
|
|
}
|
|
|
|
// normals
|
|
if ( vb->flags & VB_FLAG_NORMAL ) {
|
|
Assert(vb->model_list->norm != NULL);
|
|
outVert->normal = vb->model_list->norm[i];
|
|
} else {
|
|
outVert->normal.xyz.x = 0.0f;
|
|
outVert->normal.xyz.y = 0.0f;
|
|
outVert->normal.xyz.z = 1.0f;
|
|
}
|
|
|
|
// tangent space data
|
|
if ( vb->flags & VB_FLAG_TANGENT ) {
|
|
Assert(vb->model_list->tsb != NULL);
|
|
tsb_t *tsb = &vb->model_list->tsb[i];
|
|
|
|
outVert->tangent.xyzw.x = tsb->tangent.xyz.x;
|
|
outVert->tangent.xyzw.y = tsb->tangent.xyz.y;
|
|
outVert->tangent.xyzw.z = tsb->tangent.xyz.z;
|
|
outVert->tangent.xyzw.w = tsb->scaler;
|
|
} else {
|
|
outVert->tangent.xyzw.x = 1.0f;
|
|
outVert->tangent.xyzw.y = 0.0f;
|
|
outVert->tangent.xyzw.z = 0.0f;
|
|
outVert->tangent.xyzw.w = 0.0f;
|
|
}
|
|
|
|
if ( vb->flags & VB_FLAG_MODEL_ID ) {
|
|
Assert(vb->model_list->submodels != NULL);
|
|
outVert->modelId = (float)vb->model_list->submodels[i];
|
|
} else {
|
|
outVert->modelId = 0.0f;
|
|
}
|
|
|
|
// verts
|
|
outVert->pos = vl->world;
|
|
}
|
|
|
|
// generate the index array
|
|
for ( j = 0; j < vb->tex_buf.size(); j++ ) {
|
|
buffer_data* tex_buf = &vb->tex_buf[j];
|
|
n_verts = (int)tex_buf->n_verts;
|
|
auto offset = tex_buf->index_offset;
|
|
const uint *index = tex_buf->get_index();
|
|
|
|
// bump to our spot in the buffer
|
|
auto ibuf = vert_src->Index_list.get() + offset;
|
|
|
|
if ( vb->tex_buf[j].flags & VB_FLAG_LARGE_INDEX ) {
|
|
memcpy(ibuf, index, n_verts * sizeof(uint));
|
|
} else {
|
|
ushort *mybuf = (ushort*)ibuf;
|
|
|
|
for ( i = 0; i < n_verts; i++ ) {
|
|
mybuf[i] = (ushort)index[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void interp_pack_vertex_buffers(polymodel *pm, int mn)
|
|
{
|
|
Assert( (mn >= 0) && (mn < pm->n_models) );
|
|
|
|
bsp_info *model = &pm->submodel[mn];
|
|
|
|
if ( !model->buffer.model_list ) {
|
|
return;
|
|
}
|
|
|
|
bool rval = model_interp_pack_buffer(&pm->vert_source, &model->buffer);
|
|
|
|
if ( model->trans_buffer.flags & VB_FLAG_TRANS && !model->trans_buffer.tex_buf.empty() ) {
|
|
model_interp_pack_buffer(&pm->vert_source, &model->trans_buffer);
|
|
}
|
|
|
|
if ( !rval ) {
|
|
Error( LOCATION, "Unable to pack vertex buffer for '%s'\n", pm->filename );
|
|
}
|
|
}
|
|
|
|
void model_interp_set_buffer_layout(vertex_layout *layout)
|
|
{
|
|
Assert(layout != NULL);
|
|
|
|
// Similarly to model_interp_config_buffer, we add all vectex components even if they aren't used
|
|
// This reduces the amount of vertex format respecification and since the data contains valid data there is no risk
|
|
// of reading garbage data on the GPU
|
|
|
|
layout->add_vertex_component(vertex_format_data::TEX_COORD2, sizeof(interp_vertex), offsetof(interp_vertex, uv));
|
|
|
|
layout->add_vertex_component(vertex_format_data::NORMAL, sizeof(interp_vertex), offsetof(interp_vertex, normal));
|
|
|
|
layout->add_vertex_component(vertex_format_data::TANGENT, sizeof(interp_vertex), offsetof(interp_vertex, tangent));
|
|
|
|
layout->add_vertex_component(vertex_format_data::MODEL_ID, sizeof(interp_vertex), offsetof(interp_vertex, modelId));
|
|
|
|
layout->add_vertex_component(vertex_format_data::POSITION3, sizeof(interp_vertex), offsetof(interp_vertex, pos));
|
|
}
|
|
|
|
bool model_interp_config_buffer(indexed_vertex_source *vert_src, vertex_buffer *vb, bool update_ibuffer_only)
|
|
{
|
|
if ( vb == NULL ) {
|
|
return false;
|
|
}
|
|
|
|
if ( !(vb->flags & VB_FLAG_POSITION) ) {
|
|
Int3();
|
|
return false;
|
|
}
|
|
|
|
// pad out the vertex buffer even if it doesn't use certain attributes
|
|
// we require consistent stride across vertex buffers so we can use base vertex offsetting for performance reasons
|
|
vb->stride = sizeof(interp_vertex);
|
|
|
|
model_interp_set_buffer_layout(&vb->layout);
|
|
|
|
// offsets for this chunk
|
|
if ( !update_ibuffer_only ) {
|
|
vb->vertex_offset = vert_src->Vertex_list_size;
|
|
vb->vertex_num_offset = vb->vertex_offset / vb->stride;
|
|
vert_src->Vertex_list_size += (uint)(vb->stride * vb->model_list->n_verts);
|
|
}
|
|
|
|
for ( size_t idx = 0; idx < vb->tex_buf.size(); idx++ ) {
|
|
buffer_data *bd = &vb->tex_buf[idx];
|
|
|
|
bd->index_offset = vert_src->Index_list_size;
|
|
vert_src->Index_list_size += (uint)(bd->n_verts * ((bd->flags & VB_FLAG_LARGE_INDEX) ? sizeof(uint) : sizeof(ushort)));
|
|
|
|
// even out index buffer so we are always word aligned
|
|
vert_src->Index_list_size += (uint)(vert_src->Index_list_size % sizeof(uint));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void interp_configure_vertex_buffers(polymodel *pm, int mn, const model_read_deferred_tasks& deferredTasks)
|
|
{
|
|
TRACE_SCOPE(tracing::ModelConfigureVertexBuffers);
|
|
|
|
int i, j, first_index;
|
|
uint total_verts = 0;
|
|
SCP_vector<int> vertex_list;
|
|
|
|
Assert( (mn >= 0) && (mn < pm->n_models) );
|
|
|
|
bsp_info *model = &pm->submodel[mn];
|
|
|
|
for (i = 0; i < MAX_MODEL_TEXTURES; i++) {
|
|
polygon_list[i].n_verts = 0;
|
|
tri_count[i] = 0;
|
|
}
|
|
|
|
int milliseconds = timer_get_milliseconds();
|
|
|
|
auto bsp_polies = new bsp_polygon_data(model->bsp_data.get(), model->bsp_data_size);
|
|
|
|
auto textureReplace = deferredTasks.texture_replacements.find(mn);
|
|
if (textureReplace != deferredTasks.texture_replacements.end())
|
|
bsp_polies->replace_textures_used(textureReplace->second.replacementIds);
|
|
|
|
for (i = 0; i < MAX_MODEL_TEXTURES; i++) {
|
|
int vert_count = bsp_polies->get_num_triangles(i) * 3;
|
|
tri_count[i] = vert_count / 3;
|
|
total_verts += vert_count;
|
|
|
|
polygon_list[i].allocate(vert_count);
|
|
|
|
bsp_polies->generate_triangles(i, polygon_list[i].vert, polygon_list[i].norm);
|
|
polygon_list[i].n_verts = vert_count;
|
|
|
|
// set submodel ID
|
|
for ( j = 0; j < polygon_list[i].n_verts; ++j ) {
|
|
polygon_list[i].submodels[j] = mn;
|
|
}
|
|
|
|
// for the moment we can only support INT_MAX worth of verts per index buffer
|
|
if (total_verts > INT_MAX) {
|
|
Error( LOCATION, "Unable to generate vertex buffer data because model '%s' with %i verts is over the maximum of %i verts!\n", pm->filename, total_verts, INT_MAX);
|
|
}
|
|
}
|
|
|
|
// figure out if we have an outline
|
|
int outline_n_lines = bsp_polies->get_num_lines(-1);
|
|
|
|
if ( outline_n_lines > 0 ) {
|
|
model->n_verts_outline = outline_n_lines * 2;
|
|
model->outline_buffer = make_shared<vertex[]>(model->n_verts_outline);
|
|
|
|
bsp_polies->generate_lines(-1, model->outline_buffer.get());
|
|
}
|
|
|
|
// done with the bsp now that we have the vertex data
|
|
delete bsp_polies;
|
|
|
|
int time_elapsed = timer_get_milliseconds() - milliseconds;
|
|
|
|
nprintf(("Model", "BSP Parse took %d milliseconds.\n", time_elapsed));
|
|
|
|
if (total_verts < 1) {
|
|
return;
|
|
}
|
|
|
|
total_verts = 0;
|
|
|
|
for (i = 0; i < MAX_MODEL_TEXTURES; i++) {
|
|
total_verts += polygon_list[i].n_verts;
|
|
}
|
|
|
|
poly_list *model_list = new(std::nothrow) poly_list;
|
|
|
|
if ( !model_list ) {
|
|
Error( LOCATION, "Unable to allocate memory for poly_list!\n" );
|
|
}
|
|
|
|
model->buffer.model_list = model_list;
|
|
|
|
model_list->allocate( (int)total_verts );
|
|
|
|
for (i = 0; i < MAX_MODEL_TEXTURES; i++) {
|
|
if ( !polygon_list[i].n_verts )
|
|
continue;
|
|
|
|
memcpy( (model_list->vert) + model_list->n_verts, polygon_list[i].vert, sizeof(vertex) * polygon_list[i].n_verts );
|
|
memcpy( (model_list->norm) + model_list->n_verts, polygon_list[i].norm, sizeof(vec3d) * polygon_list[i].n_verts );
|
|
|
|
if (Cmdline_normal) {
|
|
memcpy( (model_list->tsb) + model_list->n_verts, polygon_list[i].tsb, sizeof(tsb_t) * polygon_list[i].n_verts );
|
|
}
|
|
|
|
memcpy( (model_list->submodels) + model_list->n_verts, polygon_list[i].submodels, sizeof(int) * polygon_list[i].n_verts );
|
|
|
|
model_list->n_verts += polygon_list[i].n_verts;
|
|
}
|
|
|
|
// no read file so we'll have to generate
|
|
model_list->make_index_buffer(vertex_list);
|
|
|
|
vertex_list.clear(); // done
|
|
|
|
int vertex_flags = (VB_FLAG_POSITION | VB_FLAG_NORMAL | VB_FLAG_UV1);
|
|
|
|
if (model_list->tsb != NULL) {
|
|
Assert( Cmdline_normal );
|
|
vertex_flags |= VB_FLAG_TANGENT;
|
|
}
|
|
|
|
if ( model_list->submodels != NULL ) {
|
|
vertex_flags |= VB_FLAG_MODEL_ID;
|
|
}
|
|
|
|
model->buffer.flags = vertex_flags;
|
|
|
|
for (i = 0; i < MAX_MODEL_TEXTURES; i++) {
|
|
if ( !polygon_list[i].n_verts )
|
|
continue;
|
|
|
|
buffer_data new_buffer(polygon_list[i].n_verts);
|
|
|
|
Verify( new_buffer.get_index() != NULL );
|
|
|
|
for (j = 0; j < polygon_list[i].n_verts; j++) {
|
|
first_index = model_list->find_index_fast(&polygon_list[i], j);
|
|
Assert(first_index != -1);
|
|
|
|
new_buffer.assign(j, first_index);
|
|
}
|
|
|
|
new_buffer.texture = i;
|
|
|
|
new_buffer.flags = 0;
|
|
|
|
if (polygon_list[i].n_verts >= USHRT_MAX) {
|
|
new_buffer.flags |= VB_FLAG_LARGE_INDEX;
|
|
}
|
|
|
|
model->buffer.tex_buf.push_back( new_buffer );
|
|
}
|
|
|
|
bool rval = model_interp_config_buffer(&pm->vert_source, &model->buffer, false);
|
|
|
|
if ( !rval ) {
|
|
Error( LOCATION, "Unable to configure vertex buffer for '%s'\n", pm->filename );
|
|
}
|
|
}
|
|
|
|
void interp_copy_index_buffer(vertex_buffer *src, vertex_buffer *dest, size_t *index_counts)
|
|
{
|
|
size_t i, j, k;
|
|
size_t src_buff_size;
|
|
buffer_data *src_buffer;
|
|
buffer_data *dest_buffer;
|
|
size_t vert_offset = src->vertex_num_offset; // assuming all submodels crunched into this index buffer have the same stride
|
|
//int vert_offset = 0;
|
|
|
|
for ( i = 0; i < dest->tex_buf.size(); ++i ) {
|
|
dest_buffer = &dest->tex_buf[i];
|
|
|
|
for ( j = 0; j < src->tex_buf.size(); ++j ) {
|
|
if ( dest_buffer->texture != src->tex_buf[j].texture ) {
|
|
continue;
|
|
}
|
|
|
|
src_buffer = &src->tex_buf[j];
|
|
src_buff_size = (size_t)src_buffer->n_verts;
|
|
|
|
for ( k = 0; k < src_buff_size; ++k ) {
|
|
dest_buffer->assign(dest_buffer->n_verts, (uint32_t)(src_buffer->get_index()[k] + vert_offset)); // take into account the vertex offset.
|
|
dest_buffer->n_verts++;
|
|
|
|
Assert(dest_buffer->n_verts <= index_counts[dest_buffer->texture]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void interp_fill_detail_index_buffer(SCP_vector<int> &submodel_list, polymodel *pm, vertex_buffer *buffer)
|
|
{
|
|
size_t index_counts[MAX_MODEL_TEXTURES];
|
|
int i, j;
|
|
int model_num;
|
|
|
|
for ( i = 0; i < MAX_MODEL_TEXTURES; ++i ) {
|
|
index_counts[i] = 0;
|
|
}
|
|
|
|
buffer->vertex_offset = 0;
|
|
buffer->vertex_num_offset = 0;
|
|
buffer->model_list = new(std::nothrow) poly_list;
|
|
|
|
int num_buffers;
|
|
int tex_num;
|
|
|
|
// need to first count how many indexes there are in this entire detail model hierarchy
|
|
for ( i = 0; i < (int)submodel_list.size(); ++i ) {
|
|
model_num = submodel_list[i];
|
|
|
|
if ( pm->submodel[model_num].flags[Model::Submodel_flags::Is_thruster] ) {
|
|
continue;
|
|
}
|
|
|
|
num_buffers = (int)pm->submodel[model_num].buffer.tex_buf.size();
|
|
|
|
buffer->flags |= pm->submodel[model_num].buffer.flags;
|
|
|
|
for ( j = 0; j < num_buffers; ++j ) {
|
|
tex_num = pm->submodel[model_num].buffer.tex_buf[j].texture;
|
|
|
|
index_counts[tex_num] += pm->submodel[model_num].buffer.tex_buf[j].n_verts;
|
|
}
|
|
}
|
|
|
|
// allocate the respective texture buffers with indexes for our detail buffer
|
|
for ( i = 0; i < MAX_MODEL_TEXTURES; ++i ) {
|
|
if ( index_counts[i] == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
buffer->tex_buf.push_back(buffer_data(index_counts[i]));
|
|
|
|
buffer_data &new_buffer = buffer->tex_buf.back();
|
|
//new_buffer.n_verts = 0;
|
|
new_buffer.texture = i;
|
|
}
|
|
|
|
for ( i = 0; i < (int)buffer->tex_buf.size(); ++i ) {
|
|
buffer->tex_buf[i].n_verts = 0;
|
|
}
|
|
|
|
// finally copy over the indexes
|
|
for ( i = 0; i < (int)submodel_list.size(); ++i ) {
|
|
model_num = submodel_list[i];
|
|
|
|
if (pm->submodel[model_num].flags[Model::Submodel_flags::Is_thruster]) {
|
|
continue;
|
|
}
|
|
|
|
interp_copy_index_buffer(&pm->submodel[model_num].buffer, buffer, index_counts);
|
|
}
|
|
|
|
// check which buffers need to have the > USHORT flag
|
|
for ( i = 0; i < (int)buffer->tex_buf.size(); ++i ) {
|
|
if ( buffer->tex_buf[i].i_last >= USHRT_MAX ) {
|
|
buffer->tex_buf[i].flags |= VB_FLAG_LARGE_INDEX;
|
|
}
|
|
}
|
|
}
|
|
|
|
void interp_create_detail_index_buffer(polymodel *pm, int detail_num)
|
|
{
|
|
TRACE_SCOPE(tracing::ModelCreateDetailIndexBuffers);
|
|
|
|
SCP_vector<int> submodel_list;
|
|
|
|
submodel_list.clear();
|
|
|
|
model_get_submodel_tree_list(submodel_list, pm, pm->detail[detail_num]);
|
|
|
|
if ( submodel_list.empty() ) {
|
|
return;
|
|
}
|
|
|
|
interp_fill_detail_index_buffer(submodel_list, pm, &pm->detail_buffers[detail_num]);
|
|
|
|
// check if anything was even put into this buffer
|
|
if ( pm->detail_buffers[detail_num].tex_buf.empty() ) {
|
|
return;
|
|
}
|
|
|
|
model_interp_config_buffer(&pm->vert_source, &pm->detail_buffers[detail_num], true);
|
|
}
|
|
|
|
void interp_create_transparency_index_buffer(polymodel *pm, int mn)
|
|
{
|
|
TRACE_SCOPE(tracing::ModelCreateTransparencyIndexBuffer);
|
|
|
|
const int NUM_VERTS_PER_TRI = 3;
|
|
|
|
bsp_info *sub_model = &pm->submodel[mn];
|
|
|
|
vertex_buffer *trans_buffer = &sub_model->trans_buffer;
|
|
|
|
trans_buffer->model_list = new(std::nothrow) poly_list;
|
|
trans_buffer->vertex_offset = pm->submodel[mn].buffer.vertex_offset;
|
|
trans_buffer->vertex_num_offset = pm->submodel[mn].buffer.vertex_num_offset;
|
|
trans_buffer->stride = pm->submodel[mn].buffer.stride;
|
|
trans_buffer->flags = pm->submodel[mn].buffer.flags;
|
|
|
|
poly_list *model_list = pm->submodel[mn].buffer.model_list;
|
|
|
|
// bail out if this buffer is empty
|
|
if ( model_list == NULL || model_list->n_verts < 1 ) {
|
|
return;
|
|
}
|
|
|
|
SCP_vector<buffer_data> &tex_buffers = pm->submodel[mn].buffer.tex_buf;
|
|
uint current_tri[NUM_VERTS_PER_TRI];
|
|
SCP_vector<int> transparent_indices;
|
|
|
|
for ( int i = 0; i < (int)tex_buffers.size(); ++i ) {
|
|
buffer_data *tex_buf = &tex_buffers[i];
|
|
|
|
if ( tex_buf->n_verts < 1 ) {
|
|
continue;
|
|
}
|
|
|
|
const uint *indices = tex_buf->get_index();
|
|
|
|
texture_map *tmap = &pm->maps[tex_buf->texture];
|
|
|
|
// skip if this is already designated to be a transparent pass by the modeller
|
|
// if ( tmap->is_transparent ) {
|
|
// continue;
|
|
// }
|
|
|
|
int bitmap_handle = tmap->textures[TM_BASE_TYPE].GetTexture();
|
|
|
|
if ( bitmap_handle < 0 || !bm_has_alpha_channel(bitmap_handle) ) {
|
|
continue;
|
|
}
|
|
|
|
bitmap_lookup texture_lookup(bitmap_handle);
|
|
|
|
if ( !texture_lookup.valid() ) {
|
|
continue;
|
|
}
|
|
|
|
bool transparent_tri = false;
|
|
transparent_indices.clear();
|
|
|
|
for ( size_t j = 0; j < tex_buf->n_verts; ++j ) {
|
|
if ( j % NUM_VERTS_PER_TRI == 0 && j != 0 && transparent_tri ) {
|
|
transparent_tri = false;
|
|
|
|
// we have a triangle and it's transparent.
|
|
// shove index into the transparency buffer
|
|
transparent_indices.push_back(current_tri[0]);
|
|
transparent_indices.push_back(current_tri[1]);
|
|
transparent_indices.push_back(current_tri[2]);
|
|
}
|
|
|
|
const uint index = indices[j];
|
|
|
|
// need the uv coords of the vert at this index
|
|
float u = model_list->vert[index].texture_position.u;
|
|
float v = model_list->vert[index].texture_position.v;
|
|
|
|
if ( texture_lookup.get_channel_alpha(u, v) < 0.95f) {
|
|
transparent_tri = true;
|
|
}
|
|
|
|
current_tri[j % NUM_VERTS_PER_TRI] = index;
|
|
}
|
|
|
|
if ( transparent_indices.empty() ) {
|
|
continue;
|
|
}
|
|
|
|
pm->flags |= PM_FLAG_TRANS_BUFFER;
|
|
trans_buffer->flags |= VB_FLAG_TRANS;
|
|
|
|
trans_buffer->tex_buf.push_back ( buffer_data ( transparent_indices.size() ) );
|
|
|
|
buffer_data &new_buff = trans_buffer->tex_buf.back();
|
|
new_buff.texture = tex_buf->texture;
|
|
|
|
if ( transparent_indices.size() > USHRT_MAX ) {
|
|
new_buff.flags |= VB_FLAG_LARGE_INDEX;
|
|
}
|
|
|
|
for ( int j = 0; j < (int)transparent_indices.size(); ++j ) {
|
|
new_buff.assign(j, transparent_indices[j]);
|
|
}
|
|
}
|
|
|
|
if ( trans_buffer->flags & VB_FLAG_TRANS ) {
|
|
model_interp_config_buffer(&pm->vert_source, trans_buffer, true);
|
|
}
|
|
}
|
|
|
|
void model_interp_process_shield_mesh(polymodel * pm)
|
|
{
|
|
SCP_vector<vec3d> buffer;
|
|
|
|
if ( pm->shield.nverts <= 0 ) {
|
|
return;
|
|
}
|
|
|
|
int n_verts = 0;
|
|
|
|
for ( int i = 0; i < pm->shield.ntris; i++ ) {
|
|
shield_tri *tri = &pm->shield.tris[i];
|
|
|
|
vec3d a = pm->shield.verts[tri->verts[0]].pos;
|
|
vec3d b = pm->shield.verts[tri->verts[1]].pos;
|
|
vec3d c = pm->shield.verts[tri->verts[2]].pos;
|
|
|
|
// recalculate triangle normals to solve some issues regarding triangle collision
|
|
vec3d b_a;
|
|
vec3d c_a;
|
|
|
|
vm_vec_sub(&b_a, &b, &a);
|
|
vm_vec_sub(&c_a, &c, &a);
|
|
vm_vec_cross(&tri->norm, &b_a, &c_a);
|
|
vm_vec_normalize_safe(&tri->norm);
|
|
|
|
buffer.push_back(a);
|
|
buffer.push_back(tri->norm);
|
|
|
|
buffer.push_back(b);
|
|
buffer.push_back(tri->norm);
|
|
|
|
buffer.push_back(c);
|
|
buffer.push_back(tri->norm);
|
|
|
|
n_verts += 3;
|
|
}
|
|
|
|
if ( !buffer.empty() ) {
|
|
if (*pm->shield.buffer_id == gr_buffer_handle::invalid()) {
|
|
*pm->shield.buffer_id = gr_create_buffer(BufferType::Vertex, BufferUsageHint::Static);
|
|
pm->shield.buffer_n_verts = n_verts;
|
|
gr_update_buffer_data(*pm->shield.buffer_id, buffer.size() * sizeof(vec3d), buffer.data());
|
|
|
|
pm->shield.layout.add_vertex_component(vertex_format_data::POSITION3, sizeof(vec3d) * 2, 0);
|
|
pm->shield.layout.add_vertex_component(vertex_format_data::NORMAL, sizeof(vec3d) * 2, sizeof(vec3d));
|
|
}
|
|
} else {
|
|
*pm->shield.buffer_id = gr_buffer_handle::invalid();
|
|
}
|
|
}
|
|
|
|
// returns 1 if the thruster should be drawn
|
|
// 0 if it shouldn't
|
|
int model_should_render_engine_glow(int objnum, int bank_obj)
|
|
{
|
|
if ((bank_obj <= -1) || (objnum <= -1))
|
|
return 1;
|
|
|
|
object *obj = &Objects[objnum];
|
|
|
|
if (obj->type == OBJ_SHIP) {
|
|
ship_subsys *ssp;
|
|
ship *shipp = &Ships[obj->instance];
|
|
ship_info *sip = &Ship_info[shipp->ship_info_index];
|
|
|
|
Assert( bank_obj < sip->n_subsystems );
|
|
|
|
char subname[MAX_NAME_LEN];
|
|
// shipp->subsystems isn't always valid here so don't use it
|
|
strcpy_s(subname, sip->subsystems[bank_obj].subobj_name);
|
|
|
|
ssp = GET_FIRST(&shipp->subsys_list);
|
|
while ( ssp != END_OF_LIST( &shipp->subsys_list ) ) {
|
|
if ( !strcmp(subname, ssp->system_info->subobj_name) ) {
|
|
// this subsystem has 0 or less hits, ie. it's destroyed
|
|
if ( ssp->current_hits <= 0 )
|
|
return 0;
|
|
|
|
// see if the subsystem is disrupted, in which case it should be inoperable
|
|
if ( ship_subsys_disrupted(ssp) )
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
ssp = GET_NEXT( ssp );
|
|
}
|
|
|
|
} else if (obj->type == OBJ_WEAPON) {
|
|
// for weapons, if needed in the future
|
|
}
|
|
|
|
// default to render glow
|
|
return 1;
|
|
}
|
|
|
|
// Goober5000
|
|
// uses same algorithms as in ship_do_thruster_frame
|
|
int model_interp_get_texture(const texture_info *tinfo, int elapsed_time)
|
|
{
|
|
int texture, frame, cur_time, num_frames;
|
|
float total_time;
|
|
|
|
// get texture
|
|
num_frames = tinfo->GetNumFrames();
|
|
texture = tinfo->GetTexture();
|
|
total_time = tinfo->GetTotalTime();
|
|
|
|
// maybe animate it
|
|
if (texture >= 0 && num_frames > 1)
|
|
{
|
|
// sanity check total_time first thing
|
|
Assert(total_time > 0.0f);
|
|
total_time *= MILLISECONDS_PER_SECOND;
|
|
|
|
cur_time = elapsed_time % fl2i(total_time);
|
|
|
|
// get animation frame
|
|
frame = fl2i((cur_time * num_frames) / total_time + 0.5f);
|
|
CLAMP(frame, 0, num_frames - 1);
|
|
|
|
// advance to the correct frame
|
|
texture += frame;
|
|
}
|
|
|
|
// done
|
|
return texture;
|
|
}
|
|
|
|
void model_mix_two_team_colors(team_color* dest, team_color* a, team_color* b, float mix_factor)
|
|
{
|
|
dest->base.r = a->base.r * (1.0f - mix_factor) + b->base.r * mix_factor;
|
|
dest->base.g = a->base.g * (1.0f - mix_factor) + b->base.g * mix_factor;
|
|
dest->base.b = a->base.b * (1.0f - mix_factor) + b->base.b * mix_factor;
|
|
|
|
dest->stripe.r = a->stripe.r * (1.0f - mix_factor) + b->stripe.r * mix_factor;
|
|
dest->stripe.g = a->stripe.g * (1.0f - mix_factor) + b->stripe.g * mix_factor;
|
|
dest->stripe.b = a->stripe.b * (1.0f - mix_factor) + b->stripe.b * mix_factor;
|
|
}
|
|
|
|
bool model_get_team_color( team_color *clr, const SCP_string &team, const SCP_string &secondaryteam, fix timestamp, int fadetime )
|
|
{
|
|
Assert(clr != NULL);
|
|
|
|
if ( !stricmp(secondaryteam.c_str(), "none") ) {
|
|
if (Team_Colors.find(team) != Team_Colors.end()) {
|
|
*clr = Team_Colors[team];
|
|
return true;
|
|
} else
|
|
return false;
|
|
} else {
|
|
if ( Team_Colors.find(secondaryteam) != Team_Colors.end()) {
|
|
team_color temp_color;
|
|
team_color start;
|
|
|
|
if (Team_Colors.find(team) != Team_Colors.end()) {
|
|
start = Team_Colors[team];
|
|
} else {
|
|
start.base.r = 0.0f;
|
|
start.base.g = 0.0f;
|
|
start.base.b = 0.0f;
|
|
|
|
start.stripe.r = 0.0f;
|
|
start.stripe.g = 0.0f;
|
|
start.stripe.b = 0.0f;
|
|
}
|
|
|
|
team_color end = Team_Colors[secondaryteam];
|
|
float time_remaining = 0.0f;
|
|
if (fadetime != 0) // avoid potential div-by-zero
|
|
time_remaining = (f2fl(Missiontime - timestamp) * 1000)/fadetime;
|
|
CLAMP(time_remaining, 0.0f, 1.0f);
|
|
model_mix_two_team_colors(&temp_color, &start, &end, time_remaining);
|
|
|
|
*clr = temp_color;
|
|
return true;
|
|
} else
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//********************-----CLASS: texture_info-----********************//
|
|
texture_info::texture_info()
|
|
{
|
|
clear();
|
|
}
|
|
texture_info::texture_info(int bm_handle)
|
|
{
|
|
if(!bm_is_valid(bm_handle))
|
|
{
|
|
clear();
|
|
return;
|
|
}
|
|
|
|
this->original_texture = bm_handle;
|
|
this->ResetTexture();
|
|
}
|
|
void texture_info::clear()
|
|
{
|
|
texture = original_texture = -1;
|
|
num_frames = 0;
|
|
total_time = 1.0f;
|
|
}
|
|
int texture_info::GetNumFrames() const
|
|
{
|
|
return num_frames;
|
|
}
|
|
int texture_info::GetOriginalTexture() const
|
|
{
|
|
return original_texture;
|
|
}
|
|
int texture_info::GetTexture() const
|
|
{
|
|
return texture;
|
|
}
|
|
float texture_info::GetTotalTime() const
|
|
{
|
|
return total_time;
|
|
}
|
|
int texture_info::LoadTexture(const char *filename, const char *dbg_name)
|
|
{
|
|
if (strlen(filename) + 4 >= NAME_LENGTH) //Filenames are passed in without extension
|
|
{
|
|
mprintf(("Generated texture name %s is too long. Skipping...\n", filename));
|
|
return -1;
|
|
}
|
|
this->original_texture = bm_load_either(filename, NULL, NULL, NULL, true, CF_TYPE_MAPS);
|
|
if(this->original_texture < 0)
|
|
nprintf(("Maps", "For \"%s\" I couldn't find %s.ani\n", dbg_name, filename));
|
|
this->ResetTexture();
|
|
|
|
return texture;
|
|
}
|
|
void texture_info::PageIn()
|
|
{
|
|
bm_page_in_texture(texture);
|
|
}
|
|
|
|
void texture_info::PageOut(bool release)
|
|
{
|
|
if (texture >= 0) {
|
|
if (release) {
|
|
bm_release(texture);
|
|
texture = -1;
|
|
num_frames = 0;
|
|
total_time = 1.0f;
|
|
} else {
|
|
bm_unload(texture);
|
|
}
|
|
}
|
|
}
|
|
int texture_info::ResetTexture()
|
|
{
|
|
return this->SetTexture(original_texture);
|
|
}
|
|
int texture_info::SetTexture(int n_tex)
|
|
{
|
|
if(n_tex != -1 && !bm_is_valid(n_tex))
|
|
return texture;
|
|
|
|
//Set the new texture
|
|
texture = n_tex;
|
|
|
|
//If it is intentionally invalid, blank everything else
|
|
if(n_tex == -1)
|
|
{
|
|
num_frames = 0;
|
|
total_time = 1.0f;
|
|
}
|
|
else
|
|
{
|
|
//Determine the num_frames and total_time values.
|
|
int fps = 0;
|
|
this->num_frames = 1;
|
|
|
|
bm_get_info(texture, NULL, NULL, NULL, &this->num_frames, &fps);
|
|
|
|
this->total_time = (num_frames / ((fps > 0) ? (float)fps : 1.0f));
|
|
}
|
|
|
|
return texture;
|
|
}
|
|
|
|
//********************-----CLASS: texture_map-----********************//
|
|
int texture_map::FindTexture(int bm_handle)
|
|
{
|
|
if(!bm_is_valid(bm_handle))
|
|
return -1;
|
|
|
|
for(int i = 0; i < TM_NUM_TYPES; i++)
|
|
{
|
|
if (this->textures[i].GetTexture() == bm_handle)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
int texture_map::FindTexture(const char* fname)
|
|
{
|
|
if(fname == NULL || !strlen(fname))
|
|
return -1;
|
|
|
|
char buf[NAME_LENGTH];
|
|
for(int i = 0; i < TM_NUM_TYPES; i++)
|
|
{
|
|
bm_get_filename(this->textures[i].GetTexture(), buf);
|
|
if (!strextcmp(buf, fname)) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void texture_map::PageIn()
|
|
{
|
|
for(int i = 0; i < TM_NUM_TYPES; i++)
|
|
this->textures[i].PageIn();
|
|
}
|
|
|
|
void texture_map::PageOut(bool release)
|
|
{
|
|
for(int i = 0; i < TM_NUM_TYPES; i++)
|
|
this->textures[i].PageOut(release);
|
|
}
|
|
|
|
void texture_map::Clear()
|
|
{
|
|
is_ambient = false;
|
|
is_transparent = false;
|
|
|
|
for(int i = 0; i < TM_NUM_TYPES; i++)
|
|
this->textures[i].clear();
|
|
}
|
|
|
|
void texture_map::ResetToOriginal()
|
|
{
|
|
for(int i = 0; i < TM_NUM_TYPES; i++)
|
|
this->textures[i].ResetTexture();
|
|
}
|
|
|
|
bsp_polygon_data::bsp_polygon_data(ubyte* _bsp_data, int _bsp_data_size)
|
|
{
|
|
Polygon_vertices.clear();
|
|
Polygons.clear();
|
|
|
|
for (int i = 0; i < MAX_MODEL_TEXTURES; ++i) {
|
|
Num_verts[i] = 0;
|
|
Num_polies[i] = 0;
|
|
}
|
|
|
|
Num_flat_verts = 0;
|
|
Num_flat_polies = 0;
|
|
|
|
bsp_data_size = _bsp_data_size;
|
|
|
|
Macro_ubyte_bounds = _bsp_data + _bsp_data_size;
|
|
process_bsp(0, _bsp_data);
|
|
Macro_ubyte_bounds = nullptr;
|
|
|
|
}
|
|
|
|
void bsp_polygon_data::process_bsp(int offset, ubyte* bsp_data)
|
|
{
|
|
int id = w(bsp_data + offset);
|
|
int size = w(bsp_data + offset + 4);
|
|
|
|
bool end = id == OP_EOF;
|
|
while (!end) {
|
|
switch (id)
|
|
{
|
|
case OP_DEFPOINTS:
|
|
process_defpoints(offset, bsp_data);
|
|
break;
|
|
|
|
case OP_SORTNORM:
|
|
process_sortnorm(offset, bsp_data);
|
|
break;
|
|
|
|
case OP_SORTNORM2:
|
|
process_sortnorm2(offset, bsp_data);
|
|
end = true; // should not continue after this chunk
|
|
break;
|
|
|
|
case OP_FLATPOLY:
|
|
process_flat(offset, bsp_data);
|
|
break;
|
|
|
|
case OP_TMAPPOLY:
|
|
process_tmap(offset, bsp_data);
|
|
break;
|
|
|
|
case OP_BOUNDBOX:
|
|
break;
|
|
|
|
case OP_TMAP2POLY:
|
|
process_tmap2(offset, bsp_data);
|
|
end = true; // should not continue after this chunk
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
if (end) break;
|
|
|
|
offset += size;
|
|
id = w(bsp_data + offset);
|
|
size = w(bsp_data + offset + 4);
|
|
|
|
if (size < 1 || id == OP_EOF)
|
|
end = true;
|
|
}
|
|
}
|
|
|
|
void bsp_polygon_data::process_defpoints(int off, ubyte* bsp_data)
|
|
{
|
|
uint i, n;
|
|
uint nverts = uw(off + bsp_data + 8);
|
|
uint offset = uw(off + bsp_data + 16);
|
|
|
|
ubyte *normcount = off + bsp_data + 20;
|
|
vec3d *src = vp(off + bsp_data + offset);
|
|
|
|
// Get pointer to lights
|
|
Lights = off + bsp_data + 20 + nverts;
|
|
|
|
#ifndef NDEBUG
|
|
modelstats_num_verts += nverts;
|
|
#endif
|
|
|
|
Vertex_list.clear();
|
|
Normal_list.clear();
|
|
|
|
for (n = 0; n < nverts; n++) {
|
|
Vertex_list.push_back(*src);
|
|
src++; // move to normal
|
|
|
|
for (i = 0; i < normcount[n]; i++) {
|
|
Normal_list.push_back(*src);
|
|
src++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Parses a SORTNORM2 by recursively parsing into the two pointers it contains.
|
|
*
|
|
* @param offset The byte offset to the current SORT2NORM chunk within bsp_data.
|
|
* @param bsp_data The byte buffer containing the BSP information for the current model.
|
|
*/
|
|
void bsp_polygon_data::process_sortnorm2(int offset, ubyte* bsp_data)
|
|
{
|
|
int frontlist, backlist;
|
|
|
|
frontlist = w(bsp_data + offset + 8);
|
|
backlist = w(bsp_data + offset + 12);
|
|
|
|
if (backlist) process_bsp(offset + backlist, bsp_data);
|
|
if (frontlist) process_bsp(offset + frontlist, bsp_data);
|
|
}
|
|
|
|
void bsp_polygon_data::process_sortnorm(int offset, ubyte* bsp_data)
|
|
{
|
|
int frontlist, backlist, prelist, postlist, onlist;
|
|
|
|
frontlist = w(bsp_data + offset + 36);
|
|
backlist = w(bsp_data + offset + 40);
|
|
prelist = w(bsp_data + offset + 44);
|
|
postlist = w(bsp_data + offset + 48);
|
|
onlist = w(bsp_data + offset + 52);
|
|
|
|
if (prelist) process_bsp(offset + prelist, bsp_data);
|
|
if (backlist) process_bsp(offset + backlist, bsp_data);
|
|
if (onlist) process_bsp(offset + onlist, bsp_data);
|
|
if (frontlist) process_bsp(offset + frontlist, bsp_data);
|
|
if (postlist) process_bsp(offset + postlist, bsp_data);
|
|
}
|
|
|
|
void bsp_polygon_data::process_tmap(int offset, ubyte* bsp_data)
|
|
{
|
|
int pof_tex = w(bsp_data + offset + TMAP_TEXNUM);
|
|
uint n_vert = uw(bsp_data + offset + TMAP_NVERTS);
|
|
ubyte* p;
|
|
|
|
if ( n_vert < 3 ) {
|
|
// don't parse degenerate polygons
|
|
return;
|
|
}
|
|
|
|
p = &bsp_data[offset + TMAP_NORMAL];
|
|
|
|
auto tverts = reinterpret_cast<model_tmap_vert_old*>(&bsp_data[offset + TMAP_VERTS]);
|
|
|
|
// Copy the verts manually since they aren't aligned with the struct
|
|
//unpack_tmap_verts(&bsp_data[offset + 44], tverts, n_vert);
|
|
|
|
int problem_count = 0;
|
|
|
|
// make a polygon
|
|
bsp_polygon polygon;
|
|
|
|
polygon.Start_index = (uint)Polygon_vertices.size();
|
|
polygon.Num_verts = n_vert;
|
|
polygon.texture = pof_tex;
|
|
|
|
// this polygon will be broken up into a triangle fan. first three verts make up the first triangle
|
|
// additional verts are made into new tris
|
|
Num_polies[pof_tex]++;
|
|
Num_verts[pof_tex] += n_vert;
|
|
|
|
// stuff data making up the vertices of this polygon
|
|
for ( uint i = 0; i < n_vert; ++i ) {
|
|
bsp_vertex vert;
|
|
|
|
vert.position = Vertex_list[tverts[i].vertnum];
|
|
|
|
vert.tex_coord.u = tverts[i].u;
|
|
vert.tex_coord.v = tverts[i].v;
|
|
|
|
vert.normal = Normal_list[tverts[i].normnum];
|
|
|
|
// see if this normal is okay
|
|
if (IS_VEC_NULL(&vert.normal))
|
|
vert.normal = *vp(p);
|
|
|
|
problem_count += check_values(&vert.normal);
|
|
vm_vec_normalize_safe(&vert.normal);
|
|
|
|
Polygon_vertices.push_back(vert);
|
|
}
|
|
|
|
Polygons.push_back(polygon);
|
|
|
|
Parse_normal_problem_count += problem_count;
|
|
}
|
|
|
|
/**
|
|
* @brief Converts a TMAP2POLY chunk into a list of BSP_polygon.
|
|
*
|
|
* @param offset The byte offset into bsp_data.
|
|
* @param[in] The buffer containing the chunk data.
|
|
*/
|
|
void bsp_polygon_data::process_tmap2(int offset, ubyte* bsp_data)
|
|
{
|
|
int pof_tex = w(bsp_data + offset + TMAP2_TEXNUM);
|
|
uint n_vert = uw(bsp_data + offset + TMAP2_NVERTS);
|
|
model_tmap_vert* tverts;
|
|
int problem_count = 0;
|
|
bsp_polygon polygon;
|
|
|
|
if (n_vert < 3) {
|
|
Error(LOCATION, "Model contains TMAP2 chunk with less than 3 vertices!");
|
|
return;
|
|
}
|
|
|
|
tverts = reinterpret_cast<model_tmap_vert*>(&bsp_data[offset + TMAP2_VERTS]);
|
|
|
|
// make a polygon
|
|
polygon.Start_index = (uint)Polygon_vertices.size();
|
|
polygon.Num_verts = n_vert;
|
|
polygon.texture = pof_tex;
|
|
|
|
// this polygon will be broken up into a triangle fan. first three verts make up the first triangle
|
|
// additional verts are made into new tris
|
|
Num_polies[pof_tex]++;
|
|
Num_verts[pof_tex] += n_vert;
|
|
|
|
// stuff data making up the vertices of this polygon
|
|
for (uint i = 0; i < n_vert; ++i) {
|
|
bsp_vertex vert;
|
|
|
|
vert.position = Vertex_list[tverts[i].vertnum];
|
|
|
|
vert.tex_coord.u = tverts[i].u;
|
|
vert.tex_coord.v = tverts[i].v;
|
|
|
|
vert.normal = Normal_list[tverts[i].normnum];
|
|
|
|
// see if this normal is okay
|
|
if (IS_VEC_NULL(&vert.normal))
|
|
vert.normal = *vp(&bsp_data[offset + 32]);
|
|
|
|
problem_count += check_values(&vert.normal);
|
|
vm_vec_normalize_safe(&vert.normal);
|
|
|
|
Polygon_vertices.push_back(vert);
|
|
}
|
|
|
|
Polygons.push_back(polygon);
|
|
|
|
Parse_normal_problem_count += problem_count;
|
|
}
|
|
|
|
void bsp_polygon_data::process_flat(int offset, ubyte* bsp_data)
|
|
{
|
|
int n_vert = w(bsp_data + offset + 36);
|
|
|
|
if (n_vert < 3) {
|
|
// don't parse degenerate polygons
|
|
return;
|
|
}
|
|
|
|
short * verts = (short *)(bsp_data + offset + 44);
|
|
|
|
ubyte r = *(bsp_data + offset + 40);
|
|
ubyte g = *(bsp_data + offset + 41);
|
|
ubyte b = *(bsp_data + offset + 42);
|
|
|
|
bsp_polygon polygon;
|
|
|
|
polygon.Start_index = (uint)Polygon_vertices.size();
|
|
polygon.Num_verts = n_vert;
|
|
polygon.texture = -1;
|
|
|
|
Num_flat_polies++;
|
|
Num_flat_verts += n_vert;
|
|
|
|
for (int i = 0; i < n_vert; i++) {
|
|
bsp_vertex vert;
|
|
|
|
int vertnum = verts[i * 2 + 0];
|
|
int norm = verts[i * 2 + 1];
|
|
|
|
vert.position = Vertex_list[vertnum];
|
|
vert.normal = Normal_list[norm];
|
|
|
|
vert.r = r;
|
|
vert.g = g;
|
|
vert.b = b;
|
|
vert.a = 255;
|
|
|
|
Polygon_vertices.push_back(vert);
|
|
}
|
|
|
|
Polygons.push_back(polygon);
|
|
}
|
|
|
|
int bsp_polygon_data::get_num_triangles(int texture)
|
|
{
|
|
if ( texture < 0 ) {
|
|
return MAX(Num_flat_verts - 2 * Num_flat_polies, 0);
|
|
}
|
|
|
|
return MAX(Num_verts[texture] - 2 * Num_polies[texture], 0);
|
|
}
|
|
|
|
int bsp_polygon_data::get_num_lines(int texture)
|
|
{
|
|
if (texture < 0) {
|
|
return Num_flat_verts;
|
|
}
|
|
|
|
return Num_verts[texture];
|
|
}
|
|
|
|
void bsp_polygon_data::generate_triangles(int texture, vertex *vert_ptr, vec3d* norm_ptr)
|
|
{
|
|
int num_verts = 0;
|
|
|
|
for ( uint i = 0; i < Polygons.size(); ++i ) {
|
|
if ( Polygons[i].texture != texture ) {
|
|
continue;
|
|
}
|
|
|
|
uint start_index = Polygons[i].Start_index;
|
|
uint end_index = Polygons[i].Start_index + Polygons[i].Num_verts;
|
|
|
|
for ( uint j = start_index + 1; j < end_index - 1; ++j ) {
|
|
// first vertex of this triangle. Always the first vertex of the polygon
|
|
vertex* vert = &vert_ptr[num_verts];
|
|
vert->world = Polygon_vertices[start_index].position;
|
|
vert->texture_position = Polygon_vertices[start_index].tex_coord;
|
|
|
|
vec3d* norm = &norm_ptr[num_verts];
|
|
*norm = Polygon_vertices[start_index].normal;
|
|
|
|
// second vertex of this triangle.
|
|
vert = &vert_ptr[num_verts + 1];
|
|
vert->world = Polygon_vertices[j].position;
|
|
vert->texture_position = Polygon_vertices[j].tex_coord;
|
|
|
|
norm = &norm_ptr[num_verts + 1];
|
|
*norm = Polygon_vertices[j].normal;
|
|
|
|
// third vertex of this triangle.
|
|
vert = &vert_ptr[num_verts + 2];
|
|
vert->world = Polygon_vertices[j+1].position;
|
|
vert->texture_position = Polygon_vertices[j+1].tex_coord;
|
|
|
|
norm = &norm_ptr[num_verts + 2];
|
|
*norm = Polygon_vertices[j+1].normal;
|
|
|
|
num_verts += 3;
|
|
}
|
|
}
|
|
}
|
|
|
|
void bsp_polygon_data::generate_lines(int texture, vertex *vert_ptr)
|
|
{
|
|
int num_verts = 0;
|
|
|
|
for (uint i = 0; i < Polygons.size(); ++i) {
|
|
if (Polygons[i].texture != texture) {
|
|
continue;
|
|
}
|
|
|
|
uint start_index = Polygons[i].Start_index;
|
|
uint end_index = Polygons[i].Start_index + Polygons[i].Num_verts;
|
|
|
|
for (uint j = start_index; j < end_index; ++j) {
|
|
// first vertex of this triangle. Always the first vertex of the polygon
|
|
vertex* vert = &vert_ptr[num_verts];
|
|
vert->world = Polygon_vertices[j].position;
|
|
vert->r = Polygon_vertices[j].r;
|
|
vert->g = Polygon_vertices[j].g;
|
|
vert->b = Polygon_vertices[j].b;
|
|
vert->a = Polygon_vertices[j].a;
|
|
|
|
if ( j == end_index - 1 ) {
|
|
vert = &vert_ptr[num_verts + 1];
|
|
vert->world = Polygon_vertices[start_index].position;
|
|
vert->r = Polygon_vertices[start_index].r;
|
|
vert->g = Polygon_vertices[start_index].g;
|
|
vert->b = Polygon_vertices[start_index].b;
|
|
vert->a = Polygon_vertices[start_index].a;
|
|
} else {
|
|
vert = &vert_ptr[num_verts + 1];
|
|
vert->world = Polygon_vertices[j + 1].position;
|
|
vert->r = Polygon_vertices[j + 1].r;
|
|
vert->g = Polygon_vertices[j + 1].g;
|
|
vert->b = Polygon_vertices[j + 1].b;
|
|
vert->a = Polygon_vertices[j + 1].a;
|
|
}
|
|
|
|
num_verts += 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
SCP_set<int> bsp_polygon_data::get_textures_used() const {
|
|
SCP_set<int> textures;
|
|
for (const auto& poly : Polygons)
|
|
textures.emplace(poly.texture);
|
|
return textures;
|
|
}
|
|
|
|
void bsp_polygon_data::replace_textures_used(const SCP_map<int, int>& replacementMap) {
|
|
for (auto& poly : Polygons) {
|
|
auto it = replacementMap.find(poly.texture);
|
|
if (it != replacementMap.end()) {
|
|
poly.texture = it->second;
|
|
Num_verts[it->first] -= poly.Num_verts;
|
|
Num_verts[it->second] += poly.Num_verts;
|
|
--Num_polies[it->first];
|
|
++Num_polies[it->second];
|
|
}
|
|
}
|
|
}
|
|
|
|
SCP_set<int> model_get_textures_used(const polymodel* pm, int submodel) {
|
|
return bsp_polygon_data{ pm->submodel[submodel].bsp_data.get(), pm->submodel[submodel].bsp_data_size }.get_textures_used();
|
|
}
|