--[[ * CdvCast * Copyright Regan "CuckyDev" Green 2022 --]] --3D vertex helpers local function CdvCast_Vec3_Add(vec_a, vec_b) return { x = vec_a.x + vec_b.x, y = vec_a.y + vec_b.y, z = vec_a.z + vec_b.z } end local function CdvCast_Vec3_Sub(vec_a, vec_b) return { x = vec_a.x - vec_b.x, y = vec_a.y - vec_b.y, z = vec_a.z - vec_b.z } end local function CdvCast_Vec3_Scale(vec, scale) return { x = FixedMul(vec.x, scale), y = FixedMul(vec.y, scale), z = FixedMul(vec.z, scale) } end local function CdvCast_Vec3_Multiply(vec_a, vec_b) return { x = FixedMul(vec_a.x, vec_b.x), y = FixedMul(vec_a.y, vec_b.y), z = FixedMul(vec_a.z, vec_b.z) } end local function CdvCast_Vec3_Divide(vec_a, vec_b) return { x = FixedDiv(vec_a.x, vec_b.x), y = FixedDiv(vec_a.y, vec_b.y), z = FixedDiv(vec_a.z, vec_b.z) } end local function CdvCast_Vec3_Dot(vec_a, vec_b) return FixedMul(vec_a.x, vec_b.x) + FixedMul(vec_a.y, vec_b.y) + FixedMul(vec_a.z, vec_b.z) end local function CdvCast_Vec3_Cross(vec_a, vec_b) return { x = FixedMul(vec_a.y, vec_b.z) - FixedMul(vec_a.z, vec_b.y), y = FixedMul(vec_a.z, vec_b.x) - FixedMul(vec_a.x, vec_b.z), z = FixedMul(vec_a.x, vec_b.y) - FixedMul(vec_a.y, vec_b.x) } end local function CdvCast_Vec3_Normalize(vec) local sqr = FixedHypot(FixedHypot(vec.x, vec.y), vec.z) return { x = FixedDiv(vec.x, sqr), y = FixedDiv(vec.y, sqr), z = FixedDiv(vec.z, sqr) } end local function CdvCast_Vec3_Length(vec) return FixedHypot(FixedHypot(vec.x, vec.y), vec.z) end --2D vertex helpers local function CdvCast_Vec2_Add(vec_a, vec_b) return { x = vec_a.x + vec_b.x, y = vec_a.y + vec_b.y } end local function CdvCast_Vec2_Sub(vec_a, vec_b) return { x = vec_a.x - vec_b.x, y = vec_a.y - vec_b.y } end local function CdvCast_Vec2_Scale(vec_a, scale) return { x = FixedMul(vec_a.x, scale), y = FixedMul(vec_a.y, scale) } end local function CdvCast_Vec2_Multiply(vec_a, vec_b) return { x = FixedMul(vec_a.x, vec_b.x), y = FixedMul(vec_a.y, vec_b.y) } end local function CdvCast_Vec2_Divide(vec_a, vec_b) return { x = FixedDiv(vec_a.x, vec_b.x), y = FixedDiv(vec_a.y, vec_b.y) } end local function CdvCast_Vec2_Dot(vec_a, vec_b) return FixedMul(vec_a.x, vec_b.x) + FixedMul(vec_a.y, vec_b.y) end local function CdvCast_Vec2_Cross(vec_a, vec_b) return FixedMul(vec_a.x, vec_b.y) - FixedMul(vec_a.y, vec_b.x) end local function CdvCast_Vec2_Normalize(vec) local sqr = FixedHypot(vec.x, vec.y) return { x = FixedDiv(vec.x, sqr), y = FixedDiv(vec.y, sqr) } end local function CdvCast_Vec2_Length(vec) return FixedHypot(vec.x, vec.y) end --Intersects local function CdvCast_Intersect_Slope(intersects, ray, sector, fof, slope) --Get plane local plane_pos, plane_nor if type(slope) == "number" then plane_pos = {x = ray.from.x, y = ray.from.y, z = slope} plane_nor = {x = 0, y = 0, z = (ray.from.z > slope) and FRACUNIT or -FRACUNIT} else plane_pos = {x = ray.from.x, y = ray.from.y, z = P_GetZAt(slope, ray.from.x, ray.from.y)} plane_nor = CdvCast_Vec3_Normalize({x = slope.normal.x, y = slope.normal.y, z = slope.normal.z}) end --Get t value local d = CdvCast_Vec3_Dot(plane_nor, plane_pos) local t = CdvCast_Vec3_Dot(plane_nor, ray.ndir) if t == 0 then return end --Get x value and contact point local y = CdvCast_Vec3_Dot(plane_nor, ray.from) local x = FixedDiv(d - y, t) if x > 0 then local contact = CdvCast_Vec3_Add(ray.from, CdvCast_Vec3_Scale(ray.ndir, x)) --Check for cross (non-solid FOFs) local cross = nil if fof ~= nil and not (fof.flags & FF_SOLID) then cross = {sector = sector, line = nil} end --Add to intersect list if (d - y) >= 0 then plane_nor.x = -plane_nor.x plane_nor.y = -plane_nor.y plane_nor.z = -plane_nor.z end table.insert(intersects, { hit = {sector = sector, fof = fof, line = nil, side = nil}, pos = contact, nor = plane_nor, length = x, cross = cross }) end end local function CdvCast_Intersect_CheckSide(contact, bslope, tslope) --Get heights at contact point local bheight, theight if type(bslope) == "number" then bheight = bslope else bheight = P_GetZAt(bslope, contact.x, contact.y) end if type(tslope) == "number" then theight = tslope else theight = P_GetZAt(tslope, contact.x, contact.y) end --Check against heights return contact.z >= bheight and contact.z <= theight end local function CdvCast_Intersect_Line(intersects, ray, sector, line) --Get line and sector on other side of line local osector = nil local side = nil if line.frontsector == sector then osector = line.backsector side = line.frontside else osector = line.frontsector side = line.backside end if (line.flags & ML_IMPASSIBLE) then osector = nil end --Get plane local plane_pos = {x = line.v1.x, y = line.v1.y, z = 0} local plane_nor = CdvCast_Vec2_Normalize({x = line.dx, y = line.dy}) plane_nor = {x = -plane_nor.y, y = plane_nor.x, z = 0} --Get t value local d = CdvCast_Vec3_Dot(plane_nor, plane_pos) local t = CdvCast_Vec3_Dot(plane_nor, ray.ndir) if t == 0 then return end --Get x value and contact point local y = CdvCast_Vec3_Dot(plane_nor, ray.from) local x = FixedDiv(d - y, t) if x > 0 then local contact = CdvCast_Vec3_Add(ray.from, CdvCast_Vec3_Scale(ray.ndir, x)) --Check if contact point is on line local cs = {x = line.dx, y = line.dy} local cl = CdvCast_Vec2_Length(cs) local cd = CdvCast_Vec2_Dot(CdvCast_Vec2_Normalize(cs), CdvCast_Vec2_Sub(contact, line.v1)) if cd < 0 or cd > cl then return end --Check against sides to cross other sector local hit = { sector = sector, fof = nil, line = line, side = side } local cross = nil if osector ~= nil then --Check sector cross sides cross = {sector = osector, line = line} if CdvCast_Intersect_CheckSide(contact, sector.f_slope or sector.floorheight, osector.f_slope or osector.floorheight) then cross = nil elseif CdvCast_Intersect_CheckSide(contact, osector.c_slope or osector.ceilingheight, sector.c_slope or sector.ceilingheight) then cross = nil else --Check FOF sides for v in osector.ffloors() do if (v.flags & FF_EXISTS) and CdvCast_Intersect_CheckSide(contact, v.b_slope or v.bottomheight, v.t_slope or v.topheight) then hit.sector = osector hit.fof = v if (v.flags & FF_SOLID) then cross = nil end break end end end end --Add to intersection list if (d - y) >= 0 then plane_nor.x = -plane_nor.x plane_nor.y = -plane_nor.y plane_nor.z = -plane_nor.z end table.insert(intersects, { hit = hit, pos = contact, nor = plane_nor, length = x, cross = cross }) end end --Raycast function local function CdvCast_Raycast(from, dir) --Get ray local ray = { from = from, dir = dir, ndir = CdvCast_Vec3_Normalize(dir), to = CdvCast_Vec3_Add(from, dir), length = CdvCast_Vec3_Length(dir) } --Get sector to start in local sector = R_PointInSubsector(ray.from.x, ray.from.y) sector = sector.sector --Sector loop local crosses = {} local hit_lines = {} while sector ~= nil do --Get intersections local intersects = {} --Slope intersects CdvCast_Intersect_Slope(intersects, ray, sector, nil, sector.f_slope or sector.floorheight) CdvCast_Intersect_Slope(intersects, ray, sector, nil, sector.c_slope or sector.ceilingheight) for v in sector.ffloors() do if (v.flags & FF_EXISTS) then CdvCast_Intersect_Slope(intersects, ray, sector, v, v.t_slope or v.topheight) CdvCast_Intersect_Slope(intersects, ray, sector, v, v.b_slope or v.bottomheight) end end --Line intersects for i = 0, #sector.lines - 1 do if hit_lines[sector.lines[i]] == nil then CdvCast_Intersect_Line(intersects, ray, sector, sector.lines[i]) end end --Sort intersections then process table.sort(intersects, function(a, b) return a.length < b.length end) local next_sector = nil for _,v in ipairs(intersects) do if v.length > ray.length then break end if v.cross == nil then --Solid intersection return { hit = v.hit, hit_position = v.pos, hit_normal = v.nor, crosses = crosses } else --Cross intersection table.insert(crosses, { hit = v.hit, hit_position = v.pos, hit_normal = v.nor }) if v.cross.sector ~= sector then --Clip ray against hit line local rayadd = CdvCast_Vec3_Sub(v.pos, ray.from) ray.from = v.pos ray.dir = CdvCast_Vec3_Sub(ray.dir, rayadd) ray.length = CdvCast_Vec3_Length(ray.dir) --Cross to next sector hit_lines[v.cross.line] = true next_sector = v.cross.sector break end end end sector = next_sector end --Hit nothing return { hit = nil, hit_position = {x = ray.to.x, y = ray.to.y, z = ray.to.z}, hit_normal = nil, crosses = crosses } end --Export interface rawset(_G, "CdvCast", { --Raycast function Raycast = CdvCast_Raycast })