--!strict -- ZFightSweep: find (and optionally fix) faces that will z-fight inside a model or a built scene. -- -- Two box faces fight when their outward normals agree, their planes are < TH apart, and their rectangles -- overlap by more than MARGIN on both in-plane axes. Edge contact is fine. Only Block parts are checked -- (MeshParts, unions, balls and SpecialMesh parts don't have box faces). -- A pair is "visible" when the faces differ in colour/material/variant, or either carries a stud -- MaterialVariant (texture origin is per part, so even identical studded parts disagree). -- -- Fix: grow the part with the SMALLER face outward along that face normal until the planes are EPS apart -- (its opposite face stays put). Run sweeps in edit mode; long loops on a playtest client can kill it. -- -- local pairs_ = ZFightSweep.sweep(model) -- read only -- local fixed = ZFightSweep.fix(model) -- returns count fixed -- -- This is an O(n^2)-per-bin sketch: fine for templates (hundreds of parts) and scenes up to ~20k parts -- with the grid binning. For bigger worlds, sweep per chunk. local ZFightSweep = {} ZFightSweep.TH = 0.005 -- planes closer than this fight ZFightSweep.EPS = 0.01 -- fixed planes end up this far apart ZFightSweep.MARGIN = 0.004 -- overlap must exceed this to count ZFightSweep.BIN = 0.05 -- plane-offset bin size type Face = { part: BasePart, n: Vector3, -- outward world normal c: Vector3, -- centre u: Vector3, v: Vector3, -- in-plane axes hu: number, hv: number, -- half sizes axis: number, sign: number, area: number, } local function eligible(p: BasePart): boolean if p.Transparency >= 0.98 then return false end if not p:IsA("Part") or p.Shape ~= Enum.PartType.Block then return false end if p:FindFirstChildWhichIsA("DataModelMesh") then return false end return true end local function faces(p: BasePart, out: { Face }) local cf, s = p.CFrame, p.Size local ax = { cf.RightVector, cf.UpVector, -cf.LookVector } local half = { s.X / 2, s.Y / 2, s.Z / 2 } for a = 1, 3 do local b, c = a % 3 + 1, (a + 1) % 3 + 1 for _, sg in { 1, -1 } do table.insert(out, { part = p, n = ax[a] * sg, c = cf.Position + ax[a] * sg * half[a], u = ax[b], v = ax[c], hu = half[b], hv = half[c], axis = a, sign = sg, area = 4 * half[b] * half[c], }) end end end local function looksDifferent(a: BasePart, b: BasePart): boolean return a.Color ~= b.Color or a.Material ~= b.Material or a.MaterialVariant ~= b.MaterialVariant or a.MaterialVariant ~= "" or b.MaterialVariant ~= "" end -- overlap of face b's rectangle projected on face a's axes (both directions must exceed MARGIN) local function overlaps(a: Face, b: Face): boolean local d = b.c - a.c local bu = math.abs(b.u:Dot(a.u)) * b.hu + math.abs(b.v:Dot(a.u)) * b.hv local bv = math.abs(b.u:Dot(a.v)) * b.hu + math.abs(b.v:Dot(a.v)) * b.hv local ou = a.hu + bu - math.abs(d:Dot(a.u)) local ov = a.hv + bv - math.abs(d:Dot(a.v)) return ou > ZFightSweep.MARGIN and ov > ZFightSweep.MARGIN end export type Pair = { a: Face, b: Face, sep: number, visible: boolean } function ZFightSweep.sweep(root: Instance): { Pair } local all: { Face } = {} for _, d in root:GetDescendants() do if d:IsA("BasePart") and eligible(d) then faces(d, all) end end -- bin by (rounded normal, plane offset) so only candidates are compared local bins: { [string]: { Face } } = {} for _, f in all do local n = f.n local key = string.format("%d,%d,%d|%d", math.round(n.X * 100), math.round(n.Y * 100), math.round(n.Z * 100), math.floor(f.c:Dot(n) / ZFightSweep.BIN)) local list = bins[key] if not list then list = {}; bins[key] = list end table.insert(list, f) end local found: { Pair } = {} local seen: { [string]: boolean } = {} for key, list in bins do -- also compare against the neighbouring offset bin (planes straddling a bin edge) local head, off = key:match("^(.-)|(%-?%d+)$") local nxt = bins[`{head}|{tonumber(off) :: number + 1}`] or {} local cands = table.clone(list) table.move(nxt, 1, #nxt, #cands + 1, cands) for i, a in list do for j, b in cands do if (j > i or j > #list) and a.part ~= b.part and a.n:Dot(b.n) > 0.9995 then local sep = math.abs((b.c - a.c):Dot(a.n)) if sep < ZFightSweep.TH and overlaps(a, b) then local id = `{a.part:GetDebugId()}{a.axis}{a.sign}/{b.part:GetDebugId()}{b.axis}{b.sign}` if not seen[id] then seen[id] = true table.insert(found, { a = a, b = b, sep = sep, visible = looksDifferent(a.part, b.part) }) end end end end end end return found end -- Grow the smaller face's part outward so the planes end EPS apart. Jointed (unanchored) parts are skipped: -- moving them would break weld offsets; report and fix those by hand. function ZFightSweep.fix(root: Instance): number local fixed = 0 for _, pr in ZFightSweep.sweep(root) do if pr.visible then local f = if pr.a.area <= pr.b.area then pr.a else pr.b local other = if f == pr.a then pr.b else pr.a local p = f.part if p.Anchored then -- distance f's plane must move outward to sit EPS beyond the other plane local grow = ZFightSweep.EPS - (f.c - other.c):Dot(f.n) if grow > 0 then local axisSize = { p.Size.X, p.Size.Y, p.Size.Z } axisSize[f.axis] += grow p.Size = Vector3.new(axisSize[1], axisSize[2], axisSize[3]) p.CFrame = p.CFrame + f.n * (grow / 2) fixed += 1 end end end end return fixed end return ZFightSweep