Files
wnsrc/lua/niknaks/modules/sh_bsp_leafs.lua
lifestorm 94063e4369 Upload
2024-08-04 22:55:00 +03:00

487 lines
14 KiB
Lua

--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
-- Copyright © 2022-2072, Nak, https://steamcommunity.com/id/Nak2/
-- All Rights Reserved. Not allowed to be reuploaded.
-- License: https://github.com/Nak2/NikNaks/blob/main/LICENSE
local obj_tostring = "BSP %s [ %s ]"
local format, clamp, min, max = string.format, math.Clamp, math.min, math.max
--- @class BSPObject
local meta = NikNaks.__metatables["BSP"]
--- @class LeafObject
local meta_leaf = {}
meta_leaf.__index = meta_leaf
meta_leaf.__tostring = function( self ) return format( obj_tostring, "Leaf", self.__id ) end
meta_leaf.MetaName = "BSP Leaf"
NikNaks.__metatables["BSP Leaf"] = meta_leaf
local MAX_MAP_NODES = 65536
local TEST_EPSILON = 0.01
local canGenerateParents = false
--- Generates parentNodes for nodes and leafs.
--- @param self BSPObject
--- @param nodeNum integer
--- @param parent integer
--- @param nodes table<Nodes>
--- @param leafs table<Leafs>
local function makeParents(self, nodeNum, parent, nodes, leafs, firstRun)
if firstRun and not canGenerateParents then return end
canGenerateParents = false
nodes[nodeNum].parentNode = parent
for i = 1, 2 do
local j = nodes[nodeNum].children[i]
if j < 0 then
leafs[-j - 1].parentNode = nodeNum;
else
makeParents(self, j, nodeNum, nodes, leafs)
end
end
end
--- Returns a table of map nodes
--- @return MapNode[]
function meta:GetNodes()
if self._node then return self._node end
self._node = {}
local data = self:GetLump( 5 )
for i = 0, math.min( data:Size() / 256, MAX_MAP_NODES ) - 1 do
--- @class MapNode
--- @field children number[]
local t = {}
t.planenum = data:ReadLong()
t.plane = self:GetPlanes()[ t.planenum ]
t.children = { data:ReadLong(), data:ReadLong() }
t.mins = Vector( data:ReadShort(), data:ReadShort(), data:ReadShort() )
t.maxs = Vector( data:ReadShort(), data:ReadShort(), data:ReadShort() )
t.firstFace = data:ReadUShort()
t.numFaces = data:ReadUShort()
t.area = data:ReadShort()
t.padding = data:ReadShort()
self._node[i] = t
end
self:ClearLump( 5 )
canGenerateParents = true
makeParents(self, 0, -1, self._node, self:GetLeafs(), true)
canGenerateParents = false
return self._node
end
--- Returns a table of map leafs.
--- @return LeafObject[], number num_clusters
function meta:GetLeafs()
if self._leafs then return self._leafs, self._leafs_num_clusters end
--- @type LeafObject[]
self._leafs = {}
local lumpversion = self:GetLumpVersion( 10 )
local data = self:GetLump( 10 )
local size = 240 -- version
if lumpversion == 0 then
size = size + 192 -- byte r, byte g, byte b + char expo
end
if self._version <= 19 or true then
size = size + 16
end
local n = 0
for i = 0, data:Size() / size - 1 do
data:Seek( i * size )
--- @class LeafObject
--- @field mins Vector
--- @field maxs Vector
local t = {}
t.contents = data:ReadLong() -- 32 32 4
t.cluster = data:ReadShort() -- 16 48 6
n = math.max( t.cluster + 1, n )
local d = data:ReadUShort()
t.area = bit.band( d, 0x1FF ) -- 16 64 8
t.flags = bit.rshift( d, 9 ) -- 16 80 10
t.mins = Vector( data:ReadShort(), data:ReadShort(), data:ReadShort() ) -- 16 x 3 ( 48 ) 128 16
t.maxs = Vector( data:ReadShort(), data:ReadShort(), data:ReadShort() ) -- 16 x 3 ( 48 ) 176 22
t.firstleafface = data:ReadUShort() -- 16 192 24
t.numleaffaces = data:ReadUShort() -- 16 208 26
t.firstleafbrush = data:ReadUShort() -- 16 224 28
t.numleafbrushes = data:ReadUShort() -- 16 240 30
t.leafWaterDataID = data:ReadShort() -- 16 256 32
t.__id = i
t.__map = self
t.parentNode = -1
if t.leafWaterDataID > -1 then
t.leafWaterData = self:GetLeafWaterData()[t.leafWaterDataID]
end
setmetatable( t, meta_leaf )
self._leafs[i] = t
end
self._leafs_num_clusters = n
self:ClearLump( 10 )
canGenerateParents = true
makeParents(self, 0, -1, self:GetNodes(), self._leafs, true)
canGenerateParents = false
return self._leafs, n
end
--- Returns a list of LeafWaterData. Holds the data of leaf nodes that are inside water.
--- @return LeafWaterData[]
function meta:GetLeafWaterData()
if self._pLeafWaterData then return self._pLeafWaterData end
local data = self:GetLump( 36 )
self._pLeafWaterData = {}
for i = 0, data:Size() / 80 - 1 do
--- @class LeafWaterData
local t = {}
t.surfaceZ = data:ReadFloat()
t.minZ = data:ReadFloat()
t.surfaceTexInfoID = data:ReadShort()
data:Skip( 2 ) -- A short that is always 0x00
self._pLeafWaterData[i] = t
end
self:ClearLump( 36 )
return self._pLeafWaterData
end
--- Returns the number of leaf-clusters
--- @return number
function meta:GetLeafsNumClusters()
local _, num_clusters = self:GetLeafs()
return num_clusters
end
local mat = Material( "vgui/menu_mode_border" )
local defaultColor = Color( 255, 0, 0, 255 )
--- A simple debug-render function that renders the leaf
--- @CLIENT
--- @param col Color
function meta_leaf:DebugRender( col )
render.SetMaterial( mat )
render.SetBlend( 0.8 )
render.DrawBox( Vector( 0, 0, 0 ), Angle( 0, 0, 0 ), self.maxs, self.mins, col or defaultColor )
render.SetBlend( 1 )
end
--- Returns the leaf index.
--- @return number
function meta_leaf:GetIndex()
return self.__id or -1
end
--- Returns the leaf area.
--- @return number
function meta_leaf:GetArea()
return self.area or -1
end
---In most cases, leafs within the skybox share the same value and are have the cluster id of 0.
-- However older Source versions doesn't have 3D skyboxes and untested on maps without 3D skybox.
--function meta_leaf:In3DSkyBox()
-- return self.cluster == 0
--end
--- Returns true if the leaf has the 3D sky within its PVS.
--- Note: Seems to be broken from EP2 and up.
--- @return boolean
function meta_leaf:HasSkyboxInPVS()
return bit.band( self.flags, 0x1 ) ~= 0
end
--- Returns true if the leaf has the 3D sky within its PVS.
--- Note: Seems to be deprecated. Use Leaf:HasSkyboxInPVS() and BSP:HasSkyBox() instead.
--- @return boolean
function meta_leaf:Has2DSkyboxInPVS()
return bit.band( self.flags, 0x4 ) ~= 0
end
--- Returns true if the leaf has said content
--- @return boolean
function meta_leaf:HasContents( CONTENTS )
if CONTENTS == 0 then return self.contents == CONTENTS end
return bit.band( self.contents, CONTENTS ) ~= 0
end
--- Returns the content flag the leaf has.
--- @return number
function meta_leaf:GetContents()
return self.contents
end
--- Returns a list of faces within this leaf. Starting at 1.
--- Note: A face can be in multiple leafs.
--- @return FaceObject[]
function meta_leaf:GetFaces()
if self._faces then return self._faces end
--- @type FaceObject[]
self._faces = {}
local faces = self.__map:GetFaces()
local leafFace = self.__map:GetLeafFaces()
local c = self.firstleafface
for i = 0, self.numleaffaces do
local f_id = leafFace[ i + c ]
self._faces[i + 1] = faces[f_id]
end
return self._faces
end
--- Returns true if the leaf has water within.
--- @return boolean
function meta_leaf:HasWater()
return self.leafWaterDataID > 0
end
--- Returns the water data, if any.
--- @return table|nil
function meta_leaf:GetWaterData()
return self.leafWaterData
end
--- Returns the water MaxZ within the leaf.
--- @return number|nil
function meta_leaf:GetWaterMaxZ()
return self.leafWaterData and self.leafWaterData.surfaceZ
end
--- Returns the water MinZ within the leaf.
--- @return number|nil
function meta_leaf:GetWaterMinZ()
return self.leafWaterData and self.leafWaterData.minZ
end
--- Returns true if the leaf is outside the map.
--- @return boolean
function meta_leaf:IsOutsideMap()
-- Locations outside the map are always cluster -1. However we check to see if the contnets is solid to be sure.
return self.cluster == -1 and self.contents == 1
end
--- Returns the cluster-number for the leaf. Cluster numbers can be shared between multiple leafs.
--- @return number
function meta_leaf:GetCluster()
return self.cluster
end
--- Returns true if the position is within the given leaf.
--- @param position Vector
--- @return boolean
function meta_leaf:IsPositionWithin( position )
local l = self.__map:PointInLeaf(0, position)
if not l then return false end
return l:GetIndex() == self:GetIndex()
end
--- Returns a list of all leafs around the given leaf.
--- @param range? Number
--- @return table<LeafObject>
function meta_leaf:GetAdjacentLeafs()
local t, i, s = {}, 1, 2
for _, leaf in ipairs( self.__map:AABBInLeafs(0, self.mins, self.maxs, s) ) do
if leaf == self then continue end
t[i] = leaf
i = i + 1
end
return t
end
--- Returns true if the leafs are adjacent to each other.
--- @return bool
function meta_leaf:IsLeafAdjacent( leaf )
for _, c_leaf in ipairs( self:GetAdjacentLeafs() ) do
if c_leaf == leaf then return true end
end
return false
end
--- Roughly returns the distance from leaf to the given position.
--- @param position Vector
--- @return number
function meta_leaf:Distance( position )
local cPos = Vector(clamp(position.x, self.mins.x, self.maxs.x),
clamp(position.y, self.mins.y, self.maxs.y),
clamp(position.z, self.mins.z, self.maxs.z))
return cPos:Distance( position )
end
--- Roughly returns the distance from leaf to the given position.
--- @param position Vector
--- @return number
function meta_leaf:DistToSqr( position )
local cPos = Vector(clamp(position.x, self.mins.x, self.maxs.x),
clamp(position.y, self.mins.y, self.maxs.y),
clamp(position.z, self.mins.z, self.maxs.z))
return cPos:DistToSqr( position )
end
--- Returns a list of planes, pointing into the leaf.
--- @return Plane[]
function meta_leaf:GetBoundaryPlanes()
local nodeIndex = self.parentNode
local list = {}
if not nodeIndex then return t end
local child = -( self:GetIndex() + 1 )
local nodes = self.__map:GetNodes()
while ( nodeIndex >= 0 ) do
local node = nodes[nodeIndex]
local plane = node.plane
if( node.children[1] == child ) then
table.insert(list, plane)
else
table.insert(list, {
dist = -plane.dist,
normal = -plane.normal,
type = plane.type
})
end
child = nodeIndex
nodeIndex = nodes[child].parentNode
end
return list
end
local function locateBoxLeaf( iNode, tab, mins, maxs, nodes, planes, leafs )
local cornerMin, cornerMax = Vector(0,0,0), Vector(0,0,0)
while iNode >= 0 do
local node = nodes[ iNode ]
local plane = planes[ node.planenum ]
for i = 1, 3 do
if( plane.normal[i] >= 0) then
cornerMin[i] = mins[i]
cornerMax[i] = maxs[i]
else
cornerMin[i] = maxs[i]
cornerMax[i] = mins[i]
end
end
if plane.normal:Dot(cornerMax) - plane.dist <= -TEST_EPSILON then
iNode = node.children[2]
elseif plane.normal:Dot(cornerMin) - plane.dist >= TEST_EPSILON then
iNode = node.children[1]
else
if not locateBoxLeaf(node.children[1], tab, mins, maxs, nodes, planes, leafs) then
return false
end
return locateBoxLeaf(node.children[2], tab, mins, maxs, nodes, planes, leafs)
end
end
tab[#tab + 1] = leafs[ -1 -iNode ]
return true
end
--- Returns a list of leafs within the given two positions.
--- @param iNode? number
--- @param point Vector
--- @param point2 Vector
--- @param add? number
--- @return table<LeafObject>
function meta:AABBInLeafs( iNode, point, point2, add )
add = add or 0
local mins = Vector(min(point.x, point2.x) - add, min(point.y, point2.y) - add, min(point.z, point2.z) - add)
local maxs = Vector(max(point.x, point2.x) + add, max(point.y, point2.y) + add, max(point.z, point2.z) + add)
local tab = {}
locateBoxLeaf(iNode or 0, tab, mins, maxs, self:GetNodes(), self:GetPlanes(), self:GetLeafs())
return tab
end
---Returns true if the AABB is outside the map
--- @param position Vector
--- @param position2 Vector
--- @return boolean
function meta:IsAABBOutsideMap( position, position2 )
for _, leaf in pairs( self:AABBInLeafs( 0, position, position2 ) ) do
if leaf:IsOutsideMap() then return true end
end
return false
end
local function locateSphereLeaf( iNode, tab, origin, radius, nodes, planes, leafs)
while iNode >= 0 do
local node = nodes[ iNode ]
local plane = planes[ node.planenum ]
if plane.normal:Dot(origin) + radius - plane.dist <= -TEST_EPSILON then
iNode = node.children[2]
elseif plane.normal:Dot(origin) - radius - plane.dist >= TEST_EPSILON then
iNode = node.children[1]
else
if not locateSphereLeaf( node.children[1], tab, origin, radius, nodes, planes, leafs ) then
return false
end
return locateSphereLeaf( node.children[2], tab, origin, radius, nodes, planes, leafs )
end
end
tab[#tab + 1] = leafs[ -1 -iNode ]
return true
end
--- Returns a list of leafs within the given sphere.
--- @param iNode number
--- @param origin Vector
--- @param radius number
--- @return table<LeafObject>
function meta:SphereInLeafs(iNode, origin, radius)
local tab = {}
locateSphereLeaf(iNode, tab, origin, radius, self:GetNodes(), self:GetPlanes(), self:GetLeafs())
return tab
end
--- Returns true if the sphere is outside the map
--- @param position Vector
--- @param range number
--- @return boolean
function meta:IsSphereOutsideMap( position, range )
for _, leaf in pairs( self:SphereInLeafs( 0, position, range ) ) do
if leaf:IsOutsideMap() then return true end
end
return false
end
--- Returns roughtly the leafs maximum boundary
--- @return Vector
function meta_leaf:OBBMaxs()
return self.maxs
end
--- Returns roughtly the leafs maximum boundary
--- @return Vector
function meta_leaf:OBBMins()
return self.mins
end
--- Returns roughtly the leafs center.
--- @return Vector
function meta_leaf:GetPos()
return (self.mins + self.maxs) / 2
end