mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-17 21:53:46 +03:00
Upload
This commit is contained in:
487
lua/niknaks/modules/sh_bsp_leafs.lua
Normal file
487
lua/niknaks/modules/sh_bsp_leafs.lua
Normal file
@@ -0,0 +1,487 @@
|
||||
--[[
|
||||
| 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
|
||||
Reference in New Issue
Block a user