diff --git a/include/rendering/m2_model_classifier.hpp b/include/rendering/m2_model_classifier.hpp index 6d6faeb5..9b0ee247 100644 --- a/include/rendering/m2_model_classifier.hpp +++ b/include/rendering/m2_model_classifier.hpp @@ -51,6 +51,7 @@ struct M2ClassificationResult { bool isWaterfall = false; ///< Waterfall model (ambient sound + splash particles) bool isBrazierOrFire = false; ///< Brazier / campfire / bonfire model bool isTorch = false; ///< Wall-mounted or standing torch + bool isSkyBird = false; ///< Flying bird/bat doodad (hide until animation range) // --- Ambient emitter type (for sound system) --- AmbientEmitterType ambientEmitterType = AmbientEmitterType::None; diff --git a/include/rendering/m2_renderer.hpp b/include/rendering/m2_renderer.hpp index 741436a6..24edcd70 100644 --- a/include/rendering/m2_renderer.hpp +++ b/include/rendering/m2_renderer.hpp @@ -130,6 +130,7 @@ struct M2ModelGPU { bool isLanternLike = false; // Model name matches lantern/lamp/light (precomputed) bool isKoboldFlame = false; // Model name matches kobold+(candle/torch/mine) (precomputed) bool isLavaModel = false; // Model name contains lava/molten/magma (UV scroll fallback) + bool isSkyBird = false; // Flying bird/bat doodad — hide until animation range bool hasTextureAnimation = false; // True if any batch has UV animation bool hasTransparentBatches = false; // True if any batch uses alpha-blend or additive (blendMode >= 2) uint8_t availableLODs = 0; // Bitmask: bit N set if any batch has submeshLevel==N @@ -215,6 +216,7 @@ struct M2Instance { bool cachedIsGroundDetail = false; bool cachedIsInvisibleTrap = false; bool cachedIsInstancePortal = false; + bool cachedIsSkyBird = false; bool cachedIsValid = false; bool skipCollision = false; // WMO interior doodads — skip player wall collision float cachedBoundRadius = 0.0f; diff --git a/src/rendering/m2_model_classifier.cpp b/src/rendering/m2_model_classifier.cpp index 1e8044ec..b72395d1 100644 --- a/src/rendering/m2_model_classifier.cpp +++ b/src/rendering/m2_model_classifier.cpp @@ -200,6 +200,18 @@ M2ClassificationResult classifyM2Model( }); const bool ambientCreature = hasAny(n, kAmbientTokens); + // --------------------------------------------------------------- + // Sky birds / bats: animated flying doodads that look frozen beyond bone range + // --------------------------------------------------------------- + static constexpr auto kSkyBirdTokens = std::to_array({ + "albatross", "carrionbird", "crane", "crow", + "eagle", "gull", "hawk", "osprey", + "owl", "parrot", "pelican", + "raven", "seagull", "vulture", + }); + r.isSkyBird = hasAny(n, kSkyBirdTokens) || has(n, "\\bird") + || has(n, "\\bat\\") || has(n, "\\bat."); + // --------------------------------------------------------------- // Animation / foliage rendering flags // --------------------------------------------------------------- diff --git a/src/rendering/m2_renderer.cpp b/src/rendering/m2_renderer.cpp index 33a7b1b5..4827ce16 100644 --- a/src/rendering/m2_renderer.cpp +++ b/src/rendering/m2_renderer.cpp @@ -1245,6 +1245,7 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) { gpuModel.isWaterfall = cls.isWaterfall; gpuModel.isBrazierOrFire = cls.isBrazierOrFire; gpuModel.isTorch = cls.isTorch; + gpuModel.isSkyBird = cls.isSkyBird; gpuModel.ambientEmitterType = cls.ambientEmitterType; gpuModel.boundMin = tightMin; gpuModel.boundMax = tightMax; diff --git a/src/rendering/m2_renderer_render.cpp b/src/rendering/m2_renderer_render.cpp index be415c13..d39b3d43 100644 --- a/src/rendering/m2_renderer_render.cpp +++ b/src/rendering/m2_renderer_render.cpp @@ -90,6 +90,7 @@ uint32_t M2Renderer::createInstance(uint32_t modelId, const glm::vec3& position, instance.cachedIsGroundDetail = mdlRef.isGroundDetail; instance.cachedIsInvisibleTrap = mdlRef.isInvisibleTrap; instance.cachedIsInstancePortal = mdlRef.isInstancePortal; + instance.cachedIsSkyBird = mdlRef.isSkyBird; instance.cachedIsValid = mdlRef.isValid(); instance.cachedModel = &mdlRef; instance.recomputeCachedCullFactors(); @@ -212,6 +213,7 @@ uint32_t M2Renderer::createInstanceWithMatrix(uint32_t modelId, const glm::mat4& instance.cachedBoundRadius = mdl2.boundRadius; instance.cachedIsGroundDetail = mdl2.isGroundDetail; instance.cachedIsInvisibleTrap = mdl2.isInvisibleTrap; + instance.cachedIsSkyBird = mdl2.isSkyBird; instance.cachedIsValid = mdl2.isValid(); instance.cachedModel = &mdl2; instance.recomputeCachedCullFactors(); @@ -870,6 +872,16 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const if (paddedRadius > 0.0f && !frustum.intersectsSphere(instance.position, paddedRadius)) continue; } + // Animated sky birds: cap render distance to bone-update range so they + // fade in only when close enough for animation, hiding frozen poses. + if (instance.cachedIsSkyBird && instance.cachedHasAnimation && !instance.cachedDisableAnimation) { + constexpr float kBirdMaxDistSq = rendering::M2_LOD3_DISTANCE * rendering::M2_LOD3_DISTANCE; + if (effectiveMaxDistSq > kBirdMaxDistSq) { + effectiveMaxDistSq = kBirdMaxDistSq; + if (distSq > effectiveMaxDistSq) continue; + } + } + sortedVisible_.push_back({i, instance.modelId, distSq, effectiveMaxDistSq}); }