mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-16 21:33:46 +03:00
487 lines
14 KiB
Lua
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 |