mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-09 17:52:08 -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]>
3300 lines
98 KiB
C++
3300 lines
98 KiB
C++
/*
|
|
* Copyright (C) Freespace Open 2013. All rights reserved.
|
|
*
|
|
* All source code herein is the property of Freespace Open. You may not sell
|
|
* or otherwise commercially exploit the source or things you created based on the
|
|
* source.
|
|
*
|
|
*/
|
|
|
|
#include "model/modelrender.h"
|
|
|
|
#include "asteroid/asteroid.h"
|
|
#include "cmdline/cmdline.h"
|
|
#include "decals/decals.h"
|
|
#include "gamesequence/gamesequence.h"
|
|
#include "globalincs/systemvars.h"
|
|
#include "math/floating.h"
|
|
#include "graphics/light.h"
|
|
#include "graphics/matrix.h"
|
|
#include "graphics/shadows.h"
|
|
#include "graphics/tmapper.h"
|
|
#include "graphics/uniforms.h"
|
|
#include "io/timer.h"
|
|
#include "jumpnode/jumpnode.h"
|
|
#include "lighting/lighting.h"
|
|
#include "lighting/lighting_profiles.h"
|
|
#include "math/staticrand.h"
|
|
#include "missionui/missionscreencommon.h"
|
|
#include "mod_table/mod_table.h"
|
|
#include "nebula/neb.h"
|
|
#include "particle/particle.h"
|
|
#include "prop/prop.h"
|
|
#include "parse/encrypt.h"
|
|
#include "render/3dinternal.h"
|
|
#include "render/batching.h"
|
|
#include "ship/ship.h"
|
|
#include "ship/shipfx.h"
|
|
#include "starfield/starfield.h"
|
|
#include "tracing/tracing.h"
|
|
#include "weapon/weapon.h"
|
|
|
|
#include <algorithm>
|
|
|
|
extern int Model_texturing;
|
|
extern int Model_polys;
|
|
extern int tiling;
|
|
extern float model_radius;
|
|
|
|
extern bool Scene_framebuffer_in_frame;
|
|
color Wireframe_color;
|
|
|
|
int Lab_object_detail_level = -1; // Used to display the detail level in the lab
|
|
|
|
extern void interp_generate_arc_segment(SCP_vector<vec3d> &arc_segment_points, const vec3d *v1, const vec3d *v2, ubyte depth_limit, ubyte depth);
|
|
|
|
int model_render_determine_elapsed_time(int objnum, uint64_t flags);
|
|
|
|
SCP_unordered_map<cached_ui_render_instance_key, cached_ui_render_instance_entry, cached_ui_render_instance_key_hash> Cached_ui_render_instance_cache;
|
|
int Ui_render_instance_cache_last_processed_framecount = -1;
|
|
constexpr int UI_RENDER_INSTANCE_CACHE_UNUSED_FRAMES_GRACE = 5;
|
|
|
|
size_t model_hash_subsystem_name_list_for_cache(const SCP_vector<SCP_string>& subsystem_names)
|
|
{
|
|
if (subsystem_names.empty()) {
|
|
return 0;
|
|
}
|
|
|
|
SCP_vector<SCP_string> normalized_names;
|
|
normalized_names.reserve(subsystem_names.size());
|
|
|
|
for (const auto& name : subsystem_names) {
|
|
auto normalized = name;
|
|
SCP_tolower(normalized);
|
|
normalized_names.push_back(std::move(normalized));
|
|
}
|
|
|
|
std::sort(normalized_names.begin(), normalized_names.end());
|
|
|
|
size_t seed = 0;
|
|
|
|
boost::hash_combine(seed, static_cast<uint32_t>(normalized_names.size()));
|
|
for (const auto& name : normalized_names) {
|
|
boost::hash_combine(seed, hash_fnv1a(name));
|
|
}
|
|
|
|
return seed;
|
|
}
|
|
|
|
// Returns TriStateBool::TRUE_ if a new instance was created, TriStateBool::FALSE_ if an existing instance was returned,
|
|
// or TriStateBool::UNKNOWN_ if there was an error (and model_instance_out will be set to -1 in this case)
|
|
TriStateBool model_get_cached_ui_render_instance(int model_num, int* model_instance_out, size_t instance_data_hash)
|
|
{
|
|
Assertion(model_instance_out != nullptr, "model_instance_out must not be null!");
|
|
if (model_instance_out == nullptr) {
|
|
return TriStateBool::UNKNOWN_;
|
|
}
|
|
|
|
cached_ui_render_instance_key key;
|
|
key.model_num = model_num;
|
|
key.state_instance_id = gameseq_get_state_instance_id();
|
|
key.instance_data_hash = instance_data_hash;
|
|
|
|
auto& entry = Cached_ui_render_instance_cache[key];
|
|
|
|
auto created_new = false;
|
|
|
|
if (entry.model_instance < 0) {
|
|
entry.model_instance = model_create_instance(model_objnum_special::OBJNUM_NONE, model_num);
|
|
if (entry.model_instance < 0) {
|
|
Warning(LOCATION, "Failed to create cached UI render model instance for model id %d.", model_num);
|
|
Cached_ui_render_instance_cache.erase(key);
|
|
*model_instance_out = -1;
|
|
return TriStateBool::UNKNOWN_;
|
|
}
|
|
created_new = true;
|
|
}
|
|
|
|
entry.last_used_framecount = Framecount;
|
|
*model_instance_out = entry.model_instance;
|
|
return created_new ? TriStateBool::TRUE_ : TriStateBool::FALSE_;
|
|
}
|
|
|
|
void model_process_cached_ui_render_instances()
|
|
{
|
|
if (Ui_render_instance_cache_last_processed_framecount == Framecount) {
|
|
return;
|
|
}
|
|
|
|
Ui_render_instance_cache_last_processed_framecount = Framecount;
|
|
|
|
for (auto it = Cached_ui_render_instance_cache.begin(); it != Cached_ui_render_instance_cache.end();) {
|
|
// This should never trigger but if it does it means something has gone very wrong with the cache management logic
|
|
Assertion(it->second.model_instance >= 0, "Corrupted UI render instance cache entry found");
|
|
|
|
if (it->second.model_instance < 0 || it->second.last_used_framecount < 0 ||
|
|
(Framecount - it->second.last_used_framecount) > UI_RENDER_INSTANCE_CACHE_UNUSED_FRAMES_GRACE) {
|
|
if (it->second.model_instance >= 0) {
|
|
model_delete_instance(it->second.model_instance);
|
|
}
|
|
it = Cached_ui_render_instance_cache.erase(it);
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
}
|
|
|
|
void model_clear_cached_ui_render_instances()
|
|
{
|
|
for (auto& instance : Cached_ui_render_instance_cache) {
|
|
if (instance.second.model_instance >= 0) {
|
|
model_delete_instance(instance.second.model_instance);
|
|
}
|
|
}
|
|
|
|
Cached_ui_render_instance_cache.clear();
|
|
Ui_render_instance_cache_last_processed_framecount = -1;
|
|
}
|
|
|
|
model_render_params::model_render_params() :
|
|
Model_flags(MR_NORMAL),
|
|
Debug_flags(0),
|
|
Objnum(-1),
|
|
Detail_level_locked(-1),
|
|
Depth_scale(1500.0f),
|
|
Attached_model_draw_distance(-1.0f),
|
|
Warp_bitmap(-1),
|
|
Warp_alpha(-1.0f),
|
|
Xparent_alpha(1.0f),
|
|
Forced_bitmap(-1),
|
|
Insignia_bitmap(-1),
|
|
Replacement_textures(nullptr),
|
|
Team_color_set(false),
|
|
Clip_plane_set(false),
|
|
Animated_effect(-1),
|
|
Animated_timer(0.0f),
|
|
Thruster_info(),
|
|
Use_alpha_mult(false),
|
|
Alpha_mult(1.0f)
|
|
{
|
|
Warp_scale.xyz.x = 1.0f;
|
|
Warp_scale.xyz.y = 1.0f;
|
|
Warp_scale.xyz.z = 1.0f;
|
|
|
|
Clip_normal = vmd_zero_vector;
|
|
Clip_pos = vmd_zero_vector;
|
|
|
|
if ( !Model_texturing )
|
|
Model_flags |= MR_NO_TEXTURING;
|
|
|
|
if ( !Model_polys ) {
|
|
Model_flags |= MR_NO_POLYS;
|
|
}
|
|
|
|
gr_init_color(&Color, 0, 0, 0);
|
|
}
|
|
|
|
uint64_t model_render_params::get_model_flags() const
|
|
{
|
|
return Model_flags;
|
|
}
|
|
|
|
uint model_render_params::get_debug_flags() const
|
|
{
|
|
return Debug_flags;
|
|
}
|
|
|
|
int model_render_params::get_object_number() const
|
|
{
|
|
return Objnum;
|
|
}
|
|
|
|
int model_render_params::get_detail_level_lock() const
|
|
{
|
|
return Detail_level_locked;
|
|
}
|
|
|
|
float model_render_params::get_depth_scale() const
|
|
{
|
|
return Depth_scale;
|
|
}
|
|
|
|
float model_render_params::get_attached_model_draw_distance() const
|
|
{
|
|
return Attached_model_draw_distance;
|
|
}
|
|
|
|
int model_render_params::get_warp_bitmap() const
|
|
{
|
|
return Warp_bitmap;
|
|
}
|
|
|
|
float model_render_params::get_warp_alpha() const
|
|
{
|
|
return Warp_alpha;
|
|
}
|
|
|
|
const vec3d& model_render_params::get_warp_scale() const
|
|
{
|
|
return Warp_scale;
|
|
}
|
|
|
|
const color& model_render_params::get_color() const
|
|
{
|
|
return Color;
|
|
}
|
|
|
|
float model_render_params::get_alpha() const
|
|
{
|
|
return Xparent_alpha;
|
|
}
|
|
|
|
int model_render_params::get_forced_bitmap() const
|
|
{
|
|
return Forced_bitmap;
|
|
}
|
|
|
|
int model_render_params::get_insignia_bitmap() const
|
|
{
|
|
return Insignia_bitmap;
|
|
}
|
|
|
|
std::shared_ptr<const model_texture_replace> model_render_params::get_replacement_textures() const
|
|
{
|
|
return Replacement_textures;
|
|
}
|
|
|
|
const team_color& model_render_params::get_team_color() const
|
|
{
|
|
return Current_team_color;
|
|
}
|
|
|
|
const vec3d& model_render_params::get_clip_plane_pos() const
|
|
{
|
|
return Clip_pos;
|
|
}
|
|
|
|
const vec3d& model_render_params::get_clip_plane_normal() const
|
|
{
|
|
return Clip_normal;
|
|
}
|
|
|
|
int model_render_params::get_animated_effect_num() const
|
|
{
|
|
return Animated_effect;
|
|
}
|
|
|
|
float model_render_params::get_animated_effect_timer() const
|
|
{
|
|
return Animated_timer;
|
|
}
|
|
|
|
void model_render_params::set_animated_effect(int effect_num, float timer)
|
|
{
|
|
Animated_effect = effect_num;
|
|
Animated_timer = timer;
|
|
}
|
|
|
|
void model_render_params::set_clip_plane(const vec3d &pos, const vec3d &normal)
|
|
{
|
|
Clip_plane_set = true;
|
|
|
|
Clip_normal = normal;
|
|
Clip_pos = pos;
|
|
}
|
|
|
|
bool model_render_params::is_clip_plane_set() const
|
|
{
|
|
return Clip_plane_set;
|
|
}
|
|
|
|
void model_render_params::set_team_color(const team_color &clr)
|
|
{
|
|
Team_color_set = true;
|
|
|
|
Current_team_color = clr;
|
|
}
|
|
|
|
void model_render_params::set_team_color(const SCP_string &team, const SCP_string &secondaryteam, fix timestamp, int fadetime)
|
|
{
|
|
Team_color_set = model_get_team_color(&Current_team_color, team, secondaryteam, timestamp, fadetime);
|
|
}
|
|
|
|
bool model_render_params::is_team_color_set() const
|
|
{
|
|
return Team_color_set;
|
|
}
|
|
|
|
void model_render_params::set_replacement_textures(std::shared_ptr<const model_texture_replace> textures)
|
|
{
|
|
Replacement_textures = std::move(textures);
|
|
}
|
|
|
|
void model_render_params::set_replacement_textures(int modelnum, const SCP_vector<texture_replace>& replacement_textures)
|
|
{
|
|
auto textures = std::make_shared<model_texture_replace>();
|
|
|
|
polymodel* pm = model_get(modelnum);
|
|
|
|
for (const auto& tr : replacement_textures)
|
|
{
|
|
for (int i = 0; i < pm->n_textures; ++i)
|
|
{
|
|
texture_map *tmap = &pm->maps[i];
|
|
|
|
int tnum = tmap->FindTexture(tr.old_texture);
|
|
if (tnum > -1)
|
|
(*textures)[i * TM_NUM_TYPES + tnum] = bm_load(tr.new_texture);
|
|
}
|
|
}
|
|
|
|
Replacement_textures = std::move(textures);
|
|
}
|
|
|
|
void model_render_params::set_insignia_bitmap(int bitmap)
|
|
{
|
|
Insignia_bitmap = bitmap;
|
|
}
|
|
|
|
void model_render_params::set_forced_bitmap(int bitmap)
|
|
{
|
|
Forced_bitmap = bitmap;
|
|
}
|
|
|
|
void model_render_params::set_alpha(float alpha)
|
|
{
|
|
Xparent_alpha = alpha;
|
|
}
|
|
|
|
void model_render_params::set_color(const color &clr)
|
|
{
|
|
Color = clr;
|
|
}
|
|
|
|
void model_render_params::set_color(int r, int g, int b)
|
|
{
|
|
gr_init_color( &Color, r, g, b );
|
|
}
|
|
|
|
void model_render_params::set_warp_params(int bitmap, float alpha, const vec3d &scale)
|
|
{
|
|
Warp_bitmap = bitmap;
|
|
Warp_alpha = alpha;
|
|
Warp_scale = scale;
|
|
}
|
|
|
|
void model_render_params::set_depth_scale(float scale)
|
|
{
|
|
Depth_scale = scale;
|
|
}
|
|
|
|
void model_render_params::set_attached_model_draw_distance(float distance)
|
|
{
|
|
Attached_model_draw_distance = distance;
|
|
}
|
|
|
|
void model_render_params::set_debug_flags(uint flags)
|
|
{
|
|
Debug_flags = flags;
|
|
}
|
|
|
|
void model_render_params::set_object_number(int num)
|
|
{
|
|
Objnum = num;
|
|
}
|
|
|
|
void model_render_params::set_flags(uint64_t flags)
|
|
{
|
|
Model_flags = flags;
|
|
}
|
|
|
|
void model_render_params::set_detail_level_lock(int detail_level_lock)
|
|
{
|
|
Detail_level_locked = detail_level_lock;
|
|
}
|
|
|
|
void model_render_params::set_thruster_info(const mst_info &info)
|
|
{
|
|
Thruster_info = info;
|
|
|
|
CLAMP(Thruster_info.length.xyz.z, 0.1f, 1.0f);
|
|
}
|
|
|
|
const mst_info& model_render_params::get_thruster_info() const
|
|
{
|
|
return Thruster_info;
|
|
}
|
|
|
|
void model_render_params::set_outline_thickness(float thickness) {
|
|
Outline_thickness = thickness;
|
|
}
|
|
float model_render_params::get_outline_thickness() const {
|
|
return Outline_thickness;
|
|
}
|
|
bool model_render_params::uses_thick_outlines() const {
|
|
return Outline_thickness > 0.0f;
|
|
}
|
|
|
|
void model_render_params::set_alpha_mult(float alpha) {
|
|
Alpha_mult = alpha;
|
|
Use_alpha_mult = true;
|
|
}
|
|
|
|
bool model_render_params::is_alpha_mult_set() const
|
|
{
|
|
return Use_alpha_mult;
|
|
}
|
|
|
|
float model_render_params::get_alpha_mult() const
|
|
{
|
|
return Alpha_mult;
|
|
}
|
|
|
|
void model_batch_buffer::reset()
|
|
{
|
|
Submodel_matrices.clear();
|
|
|
|
Current_offset = 0;
|
|
}
|
|
|
|
void model_batch_buffer::set_num_models(int n_models)
|
|
{
|
|
matrix4 init_mat;
|
|
|
|
vm_matrix4_set_identity(&init_mat);
|
|
|
|
Current_offset = Submodel_matrices.size();
|
|
|
|
for ( int i = 0; i < n_models; ++i ) {
|
|
Submodel_matrices.push_back(init_mat);
|
|
}
|
|
}
|
|
|
|
void model_batch_buffer::set_model_transform(const matrix4 &transform, int model_id)
|
|
{
|
|
Submodel_matrices[Current_offset + model_id] = transform;
|
|
}
|
|
|
|
void model_batch_buffer::add_matrix(const matrix4 &mat)
|
|
{
|
|
Submodel_matrices.push_back(mat);
|
|
}
|
|
|
|
size_t model_batch_buffer::get_buffer_offset() const
|
|
{
|
|
return Current_offset;
|
|
}
|
|
|
|
void model_batch_buffer::allocate_memory()
|
|
{
|
|
auto size = Submodel_matrices.size() * sizeof(matrix4);
|
|
|
|
if ( Mem_alloc == NULL || Mem_alloc_size < size ) {
|
|
if ( Mem_alloc != NULL ) {
|
|
vm_free(Mem_alloc);
|
|
}
|
|
|
|
Mem_alloc = vm_malloc(size);
|
|
}
|
|
|
|
Mem_alloc_size = size;
|
|
memcpy(Mem_alloc, &Submodel_matrices[0], size);
|
|
}
|
|
|
|
void model_batch_buffer::submit_buffer_data()
|
|
{
|
|
if ( Submodel_matrices.empty() ) {
|
|
return;
|
|
}
|
|
|
|
allocate_memory();
|
|
|
|
gr_update_transform_buffer(Mem_alloc, Mem_alloc_size);
|
|
}
|
|
|
|
model_draw_list::model_draw_list()
|
|
{
|
|
reset();
|
|
}
|
|
|
|
void model_draw_list::add_arc(const vec3d *v1, const vec3d *v2, const SCP_vector<vec3d> *persistent_arc_points, const color *primary, const color *secondary, float arc_width, ubyte segment_depth)
|
|
{
|
|
arc_effect new_arc;
|
|
|
|
new_arc.transform = get_transform();
|
|
new_arc.v1 = *v1;
|
|
new_arc.v2 = *v2;
|
|
new_arc.primary = *primary;
|
|
new_arc.secondary = *secondary;
|
|
new_arc.width = arc_width;
|
|
new_arc.segment_depth = segment_depth;
|
|
new_arc.persistent_arc_points = persistent_arc_points;
|
|
|
|
Arcs.push_back(new_arc);
|
|
}
|
|
|
|
void model_draw_list::set_light_filter(const vec3d *pos, float rad)
|
|
{
|
|
_lights.setLightFilter(pos, rad);
|
|
|
|
Current_lights_set = _lights.bufferLights();
|
|
}
|
|
|
|
void model_draw_list::add_buffer_draw(const model_material *render_material, const indexed_vertex_source *vert_src, const vertex_buffer *buffer, size_t texi, uint tmap_flags)
|
|
{
|
|
queued_buffer_draw draw_data;
|
|
draw_data.render_material = *render_material;
|
|
|
|
{
|
|
auto possibly_deferred = draw_data.render_material.get_depth_mode() == ZBUFFER_TYPE_FULL
|
|
&& gr_is_capable(gr_capability::CAPABILITY_DEFERRED_LIGHTING) && light_deferred_enabled();
|
|
|
|
if (possibly_deferred) {
|
|
draw_data.render_material.set_fog();
|
|
}
|
|
|
|
draw_data.render_material.set_deferred_lighting(possibly_deferred ? Deferred_lighting : false);
|
|
draw_data.render_material.set_high_dynamic_range(High_dynamic_range);
|
|
draw_data.render_material.set_shadow_receiving(Shadow_quality != ShadowQuality::Disabled);
|
|
}
|
|
|
|
if (tmap_flags & TMAP_FLAG_BATCH_TRANSFORMS && buffer->flags & VB_FLAG_MODEL_ID) {
|
|
vm_matrix4_set_identity(&draw_data.transform);
|
|
|
|
draw_data.scale.xyz.x = 1.0f;
|
|
draw_data.scale.xyz.y = 1.0f;
|
|
draw_data.scale.xyz.z = 1.0f;
|
|
|
|
draw_data.transform_buffer_offset = _batchBuffer.get_buffer_offset();
|
|
|
|
draw_data.render_material.set_batching(true);
|
|
} else {
|
|
draw_data.transform = get_transform();
|
|
draw_data.scale = _scale;
|
|
draw_data.transform_buffer_offset = INVALID_SIZE;
|
|
draw_data.render_material.set_batching(false);
|
|
}
|
|
|
|
draw_data.sdr_flags = draw_data.render_material.get_shader_flags();
|
|
|
|
draw_data.vert_src = vert_src;
|
|
draw_data.buffer = buffer;
|
|
draw_data.texi = texi;
|
|
draw_data.flags = tmap_flags;
|
|
draw_data.lights = Current_lights_set;
|
|
|
|
push_element(std::move(draw_data));
|
|
}
|
|
|
|
void model_draw_list::render_buffer(const queued_buffer_draw &render_elements)
|
|
{
|
|
GR_DEBUG_SCOPE("Render buffer");
|
|
TRACE_SCOPE(tracing::RenderBuffer);
|
|
|
|
gr_bind_uniform_buffer(uniform_block_type::ModelData, render_elements.uniform_buffer_offset,
|
|
sizeof(graphics::model_uniform_data), _dataBuffer.bufferHandle());
|
|
|
|
gr_render_model(const_cast<model_material*>(&render_elements.render_material), const_cast<indexed_vertex_source*>(render_elements.vert_src), const_cast<vertex_buffer*>(render_elements.buffer), render_elements.texi);
|
|
}
|
|
|
|
vec3d model_draw_list::get_view_position() const
|
|
{
|
|
matrix basis_world;
|
|
matrix4 transform_mat = _transforms.get_transform();
|
|
matrix orient;
|
|
vec3d pos;
|
|
|
|
vm_matrix4_get_orientation(&orient, &transform_mat);
|
|
vm_matrix4_get_offset(&pos, &transform_mat);
|
|
|
|
vm_matrix_x_matrix(&basis_world, &Object_matrix, &orient);
|
|
|
|
vec3d eye_pos_local;
|
|
vm_vec_sub(&eye_pos_local, &Eye_position, &pos);
|
|
|
|
vec3d return_val;
|
|
vm_vec_rotate(&return_val, &eye_pos_local, &basis_world);
|
|
|
|
return return_val;
|
|
}
|
|
|
|
void model_draw_list::init()
|
|
{
|
|
reset();
|
|
|
|
for (auto& l : Lights) {
|
|
if ( l.type == Light_Type::Directional || !Deferred_lighting ) {
|
|
_lights.addLight(&l);
|
|
}
|
|
}
|
|
}
|
|
|
|
void model_draw_list::render_all(gr_zbuffer_type depth_mode)
|
|
{
|
|
GR_DEBUG_SCOPE("Render draw list");
|
|
TRACE_SCOPE(tracing::SubmitDraws);
|
|
|
|
Assertion(_initialized, "init_render must be called before any render_all call!");
|
|
|
|
_lights.resetLightState();
|
|
|
|
for (int render_index : _keys) {
|
|
if ( depth_mode == ZBUFFER_TYPE_DEFAULT || _elements[render_index].render_material.get_depth_mode() == depth_mode ) {
|
|
render_buffer(_elements[render_index]);
|
|
}
|
|
}
|
|
|
|
gr_alpha_mask_set(0, 1.0f);
|
|
}
|
|
|
|
void model_draw_list::render_arc(const arc_effect &arc)
|
|
{
|
|
g3_start_instance_matrix(&arc.transform);
|
|
|
|
model_render_arc(&arc.v1, &arc.v2, arc.persistent_arc_points, &arc.primary, &arc.secondary, arc.width, arc.segment_depth);
|
|
|
|
g3_done_instance(true);
|
|
}
|
|
|
|
void model_draw_list::render_arcs()
|
|
{
|
|
int mode = gr_zbuffer_set(GR_ZBUFF_READ);
|
|
|
|
for ( size_t i = 0; i < Arcs.size(); ++i ) {
|
|
render_arc(Arcs[i]);
|
|
}
|
|
|
|
gr_zbuffer_set(mode);
|
|
}
|
|
|
|
void model_draw_list::add_insignia(const model_render_params *params, const polymodel *pm, int detail_level, int bitmap_num)
|
|
{
|
|
insignia_draw_data new_insignia;
|
|
|
|
new_insignia.transform = get_transform();
|
|
new_insignia.pm = pm;
|
|
new_insignia.detail_level = detail_level;
|
|
new_insignia.bitmap_num = bitmap_num;
|
|
|
|
new_insignia.clip = params->is_clip_plane_set();
|
|
new_insignia.clip_normal = params->get_clip_plane_normal();
|
|
new_insignia.clip_position = params->get_clip_plane_pos();
|
|
|
|
Insignias.push_back(new_insignia);
|
|
}
|
|
|
|
void model_draw_list::render_insignia(const insignia_draw_data &insignia_info)
|
|
{
|
|
if ( insignia_info.clip ) {
|
|
vec3d tmp;
|
|
vec3d pos;
|
|
|
|
vm_matrix4_get_offset(&pos, &insignia_info.transform);
|
|
vm_vec_sub(&tmp, &pos, &insignia_info.clip_position);
|
|
vm_vec_normalize(&tmp);
|
|
|
|
if ( vm_vec_dot(&tmp, &insignia_info.clip_normal) < 0.0f) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
g3_start_instance_matrix(&insignia_info.transform);
|
|
|
|
model_render_insignias(&insignia_info);
|
|
|
|
g3_done_instance(true);
|
|
}
|
|
|
|
void model_draw_list::render_insignias() const
|
|
{
|
|
for (const auto& ins : Insignias) {
|
|
render_insignia(ins);
|
|
}
|
|
}
|
|
|
|
void model_draw_list::add_outline(const vertex* vert_array, int n_verts, const color *clr)
|
|
{
|
|
outline_draw draw_info;
|
|
|
|
draw_info.vert_array = vert_array;
|
|
draw_info.n_verts = n_verts;
|
|
draw_info.clr = *clr;
|
|
draw_info.transform = get_transform();
|
|
|
|
Outlines.push_back(draw_info);
|
|
}
|
|
|
|
void model_draw_list::render_outlines()
|
|
{
|
|
gr_clear_states();
|
|
|
|
for ( size_t i = 0; i < Outlines.size(); ++i ) {
|
|
render_outline(Outlines[i]);
|
|
}
|
|
}
|
|
|
|
void model_draw_list::render_outline(const outline_draw &outline_info)
|
|
{
|
|
g3_start_instance_matrix(&outline_info.transform);
|
|
|
|
material material_instance;
|
|
|
|
material_instance.set_depth_mode(ZBUFFER_TYPE_READ);
|
|
material_instance.set_blend_mode(ALPHA_BLEND_ALPHA_BLEND_ALPHA);
|
|
material_instance.set_color(outline_info.clr);
|
|
|
|
g3_render_primitives(&material_instance, const_cast<vertex*>(outline_info.vert_array), outline_info.n_verts, PRIM_TYPE_LINES, false);
|
|
|
|
g3_done_instance(true);
|
|
}
|
|
|
|
bool model_draw_list::sort_draw_pair(int a, int b) const
|
|
{
|
|
auto draw_call_a = &_elements[a];
|
|
auto draw_call_b = &_elements[b];
|
|
|
|
if ( draw_call_a->sdr_flags != draw_call_b->sdr_flags ) {
|
|
return draw_call_a->sdr_flags < draw_call_b->sdr_flags;
|
|
}
|
|
|
|
if ( draw_call_a->vert_src->Vbuffer_handle != draw_call_b->vert_src->Vbuffer_handle ) {
|
|
return draw_call_a->vert_src->Vbuffer_handle.value() < draw_call_b->vert_src->Vbuffer_handle.value();
|
|
}
|
|
|
|
if ( draw_call_a->vert_src->Ibuffer_handle != draw_call_b->vert_src->Ibuffer_handle ) {
|
|
return draw_call_a->vert_src->Ibuffer_handle.value() < draw_call_b->vert_src->Ibuffer_handle.value();
|
|
}
|
|
|
|
if ( draw_call_a->render_material.get_texture_map(TM_BASE_TYPE) != draw_call_b->render_material.get_texture_map(TM_BASE_TYPE) ) {
|
|
return draw_call_a->render_material.get_texture_map(TM_BASE_TYPE) < draw_call_b->render_material.get_texture_map(TM_BASE_TYPE);
|
|
}
|
|
|
|
if ( draw_call_a->render_material.get_texture_map(TM_SPECULAR_TYPE) != draw_call_b->render_material.get_texture_map(TM_SPECULAR_TYPE) ) {
|
|
return draw_call_a->render_material.get_texture_map(TM_SPECULAR_TYPE) < draw_call_b->render_material.get_texture_map(TM_SPECULAR_TYPE);
|
|
}
|
|
|
|
if ( draw_call_a->render_material.get_texture_map(TM_SPEC_GLOSS_TYPE) != draw_call_b->render_material.get_texture_map(TM_SPEC_GLOSS_TYPE) ) {
|
|
return draw_call_a->render_material.get_texture_map(TM_SPEC_GLOSS_TYPE) < draw_call_b->render_material.get_texture_map(TM_SPEC_GLOSS_TYPE);
|
|
}
|
|
|
|
if ( draw_call_a->render_material.get_texture_map(TM_GLOW_TYPE) != draw_call_b->render_material.get_texture_map(TM_GLOW_TYPE) ) {
|
|
return draw_call_a->render_material.get_texture_map(TM_GLOW_TYPE) < draw_call_b->render_material.get_texture_map(TM_GLOW_TYPE);
|
|
}
|
|
|
|
if ( draw_call_a->render_material.get_texture_map(TM_NORMAL_TYPE) != draw_call_b->render_material.get_texture_map(TM_NORMAL_TYPE) ) {
|
|
return draw_call_a->render_material.get_texture_map(TM_NORMAL_TYPE) < draw_call_b->render_material.get_texture_map(TM_NORMAL_TYPE);
|
|
}
|
|
|
|
if ( draw_call_a->render_material.get_texture_map(TM_HEIGHT_TYPE) != draw_call_b->render_material.get_texture_map(TM_HEIGHT_TYPE) ) {
|
|
return draw_call_a->render_material.get_texture_map(TM_HEIGHT_TYPE) < draw_call_b->render_material.get_texture_map(TM_HEIGHT_TYPE);
|
|
}
|
|
|
|
if ( draw_call_a->render_material.get_texture_map(TM_AMBIENT_TYPE) != draw_call_b->render_material.get_texture_map(TM_AMBIENT_TYPE) ) {
|
|
return draw_call_a->render_material.get_texture_map(TM_AMBIENT_TYPE) < draw_call_b->render_material.get_texture_map(TM_AMBIENT_TYPE);
|
|
}
|
|
|
|
if ( draw_call_a->render_material.get_texture_map(TM_MISC_TYPE) != draw_call_b->render_material.get_texture_map(TM_MISC_TYPE) ) {
|
|
return draw_call_a->render_material.get_texture_map(TM_MISC_TYPE) < draw_call_b->render_material.get_texture_map(TM_MISC_TYPE);
|
|
}
|
|
|
|
return draw_call_a->lights.index_start < draw_call_b->lights.index_start;
|
|
}
|
|
void model_draw_list::build_uniform_buffer() {
|
|
GR_DEBUG_SCOPE("Build model uniform buffer");
|
|
|
|
TRACE_SCOPE(tracing::BuildModelUniforms);
|
|
|
|
_dataBuffer = gr_get_uniform_buffer(uniform_block_type::ModelData, _keys.size());
|
|
|
|
for (auto render_index : _keys) {
|
|
auto& queued_draw = _elements[render_index];
|
|
|
|
if ( queued_draw.render_material.is_lit() ) {
|
|
_lights.setLights(&queued_draw.lights);
|
|
} else {
|
|
_lights.resetLightState();
|
|
}
|
|
|
|
auto element = _dataBuffer.aligner().addTypedElement<graphics::model_uniform_data>();
|
|
graphics::uniforms::convert_model_material(element,
|
|
queued_draw.render_material,
|
|
queued_draw.transform,
|
|
queued_draw.scale,
|
|
queued_draw.transform_buffer_offset);
|
|
queued_draw.uniform_buffer_offset = _dataBuffer.getCurrentAlignerOffset();
|
|
}
|
|
|
|
TRACE_SCOPE(tracing::UploadModelUniforms);
|
|
|
|
_dataBuffer.submitData();
|
|
}
|
|
model_draw_list::~model_draw_list() {
|
|
reset();
|
|
}
|
|
|
|
void model_render_add_lightning(model_draw_list *scene, const model_render_params* interp, const polymodel *pm, const submodel_instance *smi )
|
|
{
|
|
float width = 0.9f;
|
|
color primary, secondary;
|
|
|
|
if ( interp->get_model_flags() & MR_SHOW_OUTLINE_PRESET ) {
|
|
return;
|
|
}
|
|
|
|
extern int Interp_lightning;
|
|
if ( !Interp_lightning ) {
|
|
return;
|
|
}
|
|
|
|
for (auto &arc: smi->electrical_arcs) {
|
|
// pick a color based upon arc type
|
|
switch ( arc.type ) {
|
|
// "normal", FreeSpace 1 style arcs
|
|
case MARC_TYPE_DAMAGED:
|
|
if ( Random::flip_coin() ) {
|
|
primary = Arc_color_damage_p1;
|
|
} else {
|
|
primary = Arc_color_damage_p2;
|
|
}
|
|
|
|
secondary = Arc_color_damage_s1;
|
|
|
|
// try and scale the size a bit so that it looks equally well on smaller vessels
|
|
width = Arc_width_default_damage;
|
|
if (pm->rad < Arc_width_no_multiply_over_radius_damage) {
|
|
width *= (pm->rad * Arc_width_radius_multiplier_damage);
|
|
|
|
if (width < Arc_width_minimum_damage) {
|
|
width = Arc_width_minimum_damage;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case MARC_TYPE_SCRIPTED:
|
|
case MARC_TYPE_SHIP:
|
|
if ( Random::flip_coin() ) {
|
|
primary = arc.primary_color_1;
|
|
} else {
|
|
primary = arc.primary_color_2;
|
|
}
|
|
|
|
secondary = arc.secondary_color;
|
|
|
|
width = arc.width;
|
|
|
|
break;
|
|
|
|
// "EMP" style arcs
|
|
case MARC_TYPE_EMP:
|
|
if ( Random::flip_coin() ) {
|
|
primary = Arc_color_emp_p1;
|
|
} else {
|
|
primary = Arc_color_emp_p2;
|
|
}
|
|
|
|
secondary = Arc_color_emp_s1;
|
|
|
|
// try and scale the size a bit so that it looks equally well on smaller vessels
|
|
width = Arc_width_default_emp;
|
|
if (pm->rad < Arc_width_no_multiply_over_radius_emp) {
|
|
width *= (pm->rad * Arc_width_radius_multiplier_emp);
|
|
|
|
if (width < Arc_width_minimum_emp) {
|
|
width = Arc_width_minimum_emp;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
UNREACHABLE("Unknown arc type of %d found in model_render_add_lightning(), please contact an SCP coder!", arc.type);
|
|
}
|
|
|
|
// render the actual arc segment
|
|
if (width > 0.0f)
|
|
scene->add_arc(&arc.endpoint_1, &arc.endpoint_2, arc.persistent_arc_points, &primary, &secondary, width, arc.segment_depth);
|
|
}
|
|
}
|
|
|
|
float model_render_determine_depth(int obj_num, int model_num, const matrix* orient, const vec3d* pos, int detail_level_locked, const vec3d* eye_pos)
|
|
{
|
|
if (eye_pos == nullptr)
|
|
eye_pos = &Eye_position;
|
|
|
|
vec3d closest_pos;
|
|
float depth = model_find_closest_point( &closest_pos, model_num, -1, orient, pos, eye_pos );
|
|
|
|
if ( detail_level_locked < 0 ) {
|
|
switch (Detail.detail_distance) {
|
|
case 0: // lowest
|
|
depth /= The_mission.ai_profile->detail_distance_mult[0];
|
|
break;
|
|
case 1: // lower than normal
|
|
depth /= The_mission.ai_profile->detail_distance_mult[1];
|
|
break;
|
|
case 2: // default
|
|
depth /= The_mission.ai_profile->detail_distance_mult[2];
|
|
break;
|
|
case 3: // above normal
|
|
depth /= The_mission.ai_profile->detail_distance_mult[3];
|
|
break;
|
|
case 4: // even more normal
|
|
depth /= The_mission.ai_profile->detail_distance_mult[4];
|
|
break;
|
|
}
|
|
|
|
// nebula ?
|
|
if (The_mission.flags[Mission::Mission_Flags::Fullneb]) {
|
|
depth *= neb2_get_lod_scale(obj_num);
|
|
}
|
|
|
|
}
|
|
|
|
return depth;
|
|
}
|
|
|
|
int model_render_determine_detail(float depth, int model_num, int detail_level_locked)
|
|
{
|
|
int tmp_detail_level = Game_detail_level;
|
|
|
|
polymodel *pm = model_get(model_num);
|
|
|
|
Assert( pm->n_detail_levels < MAX_MODEL_DETAIL_LEVELS );
|
|
|
|
int i;
|
|
|
|
if ( pm->n_detail_levels > 1 ) {
|
|
if ( detail_level_locked >= 0 ) {
|
|
i = detail_level_locked+1;
|
|
} else {
|
|
|
|
static_assert(MAX_DETAIL_VALUE == 4, "MAX_DETAIL_VALUE is assumed to be 4 in SystemVars.cpp");
|
|
|
|
for ( i = 0; i < pm->n_detail_levels; i++ ) {
|
|
if ( depth <= pm->detail_depth[i] ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If no valid detail depths specified, use highest.
|
|
if ( (i > 1) && (pm->detail_depth[i-1] < 1.0f) ) {
|
|
i = 1;
|
|
}
|
|
}
|
|
|
|
int detail_level = i - 1 - tmp_detail_level;
|
|
|
|
if ( detail_level < 0 ) {
|
|
return 0;
|
|
} else if ( detail_level >= pm->n_detail_levels ) {
|
|
return pm->n_detail_levels - 1;
|
|
}
|
|
|
|
return detail_level;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void model_render_buffers(model_draw_list* scene, model_material *rendering_material, const model_render_params* interp, const vertex_buffer *buffer, const polymodel *pm, int mn, int detail_level, uint tmap_flags)
|
|
{
|
|
bsp_info *submodel = nullptr;
|
|
const uint64_t model_flags = interp->get_model_flags();
|
|
const uint debug_flags = interp->get_debug_flags();
|
|
const int obj_num = interp->get_object_number();
|
|
|
|
Assert(buffer != nullptr);
|
|
Assert(detail_level >= 0);
|
|
|
|
if ( (mn >= 0) && (mn < pm->n_models) ) {
|
|
submodel = &pm->submodel[mn];
|
|
}
|
|
|
|
bool render_as_thruster = (submodel != nullptr) && submodel->flags[Model::Submodel_flags::Is_thruster] && (model_flags & MR_SHOW_THRUSTERS);
|
|
|
|
vec3d scale;
|
|
|
|
if ( render_as_thruster ) {
|
|
scale.xyz.x = 1.0f;
|
|
scale.xyz.y = 1.0f;
|
|
|
|
scale.xyz.z = 1.0f;
|
|
rendering_material->set_thrust_scale(interp->get_thruster_info().length.xyz.z);
|
|
} else {
|
|
scale = interp->get_warp_scale();
|
|
rendering_material->set_thrust_scale();
|
|
}
|
|
|
|
scene->set_scale(&scale);
|
|
|
|
if ( tmap_flags & TMAP_FLAG_BATCH_TRANSFORMS && (mn >= 0) && (mn < pm->n_models) ) {
|
|
scene->add_submodel_to_batch(mn);
|
|
return;
|
|
}
|
|
|
|
int elapsed_time = model_render_determine_elapsed_time(obj_num, model_flags);
|
|
|
|
texture_info tex_replace[TM_NUM_TYPES];
|
|
|
|
int no_texturing = model_flags & MR_NO_TEXTURING;
|
|
|
|
int forced_texture = -2;
|
|
float forced_alpha = 1.0f;
|
|
int forced_blend_filter = GR_ALPHABLEND_NONE;
|
|
|
|
if ( interp->get_forced_bitmap() >= 0 ) {
|
|
forced_texture = interp->get_forced_bitmap();
|
|
} else if ( interp->get_warp_bitmap() >= 0 ) {
|
|
forced_texture = interp->get_warp_bitmap();
|
|
forced_alpha = interp->get_warp_alpha();
|
|
forced_blend_filter = GR_ALPHABLEND_FILTER;
|
|
} else if ( render_as_thruster ) {
|
|
if ( ( interp->get_thruster_info().primary_bitmap >= 0 ) && ( interp->get_thruster_info().length.xyz.z > 0.0f ) ) {
|
|
forced_texture = interp->get_thruster_info().primary_bitmap;
|
|
} else {
|
|
forced_texture = -1;
|
|
}
|
|
|
|
forced_alpha = 1.2f;
|
|
forced_blend_filter = GR_ALPHABLEND_FILTER;
|
|
} else if ( model_flags & MR_ALL_XPARENT ) {
|
|
forced_alpha = interp->get_alpha();
|
|
forced_blend_filter = GR_ALPHABLEND_FILTER;
|
|
}
|
|
|
|
int texture_maps[TM_NUM_TYPES] = { -1 };
|
|
size_t buffer_size = buffer->tex_buf.size();
|
|
const auto& replacement_textures = interp->get_replacement_textures();
|
|
|
|
for ( size_t i = 0; i < buffer_size; i++ ) {
|
|
int tmap_num = buffer->tex_buf[i].texture;
|
|
auto tmap = &pm->maps[tmap_num];
|
|
int rt_begin_index = tmap_num*TM_NUM_TYPES;
|
|
float alpha = 1.0f;
|
|
|
|
texture_maps[TM_BASE_TYPE] = -1;
|
|
texture_maps[TM_GLOW_TYPE] = -1;
|
|
texture_maps[TM_SPECULAR_TYPE] = -1;
|
|
texture_maps[TM_NORMAL_TYPE] = -1;
|
|
texture_maps[TM_HEIGHT_TYPE] = -1;
|
|
texture_maps[TM_MISC_TYPE] = -1;
|
|
texture_maps[TM_SPEC_GLOSS_TYPE] = -1;
|
|
texture_maps[TM_AMBIENT_TYPE] = -1;
|
|
|
|
if (forced_texture != -2) {
|
|
texture_maps[TM_BASE_TYPE] = forced_texture;
|
|
alpha = forced_alpha;
|
|
|
|
if (interp->get_warp_bitmap() >= 0) {
|
|
texture_maps[TM_GLOW_TYPE] = forced_texture;
|
|
}
|
|
|
|
} else if ( !no_texturing ) {
|
|
// pick the texture, animating it if necessary
|
|
if ( (replacement_textures != nullptr) && ((*replacement_textures)[rt_begin_index + TM_BASE_TYPE] == REPLACE_WITH_INVISIBLE) ) {
|
|
// invisible textures aren't rendered, but we still have to skip assigning the underlying model texture
|
|
texture_maps[TM_BASE_TYPE] = -1;
|
|
} else if ( (replacement_textures != nullptr) && ((*replacement_textures)[rt_begin_index + TM_BASE_TYPE] >= 0) ) {
|
|
// an underlying texture is replaced with a real new texture
|
|
tex_replace[TM_BASE_TYPE] = texture_info((*replacement_textures)[rt_begin_index + TM_BASE_TYPE]);
|
|
texture_maps[TM_BASE_TYPE] = model_interp_get_texture(&tex_replace[TM_BASE_TYPE], elapsed_time);
|
|
} else {
|
|
// we just use the underlying texture
|
|
texture_maps[TM_BASE_TYPE] = model_interp_get_texture(&tmap->textures[TM_BASE_TYPE], elapsed_time);
|
|
}
|
|
|
|
if ( texture_maps[TM_BASE_TYPE] < 0 ) {
|
|
continue;
|
|
}
|
|
|
|
// doing glow maps?
|
|
if ( !(model_flags & MR_NO_GLOWMAPS) ) {
|
|
auto tglow = &tmap->textures[TM_GLOW_TYPE];
|
|
|
|
if ( (replacement_textures != nullptr) && ((*replacement_textures)[rt_begin_index + TM_GLOW_TYPE] >= 0) ) {
|
|
tex_replace[TM_GLOW_TYPE] = texture_info((*replacement_textures)[rt_begin_index + TM_GLOW_TYPE]);
|
|
texture_maps[TM_GLOW_TYPE] = model_interp_get_texture(&tex_replace[TM_GLOW_TYPE], elapsed_time);
|
|
} else if (tglow->GetTexture() >= 0) {
|
|
// shockwaves are special, their current frame has to come out of the shockwave code to get the timing correct
|
|
if ( (obj_num >= 0) && (Objects[obj_num].type == OBJ_SHOCKWAVE) && (tglow->GetNumFrames() > 1) ) {
|
|
texture_maps[TM_GLOW_TYPE] = tglow->GetTexture() + shockwave_get_framenum(Objects[obj_num].instance, tglow->GetTexture());
|
|
} else {
|
|
texture_maps[TM_GLOW_TYPE] = model_interp_get_texture(tglow, elapsed_time);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!(debug_flags & MR_DEBUG_NO_SPEC)) {
|
|
if (replacement_textures != nullptr && (*replacement_textures)[rt_begin_index + TM_SPECULAR_TYPE] >= 0) {
|
|
tex_replace[TM_SPECULAR_TYPE] = texture_info((*replacement_textures)[rt_begin_index + TM_SPECULAR_TYPE]);
|
|
texture_maps[TM_SPECULAR_TYPE] = model_interp_get_texture(&tex_replace[TM_SPECULAR_TYPE], elapsed_time);
|
|
}
|
|
else {
|
|
texture_maps[TM_SPECULAR_TYPE] = model_interp_get_texture(&tmap->textures[TM_SPECULAR_TYPE], elapsed_time);
|
|
}
|
|
}
|
|
|
|
if ( replacement_textures != nullptr && (*replacement_textures)[rt_begin_index + TM_SPEC_GLOSS_TYPE] >= 0 ) {
|
|
tex_replace[TM_SPEC_GLOSS_TYPE] = texture_info((*replacement_textures)[rt_begin_index + TM_SPEC_GLOSS_TYPE]);
|
|
texture_maps[TM_SPEC_GLOSS_TYPE] = model_interp_get_texture(&tex_replace[TM_SPEC_GLOSS_TYPE], elapsed_time);
|
|
} else {
|
|
texture_maps[TM_SPEC_GLOSS_TYPE] = model_interp_get_texture(&tmap->textures[TM_SPEC_GLOSS_TYPE], elapsed_time);
|
|
}
|
|
|
|
if (detail_level < 2 || Detail.detail_distance > 2) {
|
|
// likewise, etc.
|
|
auto norm_map = &tmap->textures[TM_NORMAL_TYPE];
|
|
auto height_map = &tmap->textures[TM_HEIGHT_TYPE];
|
|
auto ambient_map = &tmap->textures[TM_AMBIENT_TYPE];
|
|
auto misc_map = &tmap->textures[TM_MISC_TYPE];
|
|
|
|
if (replacement_textures != nullptr) {
|
|
const auto& replacement_textures_deref = *replacement_textures;
|
|
if (replacement_textures_deref[rt_begin_index + TM_NORMAL_TYPE] >= 0) {
|
|
tex_replace[TM_NORMAL_TYPE] = texture_info(replacement_textures_deref[rt_begin_index + TM_NORMAL_TYPE]);
|
|
norm_map = &tex_replace[TM_NORMAL_TYPE];
|
|
}
|
|
|
|
if (replacement_textures_deref[rt_begin_index + TM_HEIGHT_TYPE] >= 0) {
|
|
tex_replace[TM_HEIGHT_TYPE] = texture_info(replacement_textures_deref[rt_begin_index + TM_HEIGHT_TYPE]);
|
|
height_map = &tex_replace[TM_HEIGHT_TYPE];
|
|
}
|
|
|
|
if (replacement_textures_deref[rt_begin_index + TM_AMBIENT_TYPE] >= 0) {
|
|
tex_replace[TM_AMBIENT_TYPE] = texture_info(replacement_textures_deref[rt_begin_index + TM_AMBIENT_TYPE]);
|
|
ambient_map = &tex_replace[TM_AMBIENT_TYPE];
|
|
}
|
|
|
|
if (replacement_textures_deref[rt_begin_index + TM_MISC_TYPE] >= 0) {
|
|
tex_replace[TM_MISC_TYPE] = texture_info(replacement_textures_deref[rt_begin_index + TM_MISC_TYPE]);
|
|
misc_map = &tex_replace[TM_MISC_TYPE];
|
|
}
|
|
}
|
|
|
|
if (debug_flags & MR_DEBUG_NO_DIFFUSE) texture_maps[TM_BASE_TYPE] = -1;
|
|
if (debug_flags & MR_DEBUG_NO_GLOW) texture_maps[TM_GLOW_TYPE] = -1;
|
|
if (debug_flags & MR_DEBUG_NO_SPEC) texture_maps[TM_SPECULAR_TYPE] = -1;
|
|
if (debug_flags & MR_DEBUG_NO_REFLECT) texture_maps[TM_SPEC_GLOSS_TYPE] = -1;
|
|
if (!(debug_flags & MR_DEBUG_NO_MISC)) texture_maps[TM_MISC_TYPE] = model_interp_get_texture(misc_map, elapsed_time);
|
|
if (!(debug_flags & MR_DEBUG_NO_NORMAL) && Detail.lighting > 0) texture_maps[TM_NORMAL_TYPE] = model_interp_get_texture(norm_map, elapsed_time);
|
|
if (!(debug_flags & MR_DEBUG_NO_AMBIENT) && Detail.lighting > 0) texture_maps[TM_AMBIENT_TYPE] = model_interp_get_texture(ambient_map, elapsed_time);
|
|
if (!(debug_flags & MR_DEBUG_NO_HEIGHT) && Detail.lighting > 1) texture_maps[TM_HEIGHT_TYPE] = model_interp_get_texture(height_map, elapsed_time);
|
|
}
|
|
} else {
|
|
alpha = forced_alpha;
|
|
}
|
|
|
|
if ( (texture_maps[TM_BASE_TYPE] == -1) && !no_texturing && !(debug_flags & MR_DEBUG_NO_DIFFUSE) ) {
|
|
continue;
|
|
}
|
|
|
|
bool use_blending = false;
|
|
|
|
// trying to get transparent textures-Bobboau
|
|
if (tmap->is_transparent) {
|
|
// for special shockwave/warp map usage
|
|
alpha = (interp->get_warp_alpha() != -1.0f) ? interp->get_warp_alpha() : 0.8f;
|
|
use_blending = true;
|
|
} else if ( buffer->flags & VB_FLAG_TRANS ) {
|
|
use_blending = true;
|
|
}
|
|
|
|
if (rendering_material->is_alpha_mult_active() && !(model_flags & MR_SKYBOX)) {
|
|
use_blending = true;
|
|
}
|
|
|
|
if (forced_blend_filter != GR_ALPHABLEND_NONE) {
|
|
use_blending = true;
|
|
}
|
|
|
|
bool use_depth_test;
|
|
|
|
if ( use_blending ) {
|
|
use_depth_test = true;
|
|
} else {
|
|
use_depth_test = !((model_flags & MR_NO_ZBUFFER) || (model_flags & MR_ALL_XPARENT));
|
|
}
|
|
|
|
gr_alpha_blend blend_mode = model_render_determine_blend_mode(texture_maps[TM_BASE_TYPE], use_blending);
|
|
gr_zbuffer_type depth_mode = material_determine_depth_mode(use_depth_test, use_blending);
|
|
|
|
if (rendering_material->is_alpha_mult_active() && !(model_flags & MR_SKYBOX)) {
|
|
blend_mode = ALPHA_BLEND_PREMULTIPLIED;
|
|
}
|
|
|
|
rendering_material->set_depth_mode(depth_mode);
|
|
rendering_material->set_blend_mode(depth_mode == ZBUFFER_TYPE_FULL ? ALPHA_BLEND_NONE : blend_mode);
|
|
|
|
color clr = interp->get_color();
|
|
model_render_determine_color(&clr, alpha, blend_mode, no_texturing ? true : false, rendering_material->is_desaturated());
|
|
rendering_material->set_color(clr);
|
|
|
|
if ( (tmap_flags & TMAP_FLAG_TEXTURED) && (buffer->flags & VB_FLAG_UV1) ) {
|
|
rendering_material->set_texture_map(TM_BASE_TYPE, texture_maps[TM_BASE_TYPE]);
|
|
|
|
if ( texture_maps[TM_BASE_TYPE] >= 0 && bm_has_alpha_channel(texture_maps[TM_BASE_TYPE]) ) {
|
|
rendering_material->set_texture_type(material::TEX_TYPE_XPARENT);
|
|
}
|
|
|
|
rendering_material->set_texture_map(TM_GLOW_TYPE, texture_maps[TM_GLOW_TYPE]);
|
|
rendering_material->set_texture_map(TM_SPECULAR_TYPE, texture_maps[TM_SPECULAR_TYPE]);
|
|
rendering_material->set_texture_map(TM_SPEC_GLOSS_TYPE, texture_maps[TM_SPEC_GLOSS_TYPE]);
|
|
rendering_material->set_texture_map(TM_NORMAL_TYPE, texture_maps[TM_NORMAL_TYPE]);
|
|
rendering_material->set_texture_map(TM_HEIGHT_TYPE, texture_maps[TM_HEIGHT_TYPE]);
|
|
rendering_material->set_texture_map(TM_AMBIENT_TYPE, texture_maps[TM_AMBIENT_TYPE]);
|
|
rendering_material->set_texture_map(TM_MISC_TYPE, texture_maps[TM_MISC_TYPE]);
|
|
}
|
|
|
|
scene->add_buffer_draw(rendering_material, &pm->vert_source, buffer, i, tmap_flags);
|
|
}
|
|
}
|
|
|
|
void model_render_children_buffers(model_draw_list* scene, model_material *rendering_material, const model_render_params* interp, const polymodel* pm, const polymodel_instance *pmi, int mn, int detail_level, uint tmap_flags, bool trans_buffer)
|
|
{
|
|
int i;
|
|
|
|
if ( (mn < 0) || (mn >= pm->n_models) ) {
|
|
Int3();
|
|
return;
|
|
}
|
|
|
|
bsp_info *sm = &pm->submodel[mn];
|
|
submodel_instance *smi = nullptr;
|
|
|
|
if ( pmi != nullptr ) {
|
|
smi = &pmi->submodel[mn];
|
|
if ( smi->blown_off ) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
const uint64_t model_flags = interp->get_model_flags();
|
|
|
|
if (sm->flags[Model::Submodel_flags::Is_thruster]) {
|
|
if ( !( model_flags & MR_SHOW_THRUSTERS ) ) {
|
|
return;
|
|
}
|
|
|
|
rendering_material->set_lighting(false);
|
|
}
|
|
|
|
vec3d view_pos = scene->get_view_position();
|
|
|
|
if ( !model_render_check_detail_box(&view_pos, pm, mn, model_flags) ) {
|
|
return;
|
|
}
|
|
|
|
// Get submodel rotation/translation data and use it to put together a matrix and a vector
|
|
// describing the final position of the submodel relative to its parent
|
|
matrix submodel_orient = vmd_identity_matrix;
|
|
vec3d submodel_offset = sm->offset;
|
|
|
|
if ( smi != nullptr ) {
|
|
submodel_orient = smi->canonical_orient;
|
|
vm_vec_add2(&submodel_offset, &smi->canonical_offset);
|
|
}
|
|
|
|
scene->push_transform(&submodel_offset, &submodel_orient);
|
|
|
|
if ( (model_flags & MR_SHOW_OUTLINE || model_flags & MR_SHOW_OUTLINE_HTL || model_flags & MR_SHOW_OUTLINE_PRESET) &&
|
|
sm->outline_buffer != nullptr ) {
|
|
color outline_color = interp->get_color();
|
|
scene->add_outline(sm->outline_buffer.get(), sm->n_verts_outline, &outline_color);
|
|
} else {
|
|
if ( trans_buffer && sm->trans_buffer.flags & VB_FLAG_TRANS ) {
|
|
model_render_buffers(scene, rendering_material, interp, &sm->trans_buffer, pm, mn, detail_level, tmap_flags);
|
|
} else {
|
|
model_render_buffers(scene, rendering_material, interp, &sm->buffer, pm, mn, detail_level, tmap_flags);
|
|
}
|
|
}
|
|
|
|
if ( smi != nullptr && !smi->electrical_arcs.empty() ) {
|
|
model_render_add_lightning( scene, interp, pm, smi );
|
|
}
|
|
|
|
i = sm->first_child;
|
|
|
|
while ( i >= 0 ) {
|
|
if ( !pm->submodel[i].flags[Model::Submodel_flags::Is_thruster] ) {
|
|
model_render_children_buffers( scene, rendering_material, interp, pm, pmi, i, detail_level, tmap_flags, trans_buffer );
|
|
}
|
|
|
|
i = pm->submodel[i].next_sibling;
|
|
}
|
|
|
|
if ( sm->flags[Model::Submodel_flags::Is_thruster] ) {
|
|
rendering_material->set_lighting(true);
|
|
}
|
|
|
|
scene->pop_transform();
|
|
}
|
|
|
|
float model_render_determine_light_factor(const model_render_params* interp, const vec3d *pos, uint64_t flags)
|
|
{
|
|
if ( flags & MR_IS_ASTEROID ) {
|
|
// Dim it based on distance
|
|
float depth = vm_vec_dist_quick( pos, &Eye_position );
|
|
if ( depth > interp->get_depth_scale() ) {
|
|
float temp_light = interp->get_depth_scale()/depth;
|
|
|
|
if ( temp_light > 1.0f ) {
|
|
return 1.0f;
|
|
}
|
|
|
|
return temp_light;
|
|
}
|
|
}
|
|
|
|
return 1.0f;
|
|
}
|
|
|
|
float model_render_determine_box_scale()
|
|
{
|
|
float box_scale = 1.2f;
|
|
|
|
// scale the render box settings based on the "Model Detail" slider
|
|
switch ( Detail.detail_distance ) {
|
|
case 0: // 1st dot is 20%
|
|
box_scale = 0.2f;
|
|
break;
|
|
case 1: // 2nd dot is 50%
|
|
box_scale = 0.5f;
|
|
break;
|
|
case 2: // 3rd dot is 80%
|
|
box_scale = 0.8f;
|
|
break;
|
|
case 3: // 4th dot is 100% (this is the default setting for "High" and "Very High" settings)
|
|
box_scale = 1.0f;
|
|
break;
|
|
case 4: // 5th dot (max) is 120%
|
|
default:
|
|
box_scale = 1.2f;
|
|
break;
|
|
}
|
|
|
|
return box_scale;
|
|
}
|
|
|
|
// Goober5000
|
|
// Returns milliseconds since texture animation started.
|
|
int model_render_determine_elapsed_time(int objnum, uint64_t flags)
|
|
{
|
|
if ( objnum >= 0 ) {
|
|
object *objp = &Objects[objnum];
|
|
|
|
if ( objp->type == OBJ_SHIP ) {
|
|
return timestamp_since(Ships[objp->instance].base_texture_anim_timestamp);
|
|
}
|
|
} else if ( flags & MR_SKYBOX ) {
|
|
return timestamp_since(Skybox_timestamp);
|
|
}
|
|
|
|
// by default, assume texture animation started at the beginning of the mission
|
|
return timestamp_get_mission_time_in_milliseconds();
|
|
}
|
|
|
|
bool model_render_determine_autocenter(vec3d *auto_back, const polymodel *pm, int detail_level, uint64_t flags)
|
|
{
|
|
if ( flags & MR_AUTOCENTER ) {
|
|
// standard autocenter using data in model
|
|
if ( pm->flags & PM_FLAG_AUTOCEN ) {
|
|
*auto_back = pm->autocenter;
|
|
vm_vec_scale(auto_back, -1.0f);
|
|
return true;
|
|
} else if ( flags & MR_IS_MISSILE ) {
|
|
// fake autocenter if we are a missile and don't already have autocen info
|
|
auto_back->xyz.x = -( (pm->submodel[pm->detail[detail_level]].max.xyz.x + pm->submodel[pm->detail[detail_level]].min.xyz.x) / 2.0f );
|
|
auto_back->xyz.y = -( (pm->submodel[pm->detail[detail_level]].max.xyz.y + pm->submodel[pm->detail[detail_level]].min.xyz.y) / 2.0f );
|
|
auto_back->xyz.z = -( (pm->submodel[pm->detail[detail_level]].max.xyz.z + pm->submodel[pm->detail[detail_level]].min.xyz.z) / 2.0f );
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void model_render_determine_color(color *clr, float alpha, gr_alpha_blend blend_mode, bool no_texturing, bool desaturate)
|
|
{
|
|
clr->alpha = static_cast<ubyte>((alpha * 255.0f));
|
|
|
|
if ( no_texturing || desaturate ) {
|
|
// don't override the given color if we're not texturing or we're desaturating
|
|
return;
|
|
}
|
|
|
|
if ( blend_mode == ALPHA_BLEND_ADDITIVE ) {
|
|
clr->red = clr->green = clr->blue = clr->alpha;
|
|
clr->alpha = 255;
|
|
} else {
|
|
clr->red = clr->green = clr->blue = 255;
|
|
}
|
|
}
|
|
|
|
gr_alpha_blend model_render_determine_blend_mode(int base_bitmap, bool blending)
|
|
{
|
|
if ( blending ) {
|
|
if ( base_bitmap >= 0 && bm_has_alpha_channel(base_bitmap) ) {
|
|
return ALPHA_BLEND_PREMULTIPLIED;
|
|
}
|
|
|
|
return ALPHA_BLEND_ADDITIVE;
|
|
}
|
|
|
|
return ALPHA_BLEND_ALPHA_BLEND_ALPHA;
|
|
}
|
|
|
|
bool model_render_check_detail_box(const vec3d *view_pos, const polymodel *pm, int submodel_num, uint64_t flags)
|
|
{
|
|
Assert(pm != NULL);
|
|
|
|
bsp_info *model = &pm->submodel[submodel_num];
|
|
|
|
float box_scale = model_render_determine_box_scale();
|
|
if (model->flags[Model::Submodel_flags::Do_not_scale_detail_distances]) {
|
|
box_scale = 1.0f;
|
|
}
|
|
|
|
if ( !( flags & MR_FULL_DETAIL ) && model->use_render_box ) {
|
|
vec3d box_min, box_max, offset;
|
|
|
|
if (model->flags[Model::Submodel_flags::Use_render_box_offset]) {
|
|
offset = model->render_box_offset;
|
|
} else {
|
|
model_find_submodel_offset(&offset, pm, submodel_num);
|
|
}
|
|
|
|
vm_vec_copy_scale(&box_min, &model->render_box_min, box_scale);
|
|
vm_vec_copy_scale(&box_max, &model->render_box_max, box_scale);
|
|
|
|
if ( (-model->use_render_box + in_box(&box_min, &box_max, &offset, view_pos)) ) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ( !(flags & MR_FULL_DETAIL) && model->use_render_sphere ) {
|
|
float sphere_radius = model->render_sphere_radius * box_scale;
|
|
|
|
// TODO: doesn't consider submodel rotations yet -zookeeper
|
|
vec3d offset;
|
|
|
|
if (model->flags[Model::Submodel_flags::Use_render_sphere_offset]) {
|
|
offset = model->render_sphere_offset;
|
|
} else {
|
|
model_find_submodel_offset(&offset, pm, submodel_num);
|
|
}
|
|
|
|
if ( (-model->use_render_sphere + in_sphere(&offset, sphere_radius, view_pos)) ) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void submodel_render_immediate(const model_render_params *render_info, const polymodel *pm, const polymodel_instance *pmi, int submodel_num, const matrix *orient, const vec3d *pos)
|
|
{
|
|
Assert(!pmi || pm->id == pmi->model_num);
|
|
|
|
model_draw_list model_list;
|
|
|
|
model_list.init();
|
|
|
|
submodel_render_queue(render_info, &model_list, pm, pmi, submodel_num, orient, pos);
|
|
|
|
model_list.init_render();
|
|
model_list.render_all();
|
|
|
|
gr_zbias(0);
|
|
gr_zbuffer_set(ZBUFFER_TYPE_READ);
|
|
gr_set_cull(0);
|
|
gr_set_fill_mode(GR_FILL_MODE_SOLID);
|
|
|
|
gr_clear_states();
|
|
|
|
gr_reset_lighting();
|
|
}
|
|
|
|
void submodel_render_queue(const model_render_params *render_info, model_draw_list *scene, const polymodel *pm, const polymodel_instance *pmi, int submodel_num, const matrix *orient, const vec3d *pos)
|
|
{
|
|
Assert(!pmi || pm->id == pmi->model_num);
|
|
|
|
model_material rendering_material;
|
|
|
|
//MONITOR_INC( NumModelsRend, 1 );
|
|
|
|
if ( !( Game_detail_flags & DETAIL_FLAG_MODELS ) ) return;
|
|
|
|
if ( render_info->is_clip_plane_set() ) {
|
|
rendering_material.set_clip_plane(render_info->get_clip_plane_normal(), render_info->get_clip_plane_pos());
|
|
}
|
|
|
|
if ( render_info->is_team_color_set() ) {
|
|
rendering_material.set_team_color(render_info->get_team_color());
|
|
}
|
|
|
|
uint64_t flags = render_info->get_model_flags();
|
|
|
|
// Set the flags we will pass to the tmapper
|
|
uint tmap_flags = TMAP_FLAG_GOURAUD | TMAP_FLAG_RGB;
|
|
|
|
// if we're in nebula mode
|
|
if( ( The_mission.flags[Mission::Mission_Flags::Fullneb] ) && ( Neb2_render_mode != NEB2_RENDER_NONE ) ) {
|
|
tmap_flags |= TMAP_FLAG_PIXEL_FOG;
|
|
}
|
|
|
|
if ( !( flags & MR_NO_TEXTURING ) ) {
|
|
tmap_flags |= TMAP_FLAG_TEXTURED;
|
|
|
|
if ( ( pm->flags & PM_FLAG_ALLOW_TILING ) && tiling )
|
|
tmap_flags |= TMAP_FLAG_TILED;
|
|
|
|
if ( !( flags & MR_NO_CORRECT ) ) {
|
|
tmap_flags |= TMAP_FLAG_CORRECT;
|
|
}
|
|
}
|
|
|
|
if ( render_info->get_animated_effect_num() >= 0 ) {
|
|
tmap_flags |= TMAP_ANIMATED_SHADER;
|
|
rendering_material.set_animated_effect(render_info->get_animated_effect_num(), render_info->get_animated_effect_timer());
|
|
}
|
|
|
|
bool is_outlines_only_htl = (flags & MR_NO_POLYS) && (flags & MR_SHOW_OUTLINE_HTL);
|
|
|
|
//set to true since D3d and OGL need the api matrices set
|
|
scene->push_transform(pos, orient);
|
|
|
|
|
|
vec3d auto_back = ZERO_VECTOR;
|
|
bool set_autocen = model_render_determine_autocenter(&auto_back, pm, render_info->get_detail_level_lock(), flags);
|
|
|
|
if ( set_autocen ) {
|
|
scene->push_transform(&auto_back, NULL);
|
|
}
|
|
|
|
if (is_outlines_only_htl) {
|
|
rendering_material.set_fill_mode(GR_FILL_MODE_WIRE);
|
|
|
|
color outline_color = render_info->get_color();
|
|
rendering_material.set_color(outline_color);
|
|
|
|
tmap_flags &= ~TMAP_FLAG_RGB;
|
|
} else {
|
|
rendering_material.set_fill_mode(GR_FILL_MODE_SOLID);
|
|
}
|
|
|
|
rendering_material.set_light_factor(1.0f);
|
|
|
|
if ( !( flags & MR_NO_LIGHTING ) ) {
|
|
scene->set_light_filter(pos, pm->submodel[submodel_num].rad);
|
|
rendering_material.set_lighting(true);
|
|
} else {
|
|
rendering_material.set_lighting(false);
|
|
}
|
|
|
|
// fixes disappearing HUD in OGL - taylor
|
|
rendering_material.set_cull_mode(true);
|
|
|
|
// RT - Put this here to fog debris
|
|
if( tmap_flags & TMAP_FLAG_PIXEL_FOG ) {
|
|
unsigned char r, g, b;
|
|
neb2_get_fog_color(&r, &g, &b);
|
|
|
|
rendering_material.set_fog(r, g, b);
|
|
} else {
|
|
rendering_material.set_fog();
|
|
}
|
|
|
|
rendering_material.set_depth_bias(0);
|
|
|
|
vec3d view_pos = scene->get_view_position();
|
|
|
|
if ( model_render_check_detail_box(&view_pos, pm, submodel_num, flags) ) {
|
|
model_render_buffers(scene, &rendering_material, render_info, &pm->submodel[submodel_num].buffer, pm, submodel_num, 0, tmap_flags);
|
|
|
|
if ( pm->flags & PM_FLAG_TRANS_BUFFER && pm->submodel[submodel_num].trans_buffer.flags & VB_FLAG_TRANS ) {
|
|
model_render_buffers(scene, &rendering_material, render_info, &pm->submodel[submodel_num].trans_buffer, pm, submodel_num, 0, tmap_flags);
|
|
}
|
|
}
|
|
|
|
if ( pmi && !pmi->submodel[submodel_num].electrical_arcs.empty() ) {
|
|
model_render_add_lightning( scene, render_info, pm, &pmi->submodel[submodel_num] );
|
|
}
|
|
|
|
if ( set_autocen ) {
|
|
scene->pop_transform();
|
|
}
|
|
|
|
scene->pop_transform();
|
|
}
|
|
|
|
//Renders the sprite for a glowpoint.
|
|
void model_render_glowpoint_bitmap(int point_num, const vec3d *pos, const matrix *orient, const glow_point_bank *bank, const glow_point_bank_override *gpo, const polymodel *pm, const polymodel_instance *pmi, const ship* shipp, bool use_depth_buffer)
|
|
{
|
|
glow_point *gpt = &bank->points[point_num];
|
|
vec3d loc_offset = gpt->pnt;
|
|
vec3d loc_norm = gpt->norm;
|
|
vec3d world_pnt;
|
|
vec3d world_norm;
|
|
vec3d tempv;
|
|
vec3d submodel_static_offset; // The associated submodel's static offset in the ship's frame of reference
|
|
bool submodel_rotation = false;
|
|
|
|
if ( bank->submodel_parent > 0 && pm->submodel[bank->submodel_parent].flags[Model::Submodel_flags::Can_move] ) {
|
|
model_find_submodel_offset(&submodel_static_offset, pm, bank->submodel_parent);
|
|
|
|
submodel_rotation = true;
|
|
}
|
|
|
|
if ( submodel_rotation ) {
|
|
vm_vec_sub(&loc_offset, &gpt->pnt, &submodel_static_offset);
|
|
|
|
tempv = loc_offset;
|
|
if (pmi) {
|
|
if (IS_VEC_NULL(&loc_norm)) { // zero vectors are allowed for glowpoint norms
|
|
model_instance_local_to_global_point(&loc_offset, &tempv, pm, pmi, bank->submodel_parent);
|
|
}
|
|
else {
|
|
vec3d tempn = loc_norm;
|
|
model_instance_local_to_global_point_dir(&loc_offset, &loc_norm, &tempv, &tempn, pm, pmi, bank->submodel_parent);
|
|
}
|
|
}
|
|
}
|
|
|
|
vm_vec_unrotate(&world_pnt, &loc_offset, orient);
|
|
vm_vec_add2(&world_pnt, pos);
|
|
|
|
vm_vec_unrotate(&world_norm, &loc_norm, orient);
|
|
|
|
if (shipp != nullptr) {
|
|
// don't render if its on the wrong side of the portal
|
|
WarpEffect* warp_effect = nullptr;
|
|
|
|
if ((shipp->is_arriving()) && (shipp->warpin_effect != nullptr)
|
|
&& Warp_params[shipp->warpin_params_index].warp_type != WT_HYPERSPACE) {
|
|
warp_effect = shipp->warpin_effect.get();
|
|
}
|
|
else if ((shipp->flags[Ship::Ship_Flags::Depart_warp]) && (shipp->warpout_effect != nullptr)
|
|
&& Warp_params[shipp->warpout_params_index].warp_type != WT_HYPERSPACE) {
|
|
warp_effect = shipp->warpout_effect.get();
|
|
}
|
|
|
|
if (warp_effect != nullptr && point_is_clipped_by_warp(&world_pnt, warp_effect))
|
|
return;
|
|
}
|
|
|
|
switch ((gpo && gpo->type_override)?gpo->type:bank->type)
|
|
{
|
|
case 0:
|
|
{
|
|
float d = 1.0f;
|
|
float pulse = model_render_get_point_activation(bank, gpo);
|
|
if (pulse == 0.0f)
|
|
return;
|
|
|
|
if ( IS_VEC_NULL(&world_norm) ) {
|
|
d = 1.0f; //if given a nul vector then always show it
|
|
} else {
|
|
vm_vec_sub(&tempv,&View_position,&world_pnt);
|
|
vm_vec_normalize(&tempv);
|
|
|
|
d = vm_vec_dot(&tempv,&world_norm);
|
|
d -= 0.25;
|
|
}
|
|
|
|
float w = gpt->radius;
|
|
if (d > 0.0f) {
|
|
vertex p;
|
|
|
|
d *= 3.0f;
|
|
|
|
if (d > 1.0f)
|
|
d = 1.0f;
|
|
|
|
|
|
// fade them in the nebula as well
|
|
nebula_handle_alpha(d, &world_pnt, Neb2_fog_visibility_glowpoint);
|
|
if (The_mission.flags[Mission::Mission_Flags::Fullneb]) {
|
|
//vec3d npnt;
|
|
//vm_vec_add(&npnt, &loc_offset, pos);
|
|
|
|
w *= 1.5; //make it bigger in a nebula (but fullneb only)
|
|
}
|
|
|
|
g3_transfer_vertex(&p, &world_pnt);
|
|
|
|
p.r = p.g = p.b = p.a = (ubyte)(255.0f * MAX(d,0.0f));
|
|
|
|
if((gpo && gpo->glow_bitmap_override)?(gpo->glow_bitmap > -1):(bank->glow_bitmap > -1)) {
|
|
int bitmap_id = (gpo && gpo->glow_bitmap_override) ? gpo->glow_bitmap : bank->glow_bitmap;
|
|
|
|
if ( use_depth_buffer ) {
|
|
batching_add_volume_bitmap(bitmap_id, &p, 0, (w * 0.5f), d * pulse);
|
|
} else {
|
|
batching_add_bitmap(bitmap_id, &p, 0, (w * 0.5f), d * pulse);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 1:
|
|
{
|
|
vertex verts[4];
|
|
vec3d fvec, top1, bottom1, top2, bottom2, start, end;
|
|
|
|
vm_vec_add2(&loc_norm, &loc_offset);
|
|
|
|
vm_vec_rotate(&start, &loc_offset, orient);
|
|
vm_vec_rotate(&end, &loc_norm, orient);
|
|
vm_vec_sub(&fvec, &end, &start);
|
|
|
|
vm_vec_normalize(&fvec);
|
|
|
|
moldel_calc_facing_pts(&top1, &bottom1, &fvec, &loc_offset, gpt->radius, 1.0f, &View_position);
|
|
moldel_calc_facing_pts(&top2, &bottom2, &fvec, &loc_norm, gpt->radius, 1.0f, &View_position);
|
|
|
|
g3_transfer_vertex(&verts[0], &bottom1);
|
|
g3_transfer_vertex(&verts[1], &bottom2);
|
|
g3_transfer_vertex(&verts[2], &top2);
|
|
g3_transfer_vertex(&verts[3], &top1);
|
|
|
|
verts[0].texture_position.u = 0.0f;
|
|
verts[0].texture_position.v = 0.0f;
|
|
|
|
verts[1].texture_position.u = 1.0f;
|
|
verts[1].texture_position.v = 0.0f;
|
|
|
|
verts[2].texture_position.u = 1.0f;
|
|
verts[2].texture_position.v = 1.0f;
|
|
|
|
verts[3].texture_position.u = 0.0f;
|
|
verts[3].texture_position.v = 1.0f;
|
|
|
|
vm_vec_sub(&tempv,&View_position,&loc_offset);
|
|
vm_vec_normalize(&tempv);
|
|
|
|
if ( The_mission.flags[Mission::Mission_Flags::Fullneb] ) {
|
|
batching_add_quad(bank->glow_neb_bitmap, verts);
|
|
} else {
|
|
batching_add_quad(bank->glow_bitmap, verts);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//adds the glowpoint's lights, if any, to the lights vector.
|
|
void model_render_glowpoint_add_light(int point_num, const vec3d *pos, const matrix *orient, const glow_point_bank *bank, const glow_point_bank_override *gpo, const polymodel *pm, const polymodel_instance *pmi, const ship* shipp)
|
|
{
|
|
if(Detail.lighting <= 3 || !Deferred_lighting || gpo==nullptr || !gpo->is_lightsource) {
|
|
return;
|
|
}
|
|
glow_point *gpt = &bank->points[point_num];
|
|
vec3d loc_offset = gpt->pnt;
|
|
vec3d loc_norm = gpt->norm;
|
|
vec3d world_pnt;
|
|
vec3d world_norm;
|
|
vec3d tempv;
|
|
vec3d submodel_static_offset; // The associated submodel's static offset in the ship's frame of reference
|
|
bool submodel_rotation = false;
|
|
|
|
if ( bank->submodel_parent > 0 && pm->submodel[bank->submodel_parent].flags[Model::Submodel_flags::Can_move] && shipp != nullptr ) {
|
|
model_find_submodel_offset(&submodel_static_offset, pm, bank->submodel_parent);
|
|
|
|
submodel_rotation = true;
|
|
}
|
|
|
|
if ( submodel_rotation ) {
|
|
vm_vec_sub(&loc_offset, &gpt->pnt, &submodel_static_offset);
|
|
|
|
tempv = loc_offset;
|
|
|
|
if (!pmi){
|
|
model_local_to_global_point(&loc_offset, &tempv, pm, bank->submodel_parent);
|
|
} else if (IS_VEC_NULL(&loc_norm)) { // zero vectors are allowed for glowpoint norms
|
|
model_instance_local_to_global_point(&loc_offset, &tempv, pm, pmi, bank->submodel_parent);
|
|
} else {
|
|
vec3d tempn = loc_norm;
|
|
model_instance_local_to_global_point_dir(&loc_offset, &loc_norm, &tempv, &tempn, pm, pmi, bank->submodel_parent);
|
|
}
|
|
}
|
|
|
|
vm_vec_unrotate(&world_pnt, &loc_offset, orient);
|
|
vm_vec_add2(&world_pnt, pos);
|
|
|
|
vm_vec_unrotate(&world_norm, &loc_norm, orient);
|
|
|
|
if (shipp != nullptr && shipp->warpout_effect != nullptr) {
|
|
// don't render if its on the wrong side of the portal
|
|
WarpEffect* warp_effect = nullptr;
|
|
|
|
if ((shipp->is_arriving()) && Warp_params[shipp->warpin_params_index].warp_type != WT_HYPERSPACE) {
|
|
warp_effect = shipp->warpin_effect.get();
|
|
}
|
|
else if ((shipp->flags[Ship::Ship_Flags::Depart_warp]) && Warp_params[shipp->warpout_params_index].warp_type != WT_HYPERSPACE) {
|
|
warp_effect = shipp->warpout_effect.get();
|
|
}
|
|
|
|
if (warp_effect != nullptr && point_is_clipped_by_warp(&world_pnt, warp_effect))
|
|
return;
|
|
}
|
|
|
|
if( (gpo->type_override?gpo->type:bank->type)==0)
|
|
{
|
|
float pulse = model_render_get_point_activation(bank, gpo);
|
|
if (pulse == 0.0f)
|
|
return;
|
|
vec3d lightcolor;
|
|
//fade between mix and normal color depending on pulse state
|
|
lightcolor.xyz.x =pulse * gpo->light_color.xyz.x + (1.0f-pulse) * gpo->light_mix_color.xyz.x;
|
|
lightcolor.xyz.y =pulse * gpo->light_color.xyz.y + (1.0f-pulse) * gpo->light_mix_color.xyz.y;
|
|
lightcolor.xyz.z =pulse * gpo->light_color.xyz.z + (1.0f-pulse) * gpo->light_mix_color.xyz.z;
|
|
float light_radius = gpt->radius * gpo->radius_multi;
|
|
if ( gpo->lightcone ) {
|
|
vec3d cone_dir_rot;
|
|
vec3d cone_dir_model;
|
|
vec3d cone_dir_world;
|
|
vec3d cone_dir_screen;
|
|
|
|
if ( gpo->rotating ) {
|
|
vm_rot_point_around_line(&cone_dir_rot, &gpo->cone_direction, PI * timestamp() * 0.000033333f * gpo->rotation_speed, &vmd_zero_vector, &gpo->rotation_axis);
|
|
} else {
|
|
cone_dir_rot = gpo->cone_direction;
|
|
}
|
|
|
|
if (pmi)
|
|
model_instance_local_to_global_dir(&cone_dir_model, &cone_dir_rot, pm, pmi, bank->submodel_parent);
|
|
else
|
|
cone_dir_model = cone_dir_rot;
|
|
|
|
vm_vec_unrotate(&cone_dir_world, &cone_dir_model, orient);
|
|
vm_vec_rotate(&cone_dir_screen, &cone_dir_world, &Eye_matrix);
|
|
cone_dir_screen.xyz.z = -cone_dir_screen.xyz.z;
|
|
light_add_cone(
|
|
&world_pnt, &cone_dir_screen, gpo->cone_angle, gpo->cone_inner_angle, gpo->dualcone, 1.0f, light_radius, gpo->intensity,
|
|
lightcolor.xyz.x, lightcolor.xyz.y, lightcolor.xyz.z, gpt->radius);
|
|
} else {
|
|
light_add_point(&world_pnt, 1.0f, light_radius, gpo->intensity, lightcolor.xyz.x, lightcolor.xyz.y, lightcolor.xyz.z, gpt->radius);
|
|
}
|
|
}
|
|
}
|
|
|
|
//Returns the current brightness percentage of a glowpoint, from 1.0f to 0.0f
|
|
float model_render_get_point_activation(const glow_point_bank* bank, const glow_point_bank_override* gpo)
|
|
{
|
|
if(gpo == nullptr || !(gpo->pulse_type)){
|
|
return 1.0f;
|
|
}
|
|
|
|
float pulse = 1.0f;
|
|
int period = 0;
|
|
|
|
if (gpo->pulse_period_override) {
|
|
period = gpo->pulse_period;
|
|
} else {
|
|
if (gpo->on_time_override) {
|
|
period = 2 * gpo->on_time;
|
|
} else {
|
|
period = 2 * bank->on_time;
|
|
}
|
|
}
|
|
|
|
if (period == 0)
|
|
return 0.0f;
|
|
|
|
int x = 0;
|
|
int disp_time = gpo->disp_time_override ? gpo->disp_time : bank->disp_time;
|
|
int on_time = gpo->on_time_override ? gpo->on_time : bank->on_time;
|
|
int off_time = gpo->off_time_override ? gpo->off_time : bank->off_time;
|
|
|
|
if ( off_time) {
|
|
x = (timestamp() - disp_time) % ( on_time + off_time ) - off_time;
|
|
} else {
|
|
x = (timestamp() - disp_time) % period;
|
|
}
|
|
|
|
switch (gpo->pulse_type) {
|
|
|
|
case PULSE_SIN:
|
|
pulse = gpo->pulse_bias + gpo->pulse_amplitude * pow(sin(PI2 / period * x),gpo->pulse_exponent);
|
|
break;
|
|
|
|
case PULSE_COS:
|
|
pulse = gpo->pulse_bias + gpo->pulse_amplitude * pow(cos(PI2 / period * x),gpo->pulse_exponent);
|
|
break;
|
|
|
|
case PULSE_SHIFTTRI:
|
|
x += period / 4;
|
|
if(off_time) {
|
|
x %= on_time + off_time;
|
|
} else {
|
|
x %= period;
|
|
}
|
|
FALLTHROUGH;
|
|
|
|
case PULSE_TRI:
|
|
float inv;
|
|
if( x > period / 2) {
|
|
inv = -1;
|
|
} else {
|
|
inv = 1;
|
|
}
|
|
if( x > period / 4) {
|
|
pulse = gpo->pulse_bias + gpo->pulse_amplitude * inv * pow( 1.0f - ((x - period / 4.0f) * 4 / period) ,gpo->pulse_exponent);
|
|
} else {
|
|
pulse = gpo->pulse_bias + gpo->pulse_amplitude * inv * pow( (x * 4.0f / period) ,gpo->pulse_exponent);
|
|
}
|
|
break;
|
|
}
|
|
return pulse;
|
|
}
|
|
|
|
void model_render_set_glow_points(const polymodel *pm, int objnum)
|
|
{
|
|
int time = timestamp();
|
|
glow_point_bank_override *gpo = nullptr;
|
|
bool override_all = false;
|
|
ship_info *sip = nullptr;
|
|
ship *shipp = nullptr;
|
|
|
|
if ( Glowpoint_override ) {
|
|
return;
|
|
}
|
|
|
|
if ( objnum > -1 ) {
|
|
object *objp = &Objects[objnum];
|
|
|
|
if ( objp != NULL && objp->type == OBJ_SHIP ) {
|
|
shipp = &Ships[Objects[objnum].instance];
|
|
sip = &Ship_info[shipp->ship_info_index];
|
|
auto gpoi = sip->glowpoint_bank_override_map.find(-1);
|
|
|
|
if (gpoi != sip->glowpoint_bank_override_map.end()) {
|
|
override_all = true;
|
|
gpo = &glowpoint_bank_overrides[gpoi->second];
|
|
}
|
|
}
|
|
}
|
|
|
|
for ( int i = 0; i < pm->n_glow_point_banks; i++ ) { //glow point blink code -Bobboau
|
|
glow_point_bank *bank = &pm->glow_point_banks[i];
|
|
|
|
if ( !override_all && sip ) {
|
|
auto gpoi = sip->glowpoint_bank_override_map.find(i);
|
|
|
|
if ( gpoi != sip->glowpoint_bank_override_map.end() ) {
|
|
gpo = &glowpoint_bank_overrides[gpoi->second];
|
|
} else {
|
|
gpo = nullptr;
|
|
}
|
|
}
|
|
|
|
if ( bank->glow_timestamp == 0 ) {
|
|
bank->glow_timestamp=time;
|
|
}
|
|
|
|
int on_time = (gpo && gpo->on_time_override) ? gpo->on_time : bank->on_time;
|
|
int off_time = (gpo && gpo->off_time_override) ? gpo->off_time : bank->off_time;
|
|
int disp_time = (gpo && gpo->disp_time_override) ? gpo->disp_time : bank->disp_time;
|
|
|
|
|
|
if (off_time) {
|
|
bool glow_state = ((time - disp_time) % (on_time + off_time)) < on_time;
|
|
|
|
if ( glow_state != bank->is_on )
|
|
bank->glow_timestamp = time;
|
|
|
|
bank->is_on = glow_state;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Handle all the glow points of a model being rendered.
|
|
void model_render_glow_points(const polymodel *pm, const polymodel_instance *pmi, const ship *shipp, const matrix *orient, const vec3d *pos, bool use_depth_buffer = true, bool render_sprites = true, bool add_lights= true)
|
|
{
|
|
Assert(pmi == nullptr || pm->id == pmi->model_num);
|
|
|
|
int i, j;
|
|
|
|
int cull = gr_set_cull(0);
|
|
|
|
glow_point_bank_override *gpo = nullptr;
|
|
bool override_all = false;
|
|
ship_info *sip = nullptr;
|
|
|
|
if ( shipp ) {
|
|
sip = &Ship_info[shipp->ship_info_index];
|
|
auto gpoi = sip->glowpoint_bank_override_map.find(-1);
|
|
|
|
if(gpoi != sip->glowpoint_bank_override_map.end()) {
|
|
override_all = true;
|
|
gpo = &glowpoint_bank_overrides[gpoi->second];
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < pm->n_glow_point_banks; i++ ) {
|
|
glow_point_bank *bank = &pm->glow_point_banks[i];
|
|
|
|
if(!override_all && sip) {
|
|
auto gpoi = sip->glowpoint_bank_override_map.find(i);
|
|
if(gpoi != sip->glowpoint_bank_override_map.end()) {
|
|
gpo = &glowpoint_bank_overrides[gpoi->second];
|
|
} else {
|
|
gpo = nullptr;
|
|
}
|
|
}
|
|
|
|
//Only continue if there actually is a glowpoint bitmap available
|
|
if (bank->glow_bitmap == -1)
|
|
continue;
|
|
|
|
if ((pmi != nullptr) && (bank->submodel_parent > -1)) {
|
|
auto smi = &pmi->submodel[bank->submodel_parent];
|
|
if (smi->blown_off) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ((gpo && gpo->off_time_override && !gpo->off_time)?gpo->is_on:bank->is_on) {
|
|
if ( (shipp != nullptr) && !(shipp->glow_point_bank_active[i]) )
|
|
continue;
|
|
|
|
for (j = 0; j < bank->num_points; j++) {
|
|
Assert( bank->points != nullptr );
|
|
int flick;
|
|
|
|
if (pmi != nullptr && !pmi->submodel[pm->detail[0]].electrical_arcs.empty()) {
|
|
flick = static_rand( timestamp() % 20 ) % (pmi->submodel[pm->detail[0]].electrical_arcs.size() + j); //the more damage, the more arcs, the more likely the lights will fail
|
|
} else {
|
|
flick = 1;
|
|
}
|
|
|
|
if (flick == 1) {
|
|
//In some cases we want to be able to render only the bitmaps or only the lights
|
|
if(render_sprites)
|
|
model_render_glowpoint_bitmap(j, pos, orient, bank, gpo, pm, pmi, shipp, use_depth_buffer);
|
|
if(add_lights)
|
|
model_render_glowpoint_add_light(j, pos, orient, bank, gpo, pm, pmi, shipp);
|
|
} // flick
|
|
} // for slot
|
|
} // bank is on
|
|
} // for bank
|
|
|
|
gr_set_cull(cull);
|
|
}
|
|
|
|
// These scaling functions were adapted from Elecman's code.
|
|
// https://forum.unity.com/threads/this-script-gives-you-objects-screen-size-in-pixels.48966/#post-2107126
|
|
float convert_pixel_size_and_distance_to_diameter(float pixelsize, float distance, float field_of_view, int screen_width)
|
|
{
|
|
float diameter = (pixelsize * distance * tanf(field_of_view)) / (screen_width);
|
|
return diameter;
|
|
}
|
|
|
|
// These scaling functions were adapted from Elecman's code.
|
|
// https://forum.unity.com/threads/this-script-gives-you-objects-screen-size-in-pixels.48966/#post-2107126
|
|
float convert_distance_and_diameter_to_pixel_size(float distance, float diameter, float field_of_view, int screen_width)
|
|
{
|
|
float pixel_size = (diameter * screen_width) / (distance * tanf(field_of_view));
|
|
return pixel_size;
|
|
}
|
|
|
|
float model_render_get_diameter_clamped_to_min_pixel_size(const vec3d* pos, float diameter, float min_pixel_size)
|
|
{
|
|
// Don't do any scaling math if the pixel size is set to zero.
|
|
if (fl_near_zero(min_pixel_size))
|
|
return diameter;
|
|
|
|
float distance_to_eye = vm_vec_dist(&Eye_position, pos);
|
|
float current_pixel_size = convert_distance_and_diameter_to_pixel_size(
|
|
distance_to_eye,
|
|
diameter,
|
|
g3_get_hfov(Eye_fov),
|
|
gr_screen.max_w);
|
|
|
|
float scaled_diameter = diameter;
|
|
if (current_pixel_size < min_pixel_size) {
|
|
scaled_diameter = convert_pixel_size_and_distance_to_diameter(
|
|
min_pixel_size,
|
|
distance_to_eye,
|
|
g3_get_hfov(Eye_fov),
|
|
gr_screen.max_w);
|
|
}
|
|
|
|
return scaled_diameter;
|
|
}
|
|
|
|
void model_queue_render_thrusters(const model_render_params *interp, const polymodel *pm, int objnum, const ship *shipp, const matrix *orient, const vec3d *pos)
|
|
{
|
|
int i, j;
|
|
int n_q = 0;
|
|
size_t k;
|
|
vec3d norm, norm2, fvec, pnt, npnt;
|
|
thruster_bank *bank = nullptr;
|
|
polymodel_instance *pmi = nullptr;
|
|
vertex p;
|
|
bool do_render = false;
|
|
|
|
if ( pm == NULL ) {
|
|
Int3();
|
|
return;
|
|
}
|
|
|
|
if ( !(interp->get_model_flags() & MR_SHOW_THRUSTERS) ) {
|
|
return;
|
|
}
|
|
|
|
// get an initial count to figure out how man geo batchers we need allocated
|
|
for (i = 0; i < pm->n_thrusters; i++ ) {
|
|
bank = &pm->thrusters[i];
|
|
n_q += bank->num_points;
|
|
}
|
|
|
|
if (n_q <= 0) {
|
|
return;
|
|
}
|
|
|
|
const mst_info& thruster_info = interp->get_thruster_info();
|
|
|
|
// primary_thruster_batcher
|
|
if (thruster_info.primary_glow_bitmap >= 0) {
|
|
do_render = true;
|
|
}
|
|
|
|
// secondary_thruster_batcher
|
|
if (thruster_info.secondary_glow_bitmap >= 0) {
|
|
do_render = true;
|
|
}
|
|
|
|
// tertiary_thruster_batcher
|
|
if (thruster_info.tertiary_glow_bitmap >= 0) {
|
|
do_render = true;
|
|
}
|
|
|
|
if (do_render == false) {
|
|
return;
|
|
}
|
|
|
|
// this is used for the secondary thruster glows
|
|
// it only needs to be calculated once so I'm doing it here -Bobboau
|
|
norm.xyz.z = -1.0f;
|
|
norm.xyz.x = 1.0f;
|
|
norm.xyz.y = -1.0f;
|
|
norm.xyz.x *= thruster_info.rotvel.xyz.y/2;
|
|
norm.xyz.y *= thruster_info.rotvel.xyz.x/2;
|
|
vm_vec_normalize(&norm);
|
|
|
|
for (i = 0; i < pm->n_thrusters; i++ ) {
|
|
vec3d submodel_static_offset; // The associated submodel's static offset in the ship's frame of reference
|
|
bool submodel_rotation = false;
|
|
|
|
bank = &pm->thrusters[i];
|
|
|
|
// don't draw this thruster if the engine is destroyed or just not on
|
|
if ( !model_should_render_engine_glow(objnum, bank->obj_num) )
|
|
continue;
|
|
|
|
// If bank is attached to a submodel, prepare to account for rotations
|
|
//
|
|
// TODO: This won't work in the ship lab, because the lab code doesn't
|
|
// set the the necessary submodel instance info needed here. The second
|
|
// condition is thus a hack to disable the feature while in the lab, and
|
|
// can be removed if the lab is re-structured accordingly. -zookeeper
|
|
if ( bank->submodel_num > -1 && pm->submodel[bank->submodel_num].flags[Model::Submodel_flags::Can_move] && (gameseq_get_state_idx(GS_STATE_LAB) == -1) ) {
|
|
model_find_submodel_offset(&submodel_static_offset, pm, bank->submodel_num);
|
|
|
|
submodel_rotation = true;
|
|
}
|
|
|
|
for (j = 0; j < bank->num_points; j++) {
|
|
Assert( bank->points != NULL );
|
|
|
|
float d;
|
|
vec3d tempv;
|
|
glow_point *gpt = &bank->points[j];
|
|
vec3d loc_offset = gpt->pnt;
|
|
vec3d loc_norm = gpt->norm;
|
|
vec3d world_pnt = loc_offset;
|
|
vec3d world_norm;
|
|
|
|
if ( submodel_rotation ) {
|
|
vm_vec_sub(&loc_offset, &gpt->pnt, &submodel_static_offset);
|
|
|
|
if (pmi == nullptr)
|
|
pmi = model_get_instance(shipp->model_instance_num);
|
|
|
|
|
|
if (IS_VEC_NULL(&loc_norm)) { // zero vectors are allowed for glowpoint norms
|
|
model_instance_local_to_global_point(&world_pnt, &loc_offset, pm, pmi, bank->submodel_num);
|
|
} else {
|
|
vec3d tempn = loc_norm;
|
|
model_instance_local_to_global_point_dir(&world_pnt, &loc_norm, &loc_offset, &tempn, pm, pmi, bank->submodel_num);
|
|
}
|
|
}
|
|
|
|
tempv = world_pnt;
|
|
vm_vec_unrotate(&world_pnt, &tempv, orient);
|
|
vm_vec_add2(&world_pnt, pos);
|
|
|
|
if (shipp) {
|
|
// if ship is warping out, check position of the engine glow to the warp plane
|
|
WarpEffect* warp_effect = nullptr;
|
|
|
|
if ((shipp->is_arriving()) && (shipp->warpin_effect != nullptr)
|
|
&& Warp_params[shipp->warpin_params_index].warp_type != WT_HYPERSPACE) {
|
|
warp_effect = shipp->warpin_effect.get();
|
|
}
|
|
else if ((shipp->flags[Ship::Ship_Flags::Depart_warp]) && (shipp->warpout_effect != nullptr)
|
|
&& Warp_params[shipp->warpout_params_index].warp_type != WT_HYPERSPACE) {
|
|
warp_effect = shipp->warpout_effect.get();
|
|
}
|
|
|
|
if (warp_effect != nullptr && point_is_clipped_by_warp(&world_pnt, warp_effect))
|
|
continue;
|
|
}
|
|
|
|
vm_vec_sub(&tempv, &View_position, &world_pnt);
|
|
vm_vec_normalize(&tempv);
|
|
vm_vec_unrotate(&world_norm, &loc_norm, orient);
|
|
d = vm_vec_dot(&tempv, &world_norm);
|
|
|
|
// ADAM: Min throttle draws rad*MIN_SCALE, max uses max.
|
|
#define NOISE_SCALE 0.5f
|
|
#define MIN_SCALE 3.4f
|
|
#define MAX_SCALE 4.7f
|
|
|
|
float magnitude;
|
|
vec3d scale_vec = { { { 1.0f, 0.0f, 0.0f } } };
|
|
|
|
// normalize banks, in case of incredibly big normals
|
|
if ( !IS_VEC_NULL_SQ_SAFE(&loc_norm) )
|
|
vm_vec_copy_normalize(&scale_vec, &loc_norm);
|
|
|
|
// adjust for thrust
|
|
(scale_vec.xyz.x *= thruster_info.length.xyz.x) -= 0.1f;
|
|
(scale_vec.xyz.y *= thruster_info.length.xyz.y) -= 0.1f;
|
|
(scale_vec.xyz.z *= thruster_info.length.xyz.z) -= 0.1f;
|
|
|
|
// get magnitude, which we will use as the scaling reference
|
|
magnitude = vm_vec_normalize(&scale_vec);
|
|
|
|
// get absolute value
|
|
if (magnitude < 0.0f)
|
|
magnitude *= -1.0f;
|
|
|
|
float scale = magnitude * (MAX_SCALE - MIN_SCALE) + MIN_SCALE;
|
|
|
|
if (d > 0.0f){
|
|
// Make glow bitmap fade in/out quicker from sides.
|
|
d *= 3.0f;
|
|
|
|
if (d > 1.0f)
|
|
d = 1.0f;
|
|
}
|
|
|
|
float fog_int = 1.0f;
|
|
|
|
// fade them in the nebula as well
|
|
vm_vec_unrotate(&npnt, &gpt->pnt, orient);
|
|
vm_vec_add2(&npnt, pos);
|
|
nebula_handle_alpha(fog_int, &npnt, Neb2_fog_visibility_thruster);
|
|
d *= fog_int;
|
|
|
|
// Scale the thrusters so they always appears at least some configured amount of pixels wide.
|
|
float scaled_thruster_radius = model_render_get_diameter_clamped_to_min_pixel_size(
|
|
&world_pnt,
|
|
gpt->radius * 2.0f,
|
|
Min_pixel_size_thruster);
|
|
scaled_thruster_radius /= 2.0f;
|
|
|
|
float w = scaled_thruster_radius * (scale + thruster_info.glow_noise * NOISE_SCALE);
|
|
|
|
// these lines are used by the tertiary glows, thus we will need to project this all of the time
|
|
g3_transfer_vertex( &p, &world_pnt );
|
|
|
|
// these values are not used, but just appease the linters and future-proof
|
|
p.screen.xyw.x = 0.0f; p.screen.xyw.y = 0.0f; p.screen.xyw.w = 0.0f;
|
|
|
|
// start primary thruster glows
|
|
if ( (thruster_info.primary_glow_bitmap >= 0) && (d > 0.0f) ) {
|
|
p.r = p.g = p.b = p.a = (ubyte)(255.0f * d);
|
|
batching_add_volume_bitmap(thruster_info.primary_glow_bitmap, &p, 0, (w * 0.5f * thruster_info.glow_rad_factor), d, w * 0.325f);
|
|
}
|
|
|
|
// start tertiary thruster glows
|
|
if (thruster_info.tertiary_glow_bitmap >= 0) {
|
|
p.screen.xyw.w -= w;
|
|
p.r = p.g = p.b = p.a = (ubyte)(255.0f * fog_int);
|
|
batching_add_volume_bitmap_rotated(thruster_info.tertiary_glow_bitmap, &p, magnitude * 4, w * 0.6f * thruster_info.tertiary_glow_rad_factor, fog_int, -w*0.5f);
|
|
}
|
|
|
|
// begin secondary glows
|
|
if (thruster_info.secondary_glow_bitmap >= 0) {
|
|
pnt = world_pnt;
|
|
scale = magnitude * (MAX_SCALE - (MIN_SCALE / 2)) + (MIN_SCALE / 2);
|
|
vm_vec_unrotate(&world_norm, &norm, orient);
|
|
d = vm_vec_dot(&tempv, &world_norm);
|
|
d += 0.75f;
|
|
d *= 3.0f;
|
|
|
|
if (d > 1.0f)
|
|
d = 1.0f;
|
|
|
|
if (d > 0.0f) {
|
|
vm_vec_add(&norm2, &world_norm, &pnt);
|
|
vm_vec_sub(&fvec, &norm2, &pnt);
|
|
vm_vec_normalize(&fvec);
|
|
|
|
float wVal = gpt->radius * scale * 2;
|
|
|
|
vm_vec_scale_add(&norm2, &pnt, &fvec, wVal * 2 * thruster_info.glow_length_factor);
|
|
|
|
if (The_mission.flags[Mission::Mission_Flags::Fullneb]) {
|
|
vm_vec_add(&npnt, &pnt, pos);
|
|
}
|
|
d *= fog_int;
|
|
|
|
batching_add_beam(thruster_info.secondary_glow_bitmap, &pnt, &norm2, wVal*thruster_info.secondary_glow_rad_factor*0.5f, d);
|
|
|
|
if (Scene_framebuffer_in_frame && thruster_info.draw_distortion &&
|
|
Gr_framebuffer_effects[FramebufferEffects::Thrusters]) {
|
|
vm_vec_scale_add(&norm2, &pnt, &fvec, wVal * 2 * thruster_info.distortion_length_factor);
|
|
int dist_bitmap;
|
|
if (thruster_info.distortion_bitmap > 0) {
|
|
dist_bitmap = thruster_info.distortion_bitmap;
|
|
}
|
|
else {
|
|
dist_bitmap = thruster_info.secondary_glow_bitmap;
|
|
}
|
|
|
|
float mag = vm_vec_mag(&gpt->pnt);
|
|
mag -= (float)((int)mag);//Valathil - Get a fairly random but constant number to offset the distortion texture
|
|
|
|
batching_add_distortion_beam(dist_bitmap, &pnt, &norm2, wVal*thruster_info.distortion_rad_factor*0.5f, 1.0f, mag);
|
|
}
|
|
}
|
|
}
|
|
|
|
// begin particles
|
|
if (shipp) {
|
|
ship_info *sip = &Ship_info[shipp->ship_info_index];
|
|
thruster_particles *tp;
|
|
size_t num_particles = 0;
|
|
|
|
if (thruster_info.use_ab)
|
|
num_particles = sip->afterburner_thruster_particles.size();
|
|
else
|
|
num_particles = sip->normal_thruster_particles.size();
|
|
|
|
for (k = 0; k < num_particles; k++) {
|
|
if (thruster_info.use_ab)
|
|
tp = &sip->afterburner_thruster_particles[k];
|
|
else
|
|
tp = &sip->normal_thruster_particles[k];
|
|
|
|
auto source = particle::ParticleManager::get()->createSource(tp->particle_handle);
|
|
|
|
//This is a 180° flip around y matrix
|
|
matrix orientParticle = { { { { { { -1.0f, 0.0f, 0.0f } } }, { { { 0.0f, 1.0f, 0.0f } } }, { { { 0.0f, 0.0f, -1.0f } } } } } };
|
|
|
|
std::unique_ptr<EffectHost> host;
|
|
if (bank->submodel_num < 0)
|
|
host = std::make_unique<EffectHostObject>(&Objects[shipp->objnum], loc_offset, orientParticle);
|
|
else
|
|
host = std::make_unique<EffectHostSubmodel>(&Objects[shipp->objnum], bank->submodel_num, loc_offset, orientParticle);
|
|
source->setHost(std::move(host));
|
|
source->setTriggerRadius(gpt->radius);
|
|
source->setTriggerVelocity(vm_vec_mag_quick(&Objects[shipp->objnum].phys_info.desired_vel));
|
|
source->finishCreation();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void model_render_insignias(const insignia_draw_data *insignia_data)
|
|
{
|
|
auto pm = insignia_data->pm;
|
|
int detail_level = insignia_data->detail_level;
|
|
int bitmap_num = insignia_data->bitmap_num;
|
|
|
|
// if the model has no insignias, or we don't have a texture, then bail
|
|
if ( (pm->num_ins <= 0) || (bitmap_num < 0) )
|
|
return;
|
|
|
|
int idx, s_idx;
|
|
vertex vecs[3];
|
|
vec3d t1, t2, t3;
|
|
int i1, i2, i3;
|
|
|
|
material insignia_material;
|
|
insignia_material.set_depth_bias(1);
|
|
|
|
// set the proper texture
|
|
material_set_unlit(&insignia_material, bitmap_num, 0.65f, true, true);
|
|
|
|
if ( insignia_data->clip ) {
|
|
insignia_material.set_clip_plane(insignia_data->clip_normal, insignia_data->clip_position);
|
|
}
|
|
|
|
// otherwise render them
|
|
for(idx=0; idx<pm->num_ins; idx++){
|
|
// skip insignias not on our detail level
|
|
if(pm->ins[idx].detail_level != detail_level){
|
|
continue;
|
|
}
|
|
|
|
for(s_idx=0; s_idx<pm->ins[idx].num_faces; s_idx++){
|
|
// get vertex indices
|
|
i1 = pm->ins[idx].faces[s_idx][0];
|
|
i2 = pm->ins[idx].faces[s_idx][1];
|
|
i3 = pm->ins[idx].faces[s_idx][2];
|
|
|
|
// transform vecs and setup vertices
|
|
vm_vec_add(&t1, &pm->ins[idx].vecs[i1], &pm->ins[idx].offset);
|
|
vm_vec_add(&t2, &pm->ins[idx].vecs[i2], &pm->ins[idx].offset);
|
|
vm_vec_add(&t3, &pm->ins[idx].vecs[i3], &pm->ins[idx].offset);
|
|
|
|
g3_transfer_vertex(&vecs[0], &t1);
|
|
g3_transfer_vertex(&vecs[1], &t2);
|
|
g3_transfer_vertex(&vecs[2], &t3);
|
|
|
|
// setup texture coords
|
|
vecs[0].texture_position.u = pm->ins[idx].u[s_idx][0];
|
|
vecs[0].texture_position.v = pm->ins[idx].v[s_idx][0];
|
|
|
|
vecs[1].texture_position.u = pm->ins[idx].u[s_idx][1];
|
|
vecs[1].texture_position.v = pm->ins[idx].v[s_idx][1];
|
|
|
|
vecs[2].texture_position.u = pm->ins[idx].u[s_idx][2];
|
|
vecs[2].texture_position.v = pm->ins[idx].v[s_idx][2];
|
|
|
|
light_apply_rgb( &vecs[0].r, &vecs[0].g, &vecs[0].b, &pm->ins[idx].vecs[i1], &pm->ins[idx].norm[i1], 1.5f );
|
|
light_apply_rgb( &vecs[1].r, &vecs[1].g, &vecs[1].b, &pm->ins[idx].vecs[i2], &pm->ins[idx].norm[i2], 1.5f );
|
|
light_apply_rgb( &vecs[2].r, &vecs[2].g, &vecs[2].b, &pm->ins[idx].vecs[i3], &pm->ins[idx].norm[i3], 1.5f );
|
|
vecs[0].a = vecs[1].a = vecs[2].a = 255;
|
|
|
|
// draw the polygon
|
|
g3_render_primitives_colored_textured(&insignia_material, vecs, 3, PRIM_TYPE_TRIFAN, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
SCP_vector<vec3d> Arc_segment_points;
|
|
|
|
void model_render_arc(const vec3d *v1, const vec3d *v2, const SCP_vector<vec3d> *persistent_arc_points, const color *primary, const color *secondary, float arc_width, ubyte depth_limit)
|
|
{
|
|
int size;
|
|
const vec3d *pvecs;
|
|
|
|
if (persistent_arc_points) {
|
|
size = static_cast<int>(persistent_arc_points->size());
|
|
pvecs = persistent_arc_points->data();
|
|
} else {
|
|
Arc_segment_points.clear();
|
|
|
|
// need to add the first point
|
|
Arc_segment_points.push_back(*v1);
|
|
|
|
// this should fill in all of the middle, and the last, points
|
|
interp_generate_arc_segment(Arc_segment_points, v1, v2, depth_limit, 0);
|
|
|
|
size = static_cast<int>(Arc_segment_points.size());
|
|
pvecs = Arc_segment_points.data();
|
|
}
|
|
|
|
// use primary color for fist pass
|
|
Assert( primary );
|
|
|
|
g3_render_rod(primary, size, pvecs, arc_width);
|
|
|
|
if (secondary) {
|
|
g3_render_rod(secondary, size, pvecs, arc_width * 0.33f);
|
|
}
|
|
}
|
|
|
|
void model_render_debug_children(const polymodel *pm, int mn, int detail_level, uint64_t debug_flags)
|
|
{
|
|
int i;
|
|
|
|
if ( (mn < 0) || (mn >= pm->n_models) ) {
|
|
Int3();
|
|
return;
|
|
}
|
|
|
|
bsp_info *model = &pm->submodel[mn];
|
|
|
|
// Get submodel rotation data and use submodel orientation matrix
|
|
// to put together a matrix describing the final orientation of
|
|
// the submodel relative to its parent
|
|
// (Not needed here because we're not using model instances)
|
|
matrix submodel_orient = vmd_identity_matrix;
|
|
|
|
g3_start_instance_matrix(&model->offset, &submodel_orient, true);
|
|
|
|
if ( debug_flags & MR_DEBUG_PIVOTS ) {
|
|
model_draw_debug_points( pm, &pm->submodel[mn], debug_flags );
|
|
}
|
|
|
|
i = model->first_child;
|
|
|
|
while ( i >= 0 ) {
|
|
model_render_debug_children( pm, i, detail_level, debug_flags );
|
|
|
|
i = pm->submodel[i].next_sibling;
|
|
}
|
|
|
|
g3_done_instance(true);
|
|
}
|
|
|
|
void model_render_debug(int model_num, const matrix *orient, const vec3d *pos, uint64_t flags, uint debug_flags, int objnum, int detail_level_locked )
|
|
{
|
|
polymodel *pm = model_get(model_num);
|
|
|
|
g3_start_instance_matrix(pos, orient, true);
|
|
|
|
if ( debug_flags & MR_DEBUG_RADIUS ) {
|
|
if ( !( flags & MR_SHOW_OUTLINE_PRESET ) ) {
|
|
gr_set_color(0,64,0);
|
|
g3_draw_sphere_ez(&vmd_zero_vector,pm->rad);
|
|
}
|
|
}
|
|
float depth = model_render_determine_depth(objnum, model_num, orient, pos, detail_level_locked);
|
|
int detail_level = model_render_determine_detail(depth, model_num, detail_level_locked);
|
|
|
|
vec3d auto_back = ZERO_VECTOR;
|
|
bool set_autocen = model_render_determine_autocenter(&auto_back, pm, detail_level, flags);
|
|
|
|
if ( set_autocen ) {
|
|
g3_start_instance_matrix(&auto_back, NULL, true);
|
|
}
|
|
|
|
uint save_gr_zbuffering_mode = gr_zbuffer_set(GR_ZBUFF_READ);
|
|
|
|
int i = pm->submodel[pm->detail[detail_level]].first_child;
|
|
|
|
while ( i >= 0 ) {
|
|
model_render_debug_children( pm, i, detail_level, flags );
|
|
|
|
i = pm->submodel[i].next_sibling;
|
|
}
|
|
|
|
if ( debug_flags & MR_DEBUG_PIVOTS ) {
|
|
model_draw_debug_points( pm, NULL, flags );
|
|
model_draw_debug_points( pm, &pm->submodel[pm->detail[detail_level]], flags );
|
|
|
|
if ( pm->flags & PM_FLAG_AUTOCEN ) {
|
|
gr_set_color(255, 255, 255);
|
|
g3_draw_sphere_ez(&pm->autocenter, pm->rad / 4.5f);
|
|
}
|
|
}
|
|
|
|
if ( debug_flags & MR_DEBUG_SHIELDS ) {
|
|
model_render_shields(pm, flags);
|
|
}
|
|
|
|
if ( debug_flags & MR_DEBUG_PATHS ) {
|
|
model_draw_paths_htl(model_num, flags);
|
|
}
|
|
|
|
if ( debug_flags & MR_DEBUG_BAY_PATHS ) {
|
|
model_draw_bay_paths_htl(model_num);
|
|
}
|
|
|
|
if ( debug_flags & MR_DEBUG_DOCK_POINTS ) {
|
|
model_draw_dock_points_htl(model_num);
|
|
}
|
|
|
|
if ( (flags & MR_AUTOCENTER) && (set_autocen) ) {
|
|
g3_done_instance(true);
|
|
}
|
|
|
|
g3_done_instance(true);
|
|
|
|
gr_zbuffer_set(save_gr_zbuffering_mode);
|
|
}
|
|
|
|
void model_render_immediate(const model_render_params* render_info, int model_num, const matrix* orient, const vec3d* pos, int render, bool sort)
|
|
{
|
|
model_render_immediate(render_info, model_num, -1, orient, pos, render, sort);
|
|
}
|
|
|
|
void model_render_immediate(const model_render_params* render_info, int model_num, int model_instance_num, const matrix* orient, const vec3d* pos, int render, bool sort)
|
|
{
|
|
model_draw_list model_list;
|
|
|
|
model_list.init();
|
|
|
|
model_render_queue(render_info, &model_list, model_num, model_instance_num, orient, pos);
|
|
|
|
model_list.init_render(sort);
|
|
|
|
switch ( render ) {
|
|
case MODEL_RENDER_OPAQUE:
|
|
model_list.render_all(ZBUFFER_TYPE_FULL);
|
|
break;
|
|
case MODEL_RENDER_TRANS:
|
|
model_list.render_all(ZBUFFER_TYPE_READ);
|
|
model_list.render_all(ZBUFFER_TYPE_NONE);
|
|
break;
|
|
case MODEL_RENDER_ALL:
|
|
model_list.render_all();
|
|
break;
|
|
}
|
|
|
|
model_list.render_outlines();
|
|
model_list.render_insignias();
|
|
model_list.render_arcs();
|
|
|
|
gr_zbias(0);
|
|
gr_set_cull(0);
|
|
gr_zbuffer_set(ZBUFFER_TYPE_READ);
|
|
gr_set_fill_mode(GR_FILL_MODE_SOLID);
|
|
|
|
gr_clear_states();
|
|
|
|
gr_reset_lighting();
|
|
|
|
if ( render_info->get_debug_flags() ) {
|
|
model_render_debug(model_num, orient, pos, render_info->get_model_flags(), render_info->get_debug_flags(), render_info->get_object_number(), render_info->get_detail_level_lock());
|
|
}
|
|
}
|
|
|
|
void model_render_queue(const model_render_params* interp, model_draw_list* scene, int model_num, const matrix* orient, const vec3d* pos)
|
|
{
|
|
model_render_queue(interp, scene, model_num, -1, orient, pos);
|
|
}
|
|
|
|
void model_render_queue(const model_render_params* interp, model_draw_list* scene, int model_num, int model_instance_num, const matrix* orient, const vec3d* pos)
|
|
{
|
|
int i;
|
|
|
|
const int objnum = interp->get_object_number();
|
|
const uint64_t model_flags = interp->get_model_flags();
|
|
|
|
model_material rendering_material;
|
|
polymodel *pm = model_get(model_num);
|
|
polymodel_instance *pmi = NULL;
|
|
|
|
float light_factor = model_render_determine_light_factor(interp, pos, model_flags);
|
|
|
|
if ( light_factor < (1.0f/32.0f) ) {
|
|
// If it is too far, exit
|
|
return;
|
|
}
|
|
|
|
rendering_material.set_light_factor(light_factor);
|
|
|
|
if ( interp->is_clip_plane_set() ) {
|
|
rendering_material.set_clip_plane(interp->get_clip_plane_normal(), interp->get_clip_plane_pos());
|
|
}
|
|
|
|
if ( interp->is_team_color_set() ) {
|
|
rendering_material.set_team_color(interp->get_team_color());
|
|
}
|
|
|
|
if ( model_flags & MR_FORCE_CLAMP ) {
|
|
rendering_material.set_texture_addressing(TMAP_ADDRESS_CLAMP);
|
|
} else {
|
|
rendering_material.set_texture_addressing(TMAP_ADDRESS_WRAP);
|
|
}
|
|
|
|
model_render_set_glow_points(pm, objnum);
|
|
|
|
if ( !(model_flags & MR_NO_LIGHTING) ) {
|
|
scene->set_light_filter(pos, pm->rad);
|
|
}
|
|
|
|
ship *shipp = nullptr;
|
|
object *objp = nullptr;
|
|
|
|
if (objnum >= 0) {
|
|
objp = &Objects[objnum];
|
|
int tentative_num = -1;
|
|
|
|
if (objp->type == OBJ_SHIP) {
|
|
shipp = &Ships[objp->instance];
|
|
tentative_num = shipp->model_instance_num;
|
|
}
|
|
else {
|
|
tentative_num = object_get_model_instance_num(objp);
|
|
}
|
|
|
|
if (tentative_num >= 0) {
|
|
model_instance_num = tentative_num;
|
|
}
|
|
}
|
|
|
|
// is this a skybox with a rotating submodel?
|
|
if (model_num == Nmodel_num && Nmodel_instance_num >= 0) {
|
|
model_instance_num = Nmodel_instance_num;
|
|
}
|
|
|
|
if (model_instance_num >= 0) {
|
|
pmi = model_get_instance(model_instance_num);
|
|
|
|
// This can happen if we supply a HUD target model for a real ship.
|
|
// The passed parameter was -1 but it was assigned an instance from
|
|
// the actual object. Set it back to -1 if there is a mismatch.
|
|
if (pmi->model_num != pm->id) {
|
|
model_instance_num = -1;
|
|
pmi = nullptr;
|
|
}
|
|
}
|
|
|
|
// Set the flags we will pass to the tmapper
|
|
uint tmap_flags = TMAP_FLAG_GOURAUD | TMAP_FLAG_RGB;
|
|
|
|
if ( !(model_flags & MR_NO_TEXTURING) ) {
|
|
tmap_flags |= TMAP_FLAG_TEXTURED;
|
|
|
|
if ( (pm->flags & PM_FLAG_ALLOW_TILING) && tiling)
|
|
tmap_flags |= TMAP_FLAG_TILED;
|
|
|
|
if ( !(model_flags & MR_NO_CORRECT) ) {
|
|
tmap_flags |= TMAP_FLAG_CORRECT;
|
|
}
|
|
}
|
|
|
|
if ( model_flags & MR_DESATURATED ) {
|
|
rendering_material.set_desaturation(true);
|
|
}
|
|
|
|
if ( interp->get_animated_effect_num() >= 0 ) {
|
|
rendering_material.set_animated_effect(interp->get_animated_effect_num(), interp->get_animated_effect_timer());
|
|
}
|
|
|
|
bool is_outlines_only = (model_flags & MR_NO_POLYS) && ((model_flags & MR_SHOW_OUTLINE_PRESET) || (model_flags & MR_SHOW_OUTLINE));
|
|
bool is_outlines_only_htl = (model_flags & MR_NO_POLYS) && (model_flags & MR_SHOW_OUTLINE_HTL);
|
|
|
|
float depth;
|
|
if (interp->get_attached_model_draw_distance() >= 0.0f) {
|
|
// this is an attached weapon model, rendered inside its parent's pushed transform, so pos
|
|
// is in the parent's frame; measure the depth from the eye position in that same frame
|
|
vec3d local_eye = scene->get_view_position();
|
|
depth = model_render_determine_depth(objnum, model_num, orient, pos, interp->get_detail_level_lock(), &local_eye);
|
|
|
|
// don't render weapon models beyond the ship's tabled Weapon Model Draw Distance (which defaults to 200)
|
|
if (depth > interp->get_attached_model_draw_distance()) {
|
|
return;
|
|
}
|
|
} else {
|
|
depth = model_render_determine_depth(objnum, model_num, orient, pos, interp->get_detail_level_lock());
|
|
}
|
|
|
|
scene->push_transform(pos, orient);
|
|
|
|
int detail_level = model_render_determine_detail(depth, model_num, interp->get_detail_level_lock());
|
|
|
|
// Send the detail level to the lab for displaying
|
|
if (gameseq_get_state() == GS_STATE_LAB) {
|
|
Lab_object_detail_level = detail_level;
|
|
}
|
|
|
|
// #ifndef NDEBUG
|
|
// if ( Interp_detail_level == 0 ) {
|
|
// MONITOR_INC( NumHiModelsRend, 1 );
|
|
// } else if ( Interp_detail_level == pm->n_detail_levels-1 ) {
|
|
// MONITOR_INC( NumLowModelsRend, 1 );
|
|
// } else {
|
|
// MONITOR_INC( NumMedModelsRend, 1 );
|
|
// }
|
|
// #endif
|
|
|
|
vec3d auto_back = ZERO_VECTOR;
|
|
bool set_autocen = model_render_determine_autocenter(&auto_back, pm, detail_level, model_flags);
|
|
|
|
if ( set_autocen ) {
|
|
scene->push_transform(&auto_back, NULL);
|
|
}
|
|
|
|
// if we're in nebula mode, fog everything except for the warp holes and other non-fogged models
|
|
if ( (The_mission.flags[Mission::Mission_Flags::Fullneb]) && (Neb2_render_mode != NEB2_RENDER_NONE) && !(model_flags & MR_NO_FOGGING) ) {
|
|
unsigned char r, g, b;
|
|
neb2_get_fog_color(&r, &g, &b);
|
|
|
|
rendering_material.set_fog(r, g, b);
|
|
} else {
|
|
rendering_material.set_fog();
|
|
}
|
|
|
|
if (interp->is_alpha_mult_set()) {
|
|
rendering_material.set_alpha_mult(interp->get_alpha_mult());
|
|
}
|
|
|
|
if ( is_outlines_only_htl ) {
|
|
rendering_material.set_fill_mode(GR_FILL_MODE_WIRE);
|
|
|
|
tmap_flags &= ~TMAP_FLAG_RGB;
|
|
} else {
|
|
rendering_material.set_fill_mode(GR_FILL_MODE_SOLID);
|
|
}
|
|
|
|
color clr = interp->get_color();
|
|
rendering_material.set_color(clr);
|
|
|
|
if ( model_flags & MR_EDGE_ALPHA ) {
|
|
rendering_material.set_center_alpha(-1);
|
|
} else if ( model_flags & MR_CENTER_ALPHA ) {
|
|
rendering_material.set_center_alpha(1);
|
|
} else {
|
|
rendering_material.set_center_alpha(0);
|
|
}
|
|
|
|
if ( interp->uses_thick_outlines() ) {
|
|
rendering_material.set_outline_thickness(interp->get_outline_thickness());
|
|
}
|
|
|
|
if ( ( model_flags & MR_NO_CULL ) || ( model_flags & MR_ALL_XPARENT ) || ( interp->get_warp_bitmap() >= 0 ) ) {
|
|
rendering_material.set_cull_mode(false);
|
|
} else {
|
|
rendering_material.set_cull_mode(true);
|
|
}
|
|
|
|
if ( !(model_flags & MR_NO_LIGHTING) ) {
|
|
rendering_material.set_lighting(true);
|
|
} else {
|
|
rendering_material.set_lighting(false);
|
|
}
|
|
|
|
Assertion(!(model_flags & MR_STENCIL_READ && model_flags & MR_STENCIL_WRITE),
|
|
"Enabling stencil read and write at the same time is not supported!");
|
|
|
|
if (model_flags & MR_STENCIL_READ) {
|
|
rendering_material.set_stencil_test(true);
|
|
rendering_material.set_stencil_func(ComparisionFunction::NotEqual, 1, 0xFFFF);
|
|
rendering_material.set_stencil_op(StencilOperation::Keep, StencilOperation::Keep, StencilOperation::Keep);
|
|
} else if (model_flags & MR_STENCIL_WRITE) {
|
|
rendering_material.set_stencil_test(true);
|
|
rendering_material.set_stencil_func(ComparisionFunction::Always, 1, 0xFFFF);
|
|
rendering_material.set_stencil_op(StencilOperation::Keep, StencilOperation::Keep, StencilOperation::Replace);
|
|
} else {
|
|
rendering_material.set_stencil_test(false);
|
|
rendering_material.set_stencil_func(ComparisionFunction::Never, 1, 0xFFFF);
|
|
rendering_material.set_stencil_op(StencilOperation::Keep, StencilOperation::Keep, StencilOperation::Keep);
|
|
}
|
|
|
|
if (model_flags & MR_NO_COLOR_WRITES) {
|
|
rendering_material.set_color_mask(false, false, false, false);
|
|
} else {
|
|
rendering_material.set_color_mask(true, true, true, true);
|
|
}
|
|
|
|
rendering_material.set_depth_bias(0);
|
|
|
|
if ( !(model_flags & MR_NO_BATCH) && pm->flags & PM_FLAG_BATCHED
|
|
&& !(is_outlines_only || is_outlines_only_htl) ) {
|
|
// always set batched rendering on if supported
|
|
tmap_flags |= TMAP_FLAG_BATCH_TRANSFORMS;
|
|
}
|
|
|
|
if ( (tmap_flags & TMAP_FLAG_BATCH_TRANSFORMS) ) {
|
|
scene->start_model_batch(pm->n_models);
|
|
model_render_buffers(scene, &rendering_material, interp, &pm->detail_buffers[detail_level], pm, -1, detail_level, tmap_flags);
|
|
}
|
|
|
|
// Draw the subobjects
|
|
bool draw_thrusters = false;
|
|
bool trans_buffer = false;
|
|
i = pm->submodel[pm->detail[detail_level]].first_child;
|
|
|
|
while( i >= 0 ) {
|
|
if ( !pm->submodel[i].flags[Model::Submodel_flags::Is_thruster] ) {
|
|
model_render_children_buffers( scene, &rendering_material, interp, pm, pmi, i, detail_level, tmap_flags, trans_buffer );
|
|
} else {
|
|
draw_thrusters = true;
|
|
}
|
|
|
|
i = pm->submodel[i].next_sibling;
|
|
}
|
|
|
|
model_radius = pm->submodel[pm->detail[detail_level]].rad;
|
|
|
|
//*************************** draw the hull of the ship *********************************************
|
|
vec3d view_pos = scene->get_view_position();
|
|
|
|
if ( model_render_check_detail_box(&view_pos, pm, pm->detail[detail_level], model_flags) ) {
|
|
int detail_model_num = pm->detail[detail_level];
|
|
|
|
if ( (is_outlines_only || is_outlines_only_htl) && pm->submodel[detail_model_num].outline_buffer != NULL ) {
|
|
color outline_color = interp->get_color();
|
|
scene->add_outline(pm->submodel[detail_model_num].outline_buffer.get(), pm->submodel[detail_model_num].n_verts_outline, &outline_color);
|
|
} else {
|
|
model_render_buffers(scene, &rendering_material, interp, &pm->submodel[detail_model_num].buffer, pm, detail_model_num, detail_level, tmap_flags);
|
|
|
|
if ( pmi != nullptr && !pmi->submodel[detail_model_num].electrical_arcs.empty() ) {
|
|
model_render_add_lightning( scene, interp, pm, &pmi->submodel[detail_model_num] );
|
|
}
|
|
}
|
|
}
|
|
|
|
// make sure batch rendering is unconditionally off.
|
|
tmap_flags &= ~TMAP_FLAG_BATCH_TRANSFORMS;
|
|
|
|
if ( pm->flags & PM_FLAG_TRANS_BUFFER && !(is_outlines_only || is_outlines_only_htl) ) {
|
|
trans_buffer = true;
|
|
i = pm->submodel[pm->detail[detail_level]].first_child;
|
|
|
|
while( i >= 0 ) {
|
|
if ( !pm->submodel[i].flags[Model::Submodel_flags::Is_thruster] ) {
|
|
model_render_children_buffers( scene, &rendering_material, interp, pm, pmi, i, detail_level, tmap_flags, trans_buffer );
|
|
}
|
|
|
|
i = pm->submodel[i].next_sibling;
|
|
}
|
|
|
|
view_pos = scene->get_view_position();
|
|
|
|
if ( model_render_check_detail_box(&view_pos, pm, pm->detail[detail_level], model_flags) ) {
|
|
int detail_model_num = pm->detail[detail_level];
|
|
model_render_buffers(scene, &rendering_material, interp, &pm->submodel[detail_model_num].trans_buffer, pm, detail_model_num, detail_level, tmap_flags);
|
|
}
|
|
}
|
|
|
|
// Draw the thruster subobjects
|
|
if ( draw_thrusters && !(is_outlines_only || is_outlines_only_htl) ) {
|
|
i = pm->submodel[pm->detail[detail_level]].first_child;
|
|
trans_buffer = false;
|
|
|
|
while( i >= 0 ) {
|
|
if (pm->submodel[i].flags[Model::Submodel_flags::Is_thruster]) {
|
|
model_render_children_buffers( scene, &rendering_material, interp, pm, pmi, i, detail_level, tmap_flags, trans_buffer );
|
|
}
|
|
i = pm->submodel[i].next_sibling;
|
|
}
|
|
}
|
|
|
|
// MARKED!
|
|
if ( !( model_flags & MR_NO_TEXTURING ) && !( model_flags & MR_NO_INSIGNIA) ) {
|
|
int bitmap_num = interp->get_insignia_bitmap();
|
|
if ( Render_insignias_as_decals && objnum >= 0 && (pm->num_ins > 0) && (bitmap_num >= 0) ) {
|
|
for (int ins_idx = 0; ins_idx < pm->num_ins; ins_idx++) {
|
|
const insignia& ins = pm->ins[ins_idx];
|
|
// skip insignias not on our detail level
|
|
if (ins.detail_level != detail_level) {
|
|
continue;
|
|
}
|
|
|
|
decals::Decal decal;
|
|
decal.object = &Objects[objnum];
|
|
decal.position = ins.position;
|
|
decal.submodel = -1;
|
|
decal.scale = vec3d{{{ins.diameter, ins.diameter, ins.diameter}}};
|
|
decal.orig_obj_type = OBJ_SHIP;
|
|
decal.creation_time = f2fl(Missiontime);
|
|
decal.lifetime = 1.0f;
|
|
decal.orientation = ins.orientation;
|
|
decal.definition_handle = std::make_tuple(bitmap_num, -1, -1);
|
|
decals::addSingleFrameDecal(std::move(decal));
|
|
}
|
|
} else {
|
|
scene->add_insignia(interp, pm, detail_level, bitmap_num);
|
|
}
|
|
}
|
|
|
|
if ( (model_flags & MR_AUTOCENTER) && (set_autocen) ) {
|
|
scene->pop_transform();
|
|
}
|
|
|
|
scene->pop_transform();
|
|
|
|
// start rendering glow points -Bobboau
|
|
if ( (pm->n_glow_point_banks) && !is_outlines_only && !is_outlines_only_htl && !Glowpoint_override ) {
|
|
model_render_glow_points(pm, pmi, shipp, orient, pos, Glowpoint_use_depth_buffer);
|
|
}
|
|
|
|
// Draw the thruster glow
|
|
if ( !is_outlines_only && !is_outlines_only_htl ) {
|
|
if ( ( model_flags & MR_AUTOCENTER ) && set_autocen ) {
|
|
vec3d autoback_rotated;
|
|
|
|
vm_vec_unrotate(&autoback_rotated, &auto_back, orient);
|
|
vm_vec_add2(&autoback_rotated, pos);
|
|
|
|
model_queue_render_thrusters( interp, pm, objnum, shipp, orient, &autoback_rotated );
|
|
} else {
|
|
model_queue_render_thrusters( interp, pm, objnum, shipp, orient, pos );
|
|
}
|
|
}
|
|
}
|
|
|
|
//Renders the glowpoint light sources of a model without rendering anything else of the model.
|
|
void model_render_only_glowpoint_lights(const model_render_params* interp, int model_num, int model_instance_num, const matrix* orient, const vec3d* pos)
|
|
{
|
|
const int objnum = interp->get_object_number();
|
|
const uint64_t model_flags = interp->get_model_flags();
|
|
|
|
polymodel *pm = model_get(model_num);
|
|
polymodel_instance *pmi = nullptr;
|
|
|
|
model_render_set_glow_points(pm, objnum);
|
|
|
|
ship *shipp = nullptr;
|
|
object *objp = nullptr;
|
|
|
|
if (objnum >= 0) {
|
|
objp = &Objects[objnum];
|
|
int tentative_num = -1;
|
|
|
|
// TODO: Add Prop support here
|
|
if (objp->type == OBJ_SHIP) {
|
|
shipp = &Ships[objp->instance];
|
|
tentative_num = shipp->model_instance_num;
|
|
}
|
|
else {
|
|
tentative_num = object_get_model_instance_num(objp);
|
|
}
|
|
|
|
if (tentative_num >= 0) {
|
|
model_instance_num = tentative_num;
|
|
}
|
|
}
|
|
|
|
// is this a skybox with a rotating submodel?
|
|
if (model_num == Nmodel_num && Nmodel_instance_num >= 0) {
|
|
model_instance_num = Nmodel_instance_num;
|
|
}
|
|
|
|
if (model_instance_num >= 0) {
|
|
pmi = model_get_instance(model_instance_num);
|
|
|
|
// This can happen if we supply a HUD target model for a real ship.
|
|
// The passed parameter was -1 but it was assigned an instance from
|
|
// the actual object. Set it back to -1 if there is a mismatch.
|
|
if (pmi->model_num != pm->id) {
|
|
model_instance_num = -1;
|
|
pmi = nullptr;
|
|
}
|
|
}
|
|
|
|
bool is_outlines_only = (model_flags & MR_NO_POLYS) && ((model_flags & MR_SHOW_OUTLINE_PRESET) || (model_flags & MR_SHOW_OUTLINE));
|
|
bool is_outlines_only_htl = (model_flags & MR_NO_POLYS) && (model_flags & MR_SHOW_OUTLINE_HTL);
|
|
|
|
// start rendering glow points -Bobboau
|
|
if ( (pm->n_glow_point_banks) && !is_outlines_only && !is_outlines_only_htl && !Glowpoint_override ) {
|
|
model_render_glow_points(pm, pmi, shipp, orient, pos, Glowpoint_use_depth_buffer,false,true);
|
|
}
|
|
}
|
|
|
|
void model_render_set_wireframe_color(const color* clr)
|
|
{
|
|
Wireframe_color = *clr;
|
|
}
|
|
|
|
void modelinstance_replace_active_texture(polymodel_instance* pmi, const char* old_name, const char* new_name)
|
|
{
|
|
Assert(pmi != nullptr);
|
|
if (pmi == nullptr)
|
|
return;
|
|
polymodel* pm = model_get(pmi->model_num);
|
|
|
|
int final_index = -1;
|
|
|
|
for (int i = 0; i < pm->n_textures; i++)
|
|
{
|
|
int tm_num = pm->maps[i].FindTexture(old_name);
|
|
if (tm_num > -1)
|
|
{
|
|
final_index = i * TM_NUM_TYPES + tm_num;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (final_index >= 0)
|
|
{
|
|
int texture;
|
|
|
|
if (!stricmp(new_name, "invisible"))
|
|
texture = REPLACE_WITH_INVISIBLE;
|
|
else
|
|
texture = bm_load_either(new_name);
|
|
|
|
if (pmi->texture_replace == nullptr) {
|
|
pmi->texture_replace = std::make_shared<model_texture_replace>();
|
|
}
|
|
|
|
(*pmi->texture_replace)[final_index] = texture;
|
|
} else
|
|
Warning(LOCATION, "Invalid texture '%s' used for replacement texture", old_name);
|
|
}
|
|
|
|
// renders a model as if in the tech room or briefing UI
|
|
// model_type 1 for ship class, 2 for weapon class, 3 for pof
|
|
bool render_tech_model(tech_render_type model_type, int x1, int y1, int x2, int y2, float zoom, bool lighting, int class_idx, const matrix* orient, const SCP_string &pof_filename, float close_zoom, const vec3d *close_pos, const SCP_string& tcolor, const SCP_vector<SCP_string>& destroyed_subsystems)
|
|
{
|
|
|
|
lighting_profiles::set_non_mission_profile non_mission_lighting_profile;
|
|
|
|
model_render_params render_info;
|
|
const vec3d *closeup_pos;
|
|
float closeup_zoom;
|
|
int model_num;
|
|
bool model_lighting = true;
|
|
uint64_t render_flags = MR_AUTOCENTER | MR_NO_FOGGING;
|
|
|
|
switch (model_type) {
|
|
case TECH_SHIP:
|
|
ship_info* sip;
|
|
sip = &Ship_info[class_idx];
|
|
|
|
closeup_pos = &sip->closeup_pos;
|
|
closeup_zoom = sip->closeup_zoom;
|
|
|
|
if (sip->uses_team_colors) {
|
|
render_info.set_team_color(!tcolor.empty() ? tcolor : sip->default_team_name, "none", 0, 0);
|
|
}
|
|
|
|
if (sip->flags[Ship::Info_Flags::No_lighting]) {
|
|
model_lighting = false;
|
|
}
|
|
|
|
// Make sure model is loaded
|
|
model_num = model_load(sip, true);
|
|
render_info.set_replacement_textures(model_num, sip->replacement_textures);
|
|
|
|
break;
|
|
|
|
case TECH_PROP:
|
|
prop_info* pip;
|
|
pip = &Prop_info[class_idx];
|
|
|
|
closeup_pos = &pip->closeup_pos;
|
|
closeup_zoom = pip->closeup_zoom;
|
|
|
|
if (pip->flags[Prop::Info_Flags::No_lighting]) {
|
|
model_lighting = false;
|
|
}
|
|
|
|
// Make sure model is loaded
|
|
model_num = model_load(pip->pof_file.c_str());
|
|
|
|
break;
|
|
|
|
case TECH_WEAPON:
|
|
weapon_info* wip;
|
|
wip = &Weapon_info[class_idx];
|
|
|
|
closeup_pos = &wip->closeup_pos;
|
|
closeup_zoom = wip->closeup_zoom;
|
|
|
|
if (wip->wi_flags[Weapon::Info_Flags::Mr_no_lighting]) {
|
|
model_lighting = false;
|
|
}
|
|
|
|
// Make sure model is loaded
|
|
if (VALID_FNAME(wip->tech_model)) {
|
|
model_num = model_load(wip->tech_model, nullptr, ErrorType::WARNING);
|
|
} else {
|
|
// no tech model!!
|
|
return false;
|
|
}
|
|
|
|
break;
|
|
|
|
case TECH_POF:
|
|
closeup_pos = close_pos;
|
|
closeup_zoom = close_zoom;
|
|
model_num = model_load(pof_filename.c_str());
|
|
|
|
break;
|
|
|
|
case TECH_JUMP_NODE:
|
|
closeup_pos = close_pos;
|
|
closeup_zoom = close_zoom;
|
|
model_num = model_load(pof_filename.c_str());
|
|
render_info.set_color(HUD_color_red, HUD_color_green, HUD_color_blue);
|
|
render_flags |= MR_NO_POLYS | MR_SHOW_OUTLINE_HTL | MR_NO_TEXTURING;
|
|
model_lighting = false;
|
|
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
//check if the model was loaded
|
|
if (model_num < 0)
|
|
return false;
|
|
|
|
// Clip
|
|
gr_set_clip(x1, y1, x2 - x1, y2 - y1, GR_RESIZE_NONE);
|
|
|
|
// Handle 3D init stuff
|
|
g3_start_frame(1);
|
|
g3_set_view_matrix(closeup_pos, &vmd_identity_matrix, closeup_zoom * zoom);
|
|
|
|
gr_set_proj_matrix(Proj_fov, gr_screen.clip_aspect, Min_draw_distance, Max_draw_distance);
|
|
gr_set_view_matrix(&Eye_position, &Eye_matrix);
|
|
|
|
// setup lights
|
|
common_setup_room_lights();
|
|
|
|
// Draw the model!!
|
|
model_clear_instance(model_num);
|
|
|
|
int model_instance = -1;
|
|
|
|
// Get a cached UI render instance for ships so repeated UI/Lua renders avoid per-call allocation churn
|
|
if (model_type == TECH_SHIP) {
|
|
auto sip = &Ship_info[class_idx];
|
|
const auto subsystem_hash = model_hash_subsystem_name_list_for_cache(destroyed_subsystems);
|
|
const auto cache_result = model_get_cached_ui_render_instance(model_num, &model_instance, subsystem_hash);
|
|
if (cache_result == TriStateBool::TRUE_) {
|
|
model_set_up_techroom_instance(sip, model_instance);
|
|
if (!destroyed_subsystems.empty()) {
|
|
auto pm = model_get(model_num);
|
|
auto pmi = model_get_instance(model_instance);
|
|
flagset<Ship::Subsystem_Flags> empty;
|
|
|
|
for (int idx = 0; idx < sip->n_subsystems; ++idx) {
|
|
auto& subsystem = sip->subsystems[idx];
|
|
|
|
if (subsystem.subobj_num < 0 || subsystem.subobj_num >= pm->n_models ||
|
|
subsystem.model_num != model_num) {
|
|
continue;
|
|
}
|
|
|
|
for (auto& destroyed_name : destroyed_subsystems) {
|
|
if (!stricmp(subsystem.subobj_name, destroyed_name.c_str()) ||
|
|
!stricmp(subsystem.name, destroyed_name.c_str())) {
|
|
pmi->submodel[subsystem.subobj_num].blown_off = true;
|
|
model_replicate_submodel_instance(pm, pmi, subsystem.subobj_num, empty);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
render_info.set_detail_level_lock(0);
|
|
|
|
if (!lighting || !model_lighting)
|
|
render_flags |= MR_NO_LIGHTING;
|
|
|
|
render_info.set_flags(render_flags);
|
|
|
|
bool s_save = Shadow_override;
|
|
Shadow_override = true;
|
|
|
|
if (model_type == TECH_SHIP) {
|
|
model_render_immediate(&render_info, model_num, model_instance, orient, &vmd_zero_vector);
|
|
} else {
|
|
model_render_immediate(&render_info, model_num, orient, &vmd_zero_vector);
|
|
}
|
|
|
|
Shadow_override = s_save;
|
|
|
|
// OK we're done
|
|
gr_end_view_matrix();
|
|
gr_end_proj_matrix();
|
|
|
|
// Bye!!
|
|
g3_end_frame();
|
|
gr_reset_clip();
|
|
|
|
return true;
|
|
}
|