Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 159 additions & 0 deletions lua/entities/gmod_wire_customprop/cl_init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
local shared = include("shared.lua")

ENT.DefaultMaterial = Material("models/wireframe")
ENT.Material = ENT.DefaultMaterial

local Ent_IsValid = FindMetaTable("Entity").IsValid
local Phys_IsValid = FindMetaTable("PhysObj").IsValid
local Ent_GetTable = FindMetaTable("Entity").GetTable

function ENT:Initialize()
self.rendermesh = Mesh(self.Material)
self.meshapplied = false
self:DrawShadow(false)
self:EnableCustomCollisions(true)
end

function ENT:OnRemove()
if self.rendermesh then
self.rendermesh:Destroy()
self.rendermesh = nil
end
end

function ENT:BuildPhysics(ent_tbl, physmesh)
ent_tbl.physmesh = physmesh
self:PhysicsInitMultiConvex(physmesh)
self:SetMoveType(MOVETYPE_VPHYSICS)
self:SetSolid(SOLID_VPHYSICS)
self:EnableCustomCollisions(true)

local phys = self:GetPhysicsObject()
if Phys_IsValid(phys) then
phys:SetMaterial(ent_tbl.GetPhysMaterial(self))
end
end

function ENT:BuildRenderMesh(ent_tbl)
local phys = self:GetPhysicsObject()
if not Phys_IsValid(phys) then return end

local convexes = phys:GetMeshConvexes()
local rendermesh = convexes[1]
for i=2, #convexes do
for k, v in ipairs(convexes[i]) do
rendermesh[#rendermesh+1] = v
end
end

-- less than 3 can crash
if #rendermesh < 3 then return end

ent_tbl.rendermesh:BuildFromTriangles(rendermesh)
end

function ENT:Think()
local physobj = self:GetPhysicsObject()
if Phys_IsValid(physobj) then
physobj:SetPos(self:GetPos())
physobj:SetAngles(self:GetAngles())
physobj:EnableMotion(false)
physobj:Sleep()
end
end

function ENT:Draw(flags)
self:DrawModel(flags)
end

function ENT:GetRenderMesh()
local ent_tbl = Ent_GetTable(self)
if ent_tbl.custom_mesh then
if ent_tbl.custom_mesh_data[ent_tbl.custom_mesh] then
return { Mesh = ent_tbl.custom_mesh, Material = ent_tbl.Material }
else
ent_tbl.custom_mesh = nil
end
else
return { Mesh = ent_tbl.rendermesh, Material = ent_tbl.Material }
end
end

local wire_customprops_hullsize_max = GetConVar("wire_customprops_hullsize_max")
local quantMinX, quantMinY, quantMinZ = -wire_customprops_hullsize_max:GetFloat(), -wire_customprops_hullsize_max:GetFloat(), -wire_customprops_hullsize_max:GetFloat()
local quantMaxX, quantMaxY, quantMaxZ = wire_customprops_hullsize_max:GetFloat(), wire_customprops_hullsize_max:GetFloat(), wire_customprops_hullsize_max:GetFloat()
local function streamToMesh(meshdata)
local meshConvexes, posMins, posMaxs = {}, Vector(math.huge, math.huge, math.huge), Vector(-math.huge, -math.huge, -math.huge)

meshdata = util.Decompress(meshdata, 65536)

local pos = 1
local nConvexes
nConvexes, pos = shared.readInt16(meshdata, pos)
for iConvex = 1, nConvexes do
local nVertices
nVertices, pos = shared.readInt16(meshdata, pos)
local convex = {}
for iVertex = 1, nVertices do
local x, y, z
x, pos = shared.readQuantizedFloat16(meshdata, pos, quantMinX, quantMaxX)
y, pos = shared.readQuantizedFloat16(meshdata, pos, quantMinY, quantMaxY)
z, pos = shared.readQuantizedFloat16(meshdata, pos, quantMinZ, quantMaxZ)
if x > posMaxs.x then posMaxs.x = x end
if y > posMaxs.y then posMaxs.y = y end
if z > posMaxs.z then posMaxs.z = z end
if x < posMins.x then posMins.x = x end
if y < posMins.y then posMins.y = y end
if z < posMins.z then posMins.z = z end
convex[iVertex] = Vector(x, y, z)
end
meshConvexes[iConvex] = convex
end

return meshConvexes, posMins, posMaxs
end

net.Receive(shared.classname, function()
local receivedEntity, receivedData

local function tryApplyData()
if not receivedEntity or not receivedData then return end

if Ent_IsValid(receivedEntity) and receivedEntity:GetClass()~=shared.classname then return end
local ent_tbl = Ent_GetTable(receivedEntity)
if not (ent_tbl and ent_tbl.rendermesh:IsValid() and receivedData and not ent_tbl.meshapplied) then return end

ent_tbl.meshapplied = true

local physmesh, mins, maxs = streamToMesh(receivedData)
ent_tbl.BuildPhysics(receivedEntity, ent_tbl, physmesh)
ent_tbl.BuildRenderMesh(receivedEntity, ent_tbl)
receivedEntity:SetRenderBounds(mins, maxs)
receivedEntity:SetCollisionBounds(mins, maxs)
end

shared.readReliableEntity(function(self)
receivedEntity = self
tryApplyData()
end)

net.ReadStream(nil, function(data)
receivedData = data
tryApplyData()
end)
end)

hook.Add("NetworkEntityCreated", shared.classname.."physics", function(ent)
local ent_tbl = Ent_GetTable(ent)
local mesh = ent_tbl.physmesh
if mesh and not Phys_IsValid(ent:GetPhysicsObject()) then
ent_tbl.BuildPhysics(ent, ent_tbl, mesh)
end
end)

function ENT:OnPhysMaterialChanged(name, old, new)
local phys = self:GetPhysicsObject()
if Phys_IsValid(phys) then
phys:SetMaterial(new)
end
end
237 changes: 237 additions & 0 deletions lua/entities/gmod_wire_customprop/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
AddCSLuaFile("cl_init.lua")
AddCSLuaFile("shared.lua")
local shared = include("shared.lua")

util.AddNetworkString(shared.classname)

local ENT_META = FindMetaTable("Entity")
local Ent_GetTable = ENT_META.GetTable

// Reason why there are more max convexes but less max vertices by default is that client's ENT:BuildPhysics is the main bottleneck.
// It seems to require more time exponentially to the vertices amount.
// The same amount of vertices in total, but broken into different convexes greatly reduces the performance hit.
local wire_customprops_hullsize_max = CreateConVar("wire_customprops_hullsize_max", 2048, FCVAR_ARCHIVE, "The max hull size of a custom prop")
local wire_customprops_minvertexdistance = CreateConVar("wire_customprops_minvertexdistance", 0.2, FCVAR_ARCHIVE, "The min distance between two vertices in a custom prop.")
local wire_customprops_vertices_max = CreateConVar("wire_customprops_vertices_max", 64, FCVAR_ARCHIVE, "How many vertices custom props can have.", 4)
local wire_customprops_convexes_max = CreateConVar("wire_customprops_convexes_max", 16, FCVAR_ARCHIVE, "How many convexes custom props can have.", 1)
local wire_customprops_max = CreateConVar("wire_customprops_max", 10, FCVAR_ARCHIVE, "The maximum number of custom props a player can spawn. (0 to disable)", 0)

WireLib = WireLib or {}
WireLib.CustomProp = WireLib.CustomProp or {}

function ENT:Initialize()
self.BaseClass.Initialize(self)

self:PhysicsInitMultiConvex(self.physmesh) self.physmesh = nil
self:SetMoveType(MOVETYPE_VPHYSICS)
self:SetSolid(SOLID_VPHYSICS)
self:EnableCustomCollisions(true)
self:DrawShadow(false)

self.customForceMode = 0
self.customForceLinear = Vector()
self.customForceAngular = Vector()
self.customShadowForce = {
pos = Vector(),
angle = Angle(),
secondstoarrive = 1,
dampfactor = 0.2,
maxangular = 1000,
maxangulardamp = 1000,
maxspeed = 1000,
maxspeeddamp = 1000,
teleportdistance = 1000,
}

self:AddEFlags( EFL_FORCE_CHECK_TRANSMIT )
end

function ENT:EnableCustomPhysics(mode)
local ent_tbl = Ent_GetTable(self)
if mode then
ent_tbl.customPhysicsMode = mode
if not ent_tbl.hasMotionController then
self:StartMotionController()
ent_tbl.hasMotionController = true
end
else
ent_tbl.customPhysicsMode = nil
if ent_tbl.hasMotionController then
self:StopMotionController()
ent_tbl.hasMotionController = false
end
end
end

function ENT:PhysicsSimulate(physObj, dt)
local ent_tbl = Ent_GetTable(self)
local mode = ent_tbl.customPhysicsMode
if mode == 1 then
return ent_tbl.customForceAngular, ent_tbl.customForceLinear, ent_tbl.customForceMode
elseif mode == 2 then
ent_tbl.customShadowForce.deltatime = dt
physObj:ComputeShadowControl(ent_tbl.customShadowForce)
return SIM_NOTHING
else
return SIM_NOTHING
end
end

function ENT:UpdateTransmitState()
return TRANSMIT_ALWAYS
end

function ENT:TransmitData(recip)
net.Start(shared.classname)
shared.writeReliableEntity(self)
local stream = net.WriteStream(self.wiremeshdata, nil, true)

if recip then net.Send(recip) else net.Broadcast() end
return stream
end

hook.Add("PlayerInitialSpawn","CustomProp_SpawnFunc",function(ply)
for k, v in ipairs(ents.FindByClass(shared.classname)) do
v:TransmitData(ply)
end
end)

local function streamToMesh(meshdata)
local maxHullsize = wire_customprops_hullsize_max:GetFloat()
local quantMinX, quantMinY, quantMinZ = -maxHullsize, -maxHullsize, -maxHullsize
local quantMaxX, quantMaxY, quantMaxZ = maxHullsize, maxHullsize, maxHullsize
local maxConvexesPerProp = wire_customprops_convexes_max:GetInt()
local maxVerticesPerConvex = wire_customprops_vertices_max:GetInt()

local meshConvexes = {}
local data = util.Decompress(meshdata, 65536)
if not data or type(data) ~= "string" then return meshConvexes end

local pos = 1
local nConvexes
nConvexes, pos = shared.readInt16(data, pos)
assert(nConvexes <= maxConvexesPerProp, "Exceeded the max convexes per prop (max: " .. maxConvexesPerProp .. ", got: " .. nConvexes .. ")")
for iConvex = 1, nConvexes do
local nVertices
nVertices, pos = shared.readInt16(data, pos)
assert(nVertices <= maxVerticesPerConvex, "Exceeded the max vertices per convex (max: " .. maxVerticesPerConvex .. ", got: " .. nVertices .. ")")
local convex = {}
for iVertex = 1, nVertices do
local x, y, z
x, pos = shared.readQuantizedFloat16(data, pos, quantMinX, quantMaxX)
y, pos = shared.readQuantizedFloat16(data, pos, quantMinY, quantMaxY)
z, pos = shared.readQuantizedFloat16(data, pos, quantMinZ, quantMaxZ)
convex[iVertex] = Vector(x, y, z)
end
meshConvexes[iConvex] = convex
end
return meshConvexes
end

local function meshToStream(meshConvexes)
local maxHullsize = wire_customprops_hullsize_max:GetFloat()
local quantMinX, quantMinY, quantMinZ = -maxHullsize, -maxHullsize, -maxHullsize
local quantMaxX, quantMaxY, quantMaxZ = maxHullsize, maxHullsize, maxHullsize

local buffer = {}

table.insert(buffer, shared.writeInt16(#meshConvexes))
for _, convex in ipairs(meshConvexes) do
table.insert(buffer, shared.writeInt16(#convex))
for _, vertex in ipairs(convex) do
table.insert(buffer, shared.writeQuantizedFloat16(vertex.x, quantMinX, quantMaxX))
table.insert(buffer, shared.writeQuantizedFloat16(vertex.y, quantMinY, quantMaxY))
table.insert(buffer, shared.writeQuantizedFloat16(vertex.z, quantMinZ, quantMaxZ))
end
end

return util.Compress(table.concat(buffer))
end

local function checkMesh(ply, meshConvexes)
local maxHullSize = wire_customprops_hullsize_max:GetFloat()

local mindist = wire_customprops_minvertexdistance:GetFloat()
local maxConvexesPerProp = wire_customprops_convexes_max:GetInt()
local maxVerticesPerConvex = wire_customprops_vertices_max:GetInt()

assert(#meshConvexes > 0, "Invalid number of convexes (" .. #meshConvexes .. ")")
assert(#meshConvexes <= maxConvexesPerProp, "Exceeded the max convexes per prop (max: " .. maxConvexesPerProp .. ", got: ".. #meshConvexes .. ")")

for _, convex in ipairs(meshConvexes) do
assert(#convex <= maxVerticesPerConvex, "Exceeded the max vertices per convex (max: " .. maxVerticesPerConvex .. ", got: " .. #convex .. ")")
assert(#convex > 4, "Invalid number of vertices (" .. #convex .. ")")

for k, vertex in ipairs(convex) do
assert(math.abs(vertex[1]) < maxHullSize and math.abs(vertex[2]) < maxHullSize and math.abs(vertex[3]) < maxHullSize, "The custom prop cannot exceed a hull size of " .. maxHullSize)
assert(vertex[1] == vertex[1] and vertex[2] == vertex[2] and vertex[3] == vertex[3], "Your mesh contains nan values!")
for i = 1, k - 1 do
assert(convex[i]:DistToSqr(vertex) >= mindist, "No two vertices can have a distance less than " .. math.sqrt(mindist))
end
end
end
end

function WireLib.CustomProp.CanSpawn(ply)
ply.WireCustomPropsSpawned = ply.WireCustomPropsSpawned or 0
return ply.WireCustomPropsSpawned < wire_customprops_max:GetInt()
end

function WireLib.CustomProp.Create(ply, pos, ang, wiremeshdata)
if not WireLib.CustomProp.CanSpawn(ply) then return nil, "Max amount of custom props spawned for this player reached!" end

local meshConvexes, meshStream

if isstring(wiremeshdata) then
meshConvexes = streamToMesh(wiremeshdata)
meshStream = wiremeshdata
elseif istable(wiremeshdata) then
meshConvexes = wiremeshdata
meshStream = meshToStream(wiremeshdata)
else
assert(false, "Invalid meshdata")
end

checkMesh(self, meshConvexes)

local propent = ents.Create(shared.classname)
propent.physmesh = meshConvexes

propent.wiremeshdata = meshStream
propent:Spawn()

local physobj = propent:GetPhysicsObject()
if not physobj:IsValid() then
propent:Remove()
assert(false, "Custom prop has invalid physics!")
end

propent:SetPos(pos)
propent:SetAngles(ang)
propent:TransmitData()

physobj:EnableCollisions(true)
physobj:EnableDrag(true)
physobj:Wake()

gamemode.Call("PlayerSpawnedSENT", ply, propent)

local totalVertices = 0
for k, v in ipairs(meshConvexes) do
totalVertices = totalVertices + #v
end

propent:CallOnRemove("wire_customprop_remove",
function(propent)
if IsValid(ply) then
ply.WireCustomPropsSpawned = (ply.WireCustomPropsSpawned or 1) - 1
end
end
)

ply.WireCustomPropsSpawned = (ply.WireCustomPropsSpawned or 0) + 1

return propent
end

duplicator.RegisterEntityClass(shared.classname, WireLib.CustomProp.Create, "Pos", "Ang", "wiremeshdata")
Loading
Loading