GTA III's shatter code only works if every pane is square and lying on a world axis. Every breakable pane in Liberty City is both, so two bugs sat in the game for 25 years — until Upstate Liberty, a map expansion mod, put tilted rectangular windows on the map. Pick a code variant below and shoot the glass.
A breakable pane is handed to the shatter code as three vectors: a corner pos, a right vector spanning its width and an up vector spanning its height. The pane is cut into a grid: each axis gets length + 0.75 steps (truncated to an integer, at least one and at most three), so segments of roughly a metre, nine of them at most. Every segment is then split into the same fixed five triangles, which caps a window at 45 shards, the size of the falling-pane pool.
Each shard gets a release timer. A hard enough hit releases all of them on the same frame; anything softer — a bullet — delays each shard by 100 ms per metre of distance from the impact point, so the break spreads from the hole while the outer shards hang in mid-air. Obbe Vermeij, who wrote the system, says that stagger is why a real break looks far less wrong than a screenshot of the shard layout does.
Each shard carries its own frame: a right axis and an up axis that should each come out one segment long. The code builds them from the pane's full right and up vectors, dividing each by its own length to cancel the pane's dimension back out of the result. Except the up axis is divided by the length of right — a copy-paste of the line above it — and that cancellation only works when the two lengths are equal. On any non-square pane, a leftover factor of height ÷ width stays in every shard's up axis.
// CGlass::GeneratePanesForWindow — GTA III pane->GetRight() = (right * rightScl) / rightLen; pane->GetUp() = (up * upScl) / rightLen; // copypaste bug pane->GetUp() = (up * upScl) / upLen; // Vice City, and re3 under FIX_BUGS // the shard's position on the grid, which divides by upLen correctly pane->GetPosition() = right / rightLen * (rightScl * center.x + stepx) + up / upLen * (upScl * center.y + stepy) + pos;
Only the shard's own frame is wrong; the grid of positions it sits on divides by upLen correctly. So every shard stays centred on its proper cell and is stretched or squashed vertically in place, about its own centre. On a wide pane the shards come out too short and gaps open between them; on a tall one they come out too tall, overlapping their neighbours and hanging past the glass. Invisible in GTA III, where every stock pane is square and the factor is 1.
The shatter code never sees the pane directly; it rebuilds it from the window's four collision vertices. GTA III transforms those vertices into world space first and only then takes their min and max. What that measures is the axis-aligned box around the glass, and it is the box the pane gets rebuilt from.
// CGlass::WindowRespondsToCollision — GTA III // col->vertices[0..3]: the window's four collision corners, in model // space (the coordinates the artist built the window with). // object->GetMatrix(): the transform that places the object in the // world. GTA's Z axis is up; X and Y are the ground plane. CVector a = object->GetMatrix() * col->vertices[0].Get(); // into the world first... CVector b = object->GetMatrix() * col->vertices[1].Get(); CVector c = object->GetMatrix() * col->vertices[2].Get(); CVector d = object->GetMatrix() * col->vertices[3].Get(); float minx = Min(Min(a.x, b.x), Min(c.x, d.x)); // ...measured after float maxx = Max(Max(a.x, b.x), Max(c.x, d.x)); // miny/maxy and minz/maxz the same way — together they are the // axis-aligned box around the glass // rebuild the pane from the box: corner + up + right GeneratePanesForWindow(type, CVector(minx, miny, minz), // corner: bottom of the box CVector(0.0f, 0.0f, maxz-minz), // up: world-vertical CVector(maxx-minx, maxy-miny, 0.0f), // right: box diagonal — always +X, +Y speed, point, 0.1f, cracked, explosion); // CGlass::WindowRespondsToCollision — Vice City: extents first, transform after CVector a = col->vertices[0].Get(); // still model space... CVector b = col->vertices[1].Get(); CVector c = col->vertices[2].Get(); CVector d = col->vertices[3].Get(); float minx = Min(Min(a.x, b.x), Min(c.x, d.x)); // ...so the box fits the glass exactly float maxx = Max(Max(a.x, b.x), Max(c.x, d.x)); // miny/maxy and minz/maxz the same way // only now do the two bottom corners go into the world CVector pa = object->GetMatrix() * CVector(minx, miny, minz); CVector pb = object->GetMatrix() * CVector(maxx, maxy, minz); GeneratePanesForWindow(type, pa, // corner: bottom of the glass CVector(0.0f, 0.0f, maxz-minz), // up: still world-vertical pb - pa, // right: true bottom edge, keeps its tilt speed, point, 0.1f, cracked, explosion, 1, false);
Seen from above, a vertical pane is a line segment, and the box around it is the rectangle that has the glass as its diagonal. The length survives for exactly that reason: a rectangle's diagonal is as long as the segment it encloses. But a rectangle has two diagonals, and the code can only ever rebuild one of them: right is (maxx−minx, maxy−miny), and both differences are positive by construction, so the pane always runs along the ascending diagonal, from the (minx, miny) corner up and to the right.
Glass that happens to run that way is reproduced exactly. Glass running along the other diagonal gets its mirror image: the right size at the wrong tilt, cutting through the window. A pane tilted −θ is rebuilt at +θ, so the error is twice the tilt angle — it peaks at 45°, where the shards sit at a right angle to the glass they came from, and vanishes at 0° and 90°, where the rectangle collapses onto the glass and its two diagonals become the same line. Liberty City's stock windows all sit flat at 0° or 90°, so the wrong diagonal never came up.
Drag rotation through zero and watch the top-view inset: one half of the sweep is exact, the other mirrors. Vice City takes min and max in model space, where the pane is axis-aligned and the box fits it exactly, then transforms the two corners into the world and subtracts them, so right keeps its true direction at any yaw. Silent backported exactly that code. Both games still take up as a world-vertical built from the box height, so neither handles a pane that is pitched or rolled.
Context. Obbe Vermeij wrote
this system at Rockstar North for GTA III (2001) and says he never knew
about the axis-aligned limitation — there was no good reason for it and
it would not have been hard to fix.
Silent, author of
SilentPatch,
hit both bugs
in July 2026 while building Upstate Liberty, whose windows are tilted and
rectangular, and fixed them by backporting Vice City's code. Obbe wonders
whether GTA III's artists found the tilt bug and just stopped putting
breakable glass on non-aligned walls; Silent suspects Vice City's version
got written once artists started putting glass in the North Point Mall.
The snippets above are trimmed from the re3 / reVC decompilations
(also where the copypaste bug comment comes from).
Rockstar never shipped the original source.