@@ -51,16 +51,23 @@ namespace {
5151 bool at_target;
5252 };
5353
54- // Rings are rebuilt into `scratch` (absolute world coords) every frame and drawn with DrawPrimitiveUP,
55- // so following the character is smooth. The per-ring specs only change when the hovered skill or the
56- // settings change.
54+ // Draped ring geometry (absolute world coords) drawn with DrawPrimitiveUP. The per-vertex terrain drape
55+ // is expensive, so `scratch` is cached and only rebuilt when an anchor moves, the hovered skill changes,
56+ // or settings change - not every frame .
5757 std::vector<RingSpec> built_specs;
5858 std::vector<RingVertex> scratch;
5959 GW ::Constants::SkillID built_skill = static_cast <GW ::Constants::SkillID>(0 );
6060 bool rings_dirty = false ;
6161 uint32_t debug_skill_id = 0 ;
6262 int compositor_token = 0 ;
6363
64+ // Anchor state `scratch` was last draped at; a change beyond kAnchorMoveEpsilon triggers a rebuild.
65+ constexpr float kAnchorMoveEpsilon = 1 .f;
66+ float built_me_x = 0 .f, built_me_y = 0 .f;
67+ uint32_t built_me_zplane = 0 , built_target_id = 0 , built_target_zplane = 0 ;
68+ float built_target_x = 0 .f, built_target_y = 0 .f;
69+ bool built_tgt_valid = false ;
70+
6471 int RingSegments (const float radius)
6572 {
6673 const auto circumference = std::max (radius, ring_thickness) * DirectX::XM_2PI ;
@@ -147,11 +154,13 @@ namespace {
147154 SpecsForSkill (skill, built_specs);
148155 built_skill = skill.skill_id ;
149156 rings_dirty = false ;
157+ scratch.clear ();
150158 }
151159
152160 void ResetRings ()
153161 {
154162 built_specs.clear ();
163+ scratch.clear ();
155164 built_skill = static_cast <GW ::Constants::SkillID>(0 );
156165 }
157166} // namespace
@@ -199,16 +208,31 @@ void SkillRangeRingsModule::DrawInWorld(IDirect3DDevice9* device)
199208 if (rings_dirty || skill->skill_id != built_skill) BuildSpecs (*skill);
200209 if (built_specs.empty ()) return ;
201210
202- // Rebuild the draped rings at the current anchor positions every frame, so movement is smooth . Each
203- // ring anchors to the current target (targeted AoE skills) or the player, draped onto that surface.
211+ // Re-drape only when an anchor actually moves; otherwise reuse the cached geometry . Each ring anchors to
212+ // the current target (targeted AoE skills) or the player, draped onto that surface.
204213 const GW ::Agent* target = GW::Agents::GetTarget ();
205214 const bool tgt_valid = aoe_at_target && target && target->agent_id != me->agent_id ;
206- const auto n_planes = TerrainDrape::PathingPlaneCount ();
207- scratch.clear ();
208- for (const auto & spec : built_specs) {
209- const GW ::Agent* anchor = (spec.at_target && tgt_valid) ? target : static_cast <const GW ::Agent*>(me);
210- const float ref_z = TerrainDrape::SurfaceZ (anchor->pos .x , anchor->pos .y , anchor->pos .zplane , n_planes);
211- EmitBand (scratch, anchor->pos .x , anchor->pos .y , anchor->pos .zplane , n_planes, ref_z ? ref_z : anchor->z , spec);
215+ const uint32_t target_id = tgt_valid ? target->agent_id : 0 ;
216+ const auto moved = [](const float ax, const float ay, const float bx, const float by) {
217+ return std::fabs (ax - bx) > kAnchorMoveEpsilon || std::fabs (ay - by) > kAnchorMoveEpsilon ;
218+ };
219+ const bool rebuild = scratch.empty ()
220+ || tgt_valid != built_tgt_valid || target_id != built_target_id
221+ || me->pos .zplane != built_me_zplane || moved (me->pos .x , me->pos .y , built_me_x, built_me_y)
222+ || (tgt_valid && (target->pos .zplane != built_target_zplane
223+ || moved (target->pos .x , target->pos .y , built_target_x, built_target_y)));
224+ if (rebuild) {
225+ const auto n_planes = TerrainDrape::PathingPlaneCount ();
226+ scratch.clear ();
227+ for (const auto & spec : built_specs) {
228+ const GW ::Agent* anchor = (spec.at_target && tgt_valid) ? target : static_cast <const GW ::Agent*>(me);
229+ const float ref_z = TerrainDrape::SurfaceZ (anchor->pos .x , anchor->pos .y , anchor->pos .zplane , n_planes);
230+ EmitBand (scratch, anchor->pos .x , anchor->pos .y , anchor->pos .zplane , n_planes, ref_z ? ref_z : anchor->z , spec);
231+ }
232+ built_me_x = me->pos .x , built_me_y = me->pos .y , built_me_zplane = me->pos .zplane ;
233+ built_target_id = target_id, built_tgt_valid = tgt_valid;
234+ built_target_x = tgt_valid ? target->pos .x : 0 .f , built_target_y = tgt_valid ? target->pos .y : 0 .f ;
235+ built_target_zplane = tgt_valid ? target->pos .zplane : 0 ;
212236 }
213237 if (scratch.size () < 3 ) return ;
214238
0 commit comments