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