mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-16 21:33:46 +03:00
445 lines
13 KiB
Lua
445 lines
13 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 = string.format
|
|
|
|
--- @class BSPObject
|
|
local meta = NikNaks.__metatables["BSP"]
|
|
|
|
--- @class FaceObject
|
|
local meta_face = {}
|
|
meta_face.__index = meta_face
|
|
meta_face.__tostring = function( self ) return format( obj_tostring, "Faces", self.__id ) end
|
|
meta_face.MetaName = "BSP Faces"
|
|
NikNaks.__metatables["BSP Faces"] = meta_face
|
|
|
|
local MAX_MAP_FACES = 65536
|
|
|
|
--- Returns all faces. ( Warning, uses a lot of memory )
|
|
--- @return FaceObject[]
|
|
function meta:GetFaces()
|
|
if self._faces then return self._faces end
|
|
self._faces = {}
|
|
|
|
local data = self:GetLump( 7 )
|
|
for i = 0, math.min( data:Size() / 448, MAX_MAP_FACES ) - 1 do
|
|
--- @class FaceObject
|
|
local t = {}
|
|
t.planenum = data:ReadUShort()
|
|
t.plane = self:GetPlanes()[ t.planenum ]
|
|
t.side = data:ReadByte() -- 1 = same direciton as face
|
|
t.onNode = data:ReadByte() -- 1 if on node, 0 if in leaf
|
|
t.firstedge = data:ReadLong()
|
|
t.numedges = data:ReadShort()
|
|
t.texinfo = data:ReadShort() -- Texture info
|
|
t.dispinfo = data:ReadShort() -- Displacement info
|
|
t.surfaceFogVolumeID = data:ReadShort()
|
|
t.styles = { data:ReadByte(), data:ReadByte(), data:ReadByte(), data:ReadByte() }
|
|
t.lightofs = data:ReadLong()
|
|
t.area = data:ReadFloat()
|
|
t.LightmapTextureMinsInLuxels = { data:ReadLong(), data:ReadLong() }
|
|
t.LightmapTextureSizeInLuxels = { data:ReadLong(), data:ReadLong() }
|
|
t.origFace = data:ReadLong()
|
|
t.numPrims = data:ReadUShort()
|
|
t.firstPrimID = data:ReadUShort()
|
|
t.smoothingGroups = data:ReadULong()
|
|
t.__bmodel = self:FindBModelIDByFaceIndex( i )
|
|
t.__map = self
|
|
t.__id = i
|
|
setmetatable( t, meta_face )
|
|
self._faces[i] = t
|
|
end
|
|
|
|
self:ClearLump( 7 )
|
|
return self._faces
|
|
end
|
|
|
|
-- Returns the original face
|
|
function meta:GetOriginalFace()
|
|
return self.__map:GetOriginalFaces()[self.origFace]
|
|
end
|
|
|
|
-- We make a small hack to cache and get the entities using brush-models.
|
|
local function __findEntityUsingBrush( self )
|
|
if self.__funcBrush then return self.__funcBrush end
|
|
|
|
local entities = self:GetEntities()
|
|
self.__funcBrush = { [0] = entities[0] }
|
|
|
|
for _, v in pairs( entities ) do
|
|
local numMdl = string.match( v.model or "", "*([%d]+)" )
|
|
|
|
if numMdl then
|
|
self.__funcBrush[tonumber( numMdl )] = v
|
|
end
|
|
end
|
|
|
|
return self.__funcBrush
|
|
end
|
|
|
|
--- Parses a Color object from the given BitBuffer
|
|
--- @param data BitBuffer
|
|
--- @return Color
|
|
local function __readColorRGBExp32 ( data )
|
|
return NikNaks.ColorRGBExp32ToColor( {
|
|
r = data:ReadByte(),
|
|
g = data:ReadByte(),
|
|
b = data:ReadByte(),
|
|
exponent = data:ReadSignedByte()
|
|
} )
|
|
end
|
|
|
|
--- Returns the lightmap samples for the face.
|
|
--- @return table<string, LightmapSample[]>?
|
|
function meta_face:GetLightmapSamples()
|
|
local lightofs = self.lightofs
|
|
|
|
if lightofs == -1 then return end
|
|
if self._lightmap_samples then return self._lightmap_samples end
|
|
|
|
--- @class LightmapSample
|
|
--- @field color Color
|
|
--- @field exponent number
|
|
|
|
--- @type LightmapSample[]
|
|
local full = {}
|
|
|
|
--- @type LightmapSample[]
|
|
local average = {}
|
|
|
|
local samples = { average = average, full = full }
|
|
self._lightmap_samples = samples
|
|
|
|
local has_bumpmap = self:GetMaterial():GetString( "$bumpmap" ) ~= nil
|
|
local luxel_count = ( self.LightmapTextureSizeInLuxels[1] + 1 ) * ( self.LightmapTextureSizeInLuxels[2] + 1 )
|
|
|
|
local lightstyle_count = 0
|
|
for _, v in ipairs( self.styles ) do
|
|
if v ~= 255 then lightstyle_count = lightstyle_count + 1 end
|
|
end
|
|
|
|
-- "For faces with bumpmapped textures, there are four times the usual number of lightmap samples"
|
|
local sample_count = lightstyle_count * luxel_count
|
|
if has_bumpmap then sample_count = sample_count * 4 end
|
|
|
|
local data = self.__map:GetLump( 8 )
|
|
|
|
-- Get the average samples
|
|
-- "Immediately preceeding the lightofs-referenced sample group,
|
|
-- there are single samples containing the average lighting on the face, one for each lightstyle,
|
|
-- in reverse order from that given in the styles[] array."
|
|
local color, exponent
|
|
data:Seek( ( lightofs * 8 ) - ( 32 * lightstyle_count ) )
|
|
for _ = 1, lightstyle_count do
|
|
color, exponent = __readColorRGBExp32( data )
|
|
table.insert( average, 1, { color = color, exponent = exponent } )
|
|
end
|
|
|
|
-- Get the full samples
|
|
for _ = 1, sample_count do
|
|
color, exponent = __readColorRGBExp32( data )
|
|
table.insert( full, { color = color, exponent = exponent } )
|
|
end
|
|
|
|
return samples
|
|
end
|
|
|
|
|
|
--- Returns the face-index.
|
|
--- @return number
|
|
function meta_face:GetIndex()
|
|
return self.__id or -1
|
|
end
|
|
|
|
--- Returns the normal for the face
|
|
--- @return Vector
|
|
function meta_face:GetNormal()
|
|
return self.plane.normal
|
|
end
|
|
|
|
--- Returns the texture info for the face.
|
|
--- @return table
|
|
function meta_face:GetTexInfo()
|
|
return self.__map:GetTexInfo()[self.texinfo]
|
|
end
|
|
|
|
--- Returns the texture data for the face.
|
|
--- @return table
|
|
function meta_face:GetTexData()
|
|
return self.__map:GetTexData()[ self:GetTexInfo().texdata ]
|
|
end
|
|
|
|
--- Returns the texture for the face.
|
|
function meta_face:GetTexture()
|
|
return self:GetTexData().nameStringTableID
|
|
end
|
|
|
|
--- Returns the material the face use. Note: Materials within the BSP is not loaded.
|
|
--- @return IMaterial
|
|
function meta_face:GetMaterial()
|
|
if self._mat then return self._mat end
|
|
self._mat = Material( self:GetTexture() or "__error" )
|
|
return self._mat
|
|
end
|
|
|
|
--- Returns true if the face should render.
|
|
--- @return boolean
|
|
function meta_face:ShouldRender()
|
|
local texinfo = self:GetTexInfo()
|
|
local flags = texinfo and texinfo.flags or 0
|
|
return bit.band( flags, 0x80 ) == 0 and bit.band( flags, 0x200 ) == 0
|
|
end
|
|
|
|
--- Returns true if the face-texture is translucent
|
|
--- @return boolean
|
|
function meta_face:IsTranslucent()
|
|
local texinfo = self:GetTexInfo() or 0
|
|
return bit.band( texinfo.flags, 0x10 ) ~= 0
|
|
end
|
|
|
|
--- Returns true if the face is part of 2D skybox.
|
|
--- @return boolean
|
|
function meta_face:IsSkyBox()
|
|
local texinfo = self:GetTexInfo() or 0
|
|
return bit.band( texinfo.flags, 0x2 ) ~= 0
|
|
end
|
|
|
|
--- Returns true if the face is part of 3D skybox.
|
|
--- @return boolean
|
|
function meta_face:IsSkyBox3D()
|
|
local texinfo = self:GetTexInfo() or 0
|
|
return bit.band( texinfo.flags, 0x4 ) ~= 0
|
|
end
|
|
|
|
--- Returns true if the face's texinfo has said flag.
|
|
--- @return boolean
|
|
function meta_face:HasTexInfoFlag( flag )
|
|
local texinfo = self:GetTexInfo() or 0
|
|
return bit.band( texinfo.flags, flag ) ~= 0
|
|
end
|
|
|
|
--- Returns true if the face is part of the world and not another entity.
|
|
--- @return boolean
|
|
function meta_face:IsWorld()
|
|
return self.__bmodel == 0
|
|
end
|
|
|
|
--- Returns the BModel the face has. 0 if it is part of the world.
|
|
--- @return number
|
|
function meta_face:GetBModel()
|
|
return self.__bmodel
|
|
end
|
|
|
|
--- Returns the entity-object-data that is part of this face.
|
|
--- @return string EntityData
|
|
function meta_face:GetEntity()
|
|
return __findEntityUsingBrush( self.__map )[self.__bmodel]
|
|
end
|
|
|
|
-- Displacments TODO: Fix Displacment Position and Data
|
|
|
|
--- Returns true if the face is part of Displacment
|
|
--- @return boolean
|
|
function meta_face:IsDisplacment()
|
|
return self.dispinfo > -1
|
|
end
|
|
|
|
--- Returns the vertex positions for the face. [Not Cached]
|
|
--- Note this will ignore BModel-positions!
|
|
--- @return table
|
|
function meta_face:GetVertexs()
|
|
if self._vertex then return self._vertex end
|
|
local t = {}
|
|
if not self:IsDisplacment() then
|
|
|
|
for i = 0, self.numedges - 1 do
|
|
t[i + 1] = self.__map:GetSurfEdgesIndex( self.firstedge + i )
|
|
end
|
|
|
|
return t
|
|
end
|
|
|
|
-- This is a displacment
|
|
-- TODO: Calculate the displacment mesh and return it here
|
|
--local dispVertStart =
|
|
end
|
|
|
|
--- Returns a table in form of a polygon-mesh. [Not Cached]
|
|
--- @return PolygonMeshVertex[]
|
|
function meta_face:GenerateVertexData()
|
|
--- @type PolygonMeshVertex[]
|
|
local t = {}
|
|
local tv = self:GetTexInfo().textureVects
|
|
local lv = self:GetTexInfo().lightmapVecs
|
|
local texdata = self:GetTexData()
|
|
local mat_w, mat_h = texdata.view_width, texdata.view_height
|
|
local n = self:GetNormal()
|
|
|
|
-- Move the faces to match func_brushes (If any)
|
|
local bNum = self.__bmodel
|
|
local exPos, exAng
|
|
if bNum > 0 then
|
|
-- Get funch_brushes and their location
|
|
local func_brush = __findEntityUsingBrush( self.__map )[bNum]
|
|
if func_brush then
|
|
exPos = func_brush.origin
|
|
exAng = func_brush.angles
|
|
end
|
|
end
|
|
|
|
local luxelW = self.LightmapTextureSizeInLuxels[1] + 1
|
|
local luxelH = self.LightmapTextureSizeInLuxels[2] + 1
|
|
|
|
for i = 0, self.numedges - 1 do
|
|
--- @class PolygonMeshVertex
|
|
local vert = {}
|
|
local a = self.__map:GetSurfEdgesIndex( self.firstedge + i )
|
|
vert.pos = a
|
|
if bNum > 0 then -- WorldPos -> Entity Brush
|
|
a = WorldToLocal( a, Angle( 0, 0, 0 ), Vector( 0, 0, 0 ), exAng )
|
|
vert.pos = a + exPos
|
|
end
|
|
|
|
vert.normal = n
|
|
-- UV & LV
|
|
vert.u = ( tv[0][0] * a.x + tv[0][1] * a.y + tv[0][2] * a.z + tv[0][3] ) / mat_w
|
|
vert.v = ( tv[1][0] * a.x + tv[1][1] * a.y + tv[1][2] * a.z + tv[1][3] ) / mat_h
|
|
|
|
vert.lu = ( ( lv[0][0] * a.x + lv[0][1] * a.y + lv[0][2] * a.z + lv[0][3] ) - self.LightmapTextureMinsInLuxels[1] ) / luxelW
|
|
vert.lv = ( ( lv[1][0] * a.x + lv[1][1] * a.y + lv[1][2] * a.z + lv[1][3] ) - self.LightmapTextureMinsInLuxels[2] ) / luxelH
|
|
|
|
vert.userdata = { 0, 0, 0, 0 } -- Todo: Calculate this?
|
|
|
|
t[i + 1] = vert
|
|
end
|
|
|
|
return t
|
|
end
|
|
|
|
--- @return PolygonMeshVertex[]?
|
|
local function PolyChop( o_vert )
|
|
local vert = {}
|
|
if #o_vert < 3 then return end
|
|
|
|
local n = 1
|
|
local triCount = #o_vert - 2
|
|
|
|
for i = 1, triCount do
|
|
vert[n] = o_vert[1]
|
|
vert[n + 1] = o_vert[i + 1]
|
|
vert[n + 2] = o_vert[i + 2]
|
|
n = n + 3
|
|
end
|
|
|
|
return vert
|
|
end
|
|
|
|
---Returns a table in form of a polygon-mesh for triangles. [Not Cached]
|
|
---@return PolygonMeshVertex[]?
|
|
function meta_face:GenerateVertexTriangleData()
|
|
if self._vertTriangleData then return self._vertTriangleData end
|
|
self._vertTriangleData = PolyChop( self:GenerateVertexData() )
|
|
return self._vertTriangleData
|
|
end
|
|
|
|
--- All mesh-data regarding said face. Should use face:GenerateVertexTriangleData intead!
|
|
--- @return table
|
|
function meta_face:GenerateMeshData()
|
|
--- @class PolygonMeshData
|
|
local t = {}
|
|
t.verticies = self:GenerateVertexData()
|
|
t.triangles = PolyChop( t.verticies )
|
|
t.material = self:GetTexture()
|
|
return {t}
|
|
end
|
|
|
|
if CLIENT then
|
|
--- @type IMesh[]
|
|
NIKNAKS_TABOMESH = NIKNAKS_TABOMESH or {}
|
|
|
|
--- Builds the mesh if face has none.
|
|
--- @return IMesh|boolean?
|
|
function meta_face:BuildMesh()
|
|
if SERVER then return end
|
|
if self._mesh then return self._mesh end
|
|
|
|
-- Tex
|
|
local texinfo = self:GetTexInfo()
|
|
if bit.band( texinfo.flags, 0x80 ) ~= 0 or bit.band( texinfo.flags, 0x200 ) ~= 0 then
|
|
self._mesh = false
|
|
return self._mesh
|
|
end
|
|
|
|
local meshData = self:GenerateVertexTriangleData()
|
|
if not meshData then return self._mesh end
|
|
|
|
self._mesh = Mesh( self:GetMaterial() )
|
|
|
|
-- Vert
|
|
mesh.Begin( self._mesh, MATERIAL_TRIANGLES, #meshData )
|
|
for i = 1, #meshData do
|
|
local vert = meshData[i]
|
|
-- > Mesh
|
|
mesh.Normal( vert.normal )
|
|
mesh.Position( vert.pos ) -- Set the position
|
|
mesh.Color(col.r, col.g, col.b, col.a)
|
|
mesh.TexCoord( 0, vert.u, vert.v ) -- Set the texture UV coordinates
|
|
mesh.TexCoord( 1, vert.lu, vert.lv ) -- Set the lightmap UV coordinates
|
|
mesh.TexCoord( 2, vert.lu, vert.lv ) -- Set the lightmap UV coordinates
|
|
--mesh.TexCoord( 2, self.LightmapTextureSizeInLuxels[1], self.LightmapTextureSizeInLuxels[2] ) -- Set the texture UV coordinates
|
|
--mesh.TexCoord( 2, self.LightmapTextureMinsInLuxels[1], self.LightmapTextureMinsInLuxels[2] ) -- Set the texture UV coordinates
|
|
mesh.AdvanceVertex()
|
|
end
|
|
|
|
mesh.End()
|
|
|
|
table.insert( NIKNAKS_TABOMESH, self._mesh )
|
|
return self._mesh
|
|
end
|
|
|
|
--- Returns the mesh generated for the face.
|
|
--- Note. Need to call face:BuildMesh first.
|
|
--- @return IMesh|boolean?
|
|
function meta_face:GetMesh()
|
|
return self._mesh
|
|
end
|
|
|
|
--- Deletes the mesh generated for the face.
|
|
--- @return self
|
|
function meta_face:DeleteMesh()
|
|
if not self._mesh then return end
|
|
self._mesh:Destroy()
|
|
self._mesh = nil
|
|
return self
|
|
end
|
|
|
|
--- Generates a mesh for the face and renders it.
|
|
--- @param dontGenerate boolean
|
|
--- @return boolean? didGenerateMesh
|
|
function meta_face:DebugRender( dontGenerate )
|
|
local _mesh = self:GetMesh()
|
|
if not _mesh and dontGenerate then return false end
|
|
_mesh = _mesh or self:BuildMesh()
|
|
if not IsValid( _mesh ) then return end
|
|
render.SetMaterial( self:GetMaterial() )
|
|
_mesh:Draw()
|
|
return true
|
|
end
|
|
|
|
for _, _mesh in pairs( NIKNAKS_TABOMESH ) do
|
|
if IsValid( _mesh ) then _mesh:Destroy() end
|
|
end
|
|
end
|