Skip to content

Fix misplaced func_breakable_surf when damaged#1906

Open
Masterkatze wants to merge 1 commit intoNeotokyoRebuild:masterfrom
Masterkatze:fix_breakables
Open

Fix misplaced func_breakable_surf when damaged#1906
Masterkatze wants to merge 1 commit intoNeotokyoRebuild:masterfrom
Masterkatze:fix_breakables

Conversation

@Masterkatze
Copy link
Copy Markdown
Contributor

@Masterkatze Masterkatze commented Mar 29, 2026

Description

When a func_breakable_surf window is broken, the client renders a crack overlay by placing a panel grid starting from m_vCorner and stepping along vWidthStep/vHeightStep. On windows with non-trivial rotation, the crack overlay was displaced from the glass, sometimes by a large amount.

Three independent bugs caused this:

  1. Client independently re-derived panel directions (float precision drift)

The server computed m_vCorner using vertex positions, but the client independently re-derived the stepping directions via VectorAngles(-m_vNormal) + AngleVectors. For rotated windows, the server's vertex-based axes and the client's trig-based axes diverge due to floating-point representation, causing the panel grid to drift from the surface.

Fix: Add two new networked vectors m_vWidthDir and m_vHeightDir, sent at full float precision (SPROP_NOSCALE). The server computes these from actual vertex positions; the client uses them directly. All three rendering functions and all server-side panel position calculations use the stored vectors instead of re-deriving them.

  1. Back-face attack shifted m_vCorner 1 unit off the glass

When DotProduct(m_vNormal, vAttackDir) < 0 (attack from behind), the original code shifted all four corner vertices 1 unit along the normal before selecting m_vCorner. This placed the crack origin 1 unit away from the glass surface.

Fix: Skip the vertex shift in the NEO path. Instead, the normal is computed from vertex positions before the flip check so the dot product test remains valid, and m_vNormal is simply negated (not accompanied by a vertex shift) when needed.

  1. Axis swap produced wrong corner for 90°-rotated windows

When the BSP compiler's "width" edge (LLV to LRV) is more aligned with the canonical height axis than the width axis (e.g., a window whose width edge is vertical), the server swaps vWidth/vHeight before corner selection. Two mistakes in this swap path:

  • flDir scaling: The original code multiplied by flDir during the swap to cancel the effect of the vertex shift it had just applied. Since NEO skips the vertex shift, this scaling incorrectly negated the edge vectors for flDir = 1 (back-face attack), producing wrong bLeft/bLower values.

  • Transposed corner table: After the swap, vWidth spans what was the BSP height axis (LLV-ULV) and vHeight spans the BSP width axis (LLV-LRV). The bLeft/bLower vertex-label mapping is only valid in the unswapped frame. In the swapped frame the table must be transposed: bLeft indexes into LLV, LRV and bLower indexes into LLV, ULV.

Fix: Remove flDir from the swap and add a bSwapped flag that selects the correct corner table.

Toolchain

  • Linux GCC Distro Native [Arch Linux, GCC 15.2.1 20260209]

@Masterkatze Masterkatze requested review from a team and AdamTadeusz March 29, 2026 19:18
// the geometric height edge. Unlike the non-NEO path we do NOT scale by
// flDir here: in NEO we never shift the vertices, so flDir has no bearing
// on the edge vectors themselves.
float flWDist = DotProduct( vWidthDir, vWidth );
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be a good idea to normalize wWidth since wWidthDir already seems normalized by this point. That way the dot product would be done with both normalized parameters. Something like this:

Vector vWidthNorm = vWidth;
VectorNormalize( vWidthNorm );
float flWDist = DotProduct( vWidthDir, vWidthNorm );
bool bSwapped = ( fabs(flWDist) < 0.5f );  // < 0.5 ≡ angle > 60° from canonical width

Vector m_vCorner;
#ifdef NEO
Vector m_vWidthDir;
Vector m_vHeightDir;
Copy link
Copy Markdown
Contributor

@sunzenshen sunzenshen Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to zero initialize these new members in

void C_BreakableSurface::InitMaterial(WinEdge_t nEdgeType, int nEdgeStyle, char const* pMaterialName)

? (or some other initialization function)

https://github.com/Masterkatze/neo/blame/e931664b2fd54c99ecfdbf6bd4b6be20c6b07cba/src/game/client/c_func_breakablesurf.cpp#L344

e.g. put what you have in other places into one place in an initialization function like this:

Vector vWidth  = m_vLLVertex - m_vLRVertex;
Vector vHeight = m_vLLVertex - m_vULVertex;
m_vWidthDir  = vWidth;  VectorNormalize( m_vWidthDir.GetForModify() );
m_vHeightDir = vHeight; VectorNormalize( m_vHeightDir.GetForModify() );

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants