Skip to content

Commit aa54e88

Browse files
authored
Add gmod_wire_customprop (#3451)
* stable * cl descriptions + few util funcs * owner fix * move to 16 bit net * move to 16 bit, change limits, fix duplicator func * Use tabs instead of spaces * change limits, remove delay * Add convexes structure checks, add dynamic OPS cost * Remove spaces, use tabs * Remove unused var * Revert out of scope changes * Use table.insert instead at init.lua * Change "//" to "--" * Increase default wire_customprops_max limit to 16 (bit-nice value) * Lower the default convexes limit * Throw NULLs instead if nils
1 parent f0d26b3 commit aa54e88

File tree

5 files changed

+643
-1
lines changed

5 files changed

+643
-1
lines changed
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
local shared = include("shared.lua")
2+
3+
ENT.DefaultMaterial = Material("models/wireframe")
4+
ENT.Material = ENT.DefaultMaterial
5+
6+
local Ent_IsValid = FindMetaTable("Entity").IsValid
7+
local Phys_IsValid = FindMetaTable("PhysObj").IsValid
8+
local Ent_GetTable = FindMetaTable("Entity").GetTable
9+
10+
function ENT:Initialize()
11+
self.rendermesh = Mesh(self.Material)
12+
self.meshapplied = false
13+
self:DrawShadow(false)
14+
self:EnableCustomCollisions(true)
15+
end
16+
17+
function ENT:OnRemove()
18+
if self.rendermesh then
19+
self.rendermesh:Destroy()
20+
self.rendermesh = nil
21+
end
22+
end
23+
24+
function ENT:BuildPhysics(ent_tbl, physmesh)
25+
ent_tbl.physmesh = physmesh
26+
self:PhysicsInitMultiConvex(physmesh)
27+
self:SetMoveType(MOVETYPE_VPHYSICS)
28+
self:SetSolid(SOLID_VPHYSICS)
29+
self:EnableCustomCollisions(true)
30+
31+
local phys = self:GetPhysicsObject()
32+
if Phys_IsValid(phys) then
33+
phys:SetMaterial(ent_tbl.GetPhysMaterial(self))
34+
end
35+
end
36+
37+
function ENT:BuildRenderMesh(ent_tbl)
38+
local phys = self:GetPhysicsObject()
39+
if not Phys_IsValid(phys) then return end
40+
41+
local convexes = phys:GetMeshConvexes()
42+
local rendermesh = convexes[1]
43+
for i=2, #convexes do
44+
for k, v in ipairs(convexes[i]) do
45+
rendermesh[#rendermesh+1] = v
46+
end
47+
end
48+
49+
-- less than 3 can crash
50+
if #rendermesh < 3 then return end
51+
52+
ent_tbl.rendermesh:BuildFromTriangles(rendermesh)
53+
end
54+
55+
function ENT:Think()
56+
local physobj = self:GetPhysicsObject()
57+
if Phys_IsValid(physobj) then
58+
physobj:SetPos(self:GetPos())
59+
physobj:SetAngles(self:GetAngles())
60+
physobj:EnableMotion(false)
61+
physobj:Sleep()
62+
end
63+
end
64+
65+
function ENT:Draw(flags)
66+
self:DrawModel(flags)
67+
end
68+
69+
function ENT:GetRenderMesh()
70+
local ent_tbl = Ent_GetTable(self)
71+
if ent_tbl.custom_mesh then
72+
if ent_tbl.custom_mesh_data[ent_tbl.custom_mesh] then
73+
return { Mesh = ent_tbl.custom_mesh, Material = ent_tbl.Material }
74+
else
75+
ent_tbl.custom_mesh = nil
76+
end
77+
else
78+
return { Mesh = ent_tbl.rendermesh, Material = ent_tbl.Material }
79+
end
80+
end
81+
82+
local wire_customprops_hullsize_max = GetConVar("wire_customprops_hullsize_max")
83+
local quantMinX, quantMinY, quantMinZ = -wire_customprops_hullsize_max:GetFloat(), -wire_customprops_hullsize_max:GetFloat(), -wire_customprops_hullsize_max:GetFloat()
84+
local quantMaxX, quantMaxY, quantMaxZ = wire_customprops_hullsize_max:GetFloat(), wire_customprops_hullsize_max:GetFloat(), wire_customprops_hullsize_max:GetFloat()
85+
local function streamToMesh(meshdata)
86+
local meshConvexes, posMins, posMaxs = {}, Vector(math.huge, math.huge, math.huge), Vector(-math.huge, -math.huge, -math.huge)
87+
88+
meshdata = util.Decompress(meshdata, 65536)
89+
90+
local pos = 1
91+
local nConvexes
92+
nConvexes, pos = shared.readInt16(meshdata, pos)
93+
for iConvex = 1, nConvexes do
94+
local nVertices
95+
nVertices, pos = shared.readInt16(meshdata, pos)
96+
local convex = {}
97+
for iVertex = 1, nVertices do
98+
local x, y, z
99+
x, pos = shared.readQuantizedFloat16(meshdata, pos, quantMinX, quantMaxX)
100+
y, pos = shared.readQuantizedFloat16(meshdata, pos, quantMinY, quantMaxY)
101+
z, pos = shared.readQuantizedFloat16(meshdata, pos, quantMinZ, quantMaxZ)
102+
if x > posMaxs.x then posMaxs.x = x end
103+
if y > posMaxs.y then posMaxs.y = y end
104+
if z > posMaxs.z then posMaxs.z = z end
105+
if x < posMins.x then posMins.x = x end
106+
if y < posMins.y then posMins.y = y end
107+
if z < posMins.z then posMins.z = z end
108+
convex[iVertex] = Vector(x, y, z)
109+
end
110+
meshConvexes[iConvex] = convex
111+
end
112+
113+
return meshConvexes, posMins, posMaxs
114+
end
115+
116+
net.Receive(shared.classname, function()
117+
local receivedEntity, receivedData
118+
119+
local function tryApplyData()
120+
if not receivedEntity or not receivedData then return end
121+
122+
if Ent_IsValid(receivedEntity) and receivedEntity:GetClass()~=shared.classname then return end
123+
local ent_tbl = Ent_GetTable(receivedEntity)
124+
if not (ent_tbl and ent_tbl.rendermesh:IsValid() and receivedData and not ent_tbl.meshapplied) then return end
125+
126+
ent_tbl.meshapplied = true
127+
128+
local physmesh, mins, maxs = streamToMesh(receivedData)
129+
ent_tbl.BuildPhysics(receivedEntity, ent_tbl, physmesh)
130+
ent_tbl.BuildRenderMesh(receivedEntity, ent_tbl)
131+
receivedEntity:SetRenderBounds(mins, maxs)
132+
receivedEntity:SetCollisionBounds(mins, maxs)
133+
end
134+
135+
shared.readReliableEntity(function(self)
136+
receivedEntity = self
137+
tryApplyData()
138+
end)
139+
140+
net.ReadStream(nil, function(data)
141+
receivedData = data
142+
tryApplyData()
143+
end)
144+
end)
145+
146+
hook.Add("NetworkEntityCreated", shared.classname.."physics", function(ent)
147+
local ent_tbl = Ent_GetTable(ent)
148+
local mesh = ent_tbl.physmesh
149+
if mesh and not Phys_IsValid(ent:GetPhysicsObject()) then
150+
ent_tbl.BuildPhysics(ent, ent_tbl, mesh)
151+
end
152+
end)
153+
154+
function ENT:OnPhysMaterialChanged(name, old, new)
155+
local phys = self:GetPhysicsObject()
156+
if Phys_IsValid(phys) then
157+
phys:SetMaterial(new)
158+
end
159+
end
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
AddCSLuaFile("cl_init.lua")
2+
AddCSLuaFile("shared.lua")
3+
local shared = include("shared.lua")
4+
5+
util.AddNetworkString(shared.classname)
6+
7+
local ENT_META = FindMetaTable("Entity")
8+
local Ent_GetTable = ENT_META.GetTable
9+
10+
-- Reason why there are more max convexes but less max vertices by default is that client's ENT:BuildPhysics is the main bottleneck.
11+
-- It seems to require more time exponentially to the vertices amount.
12+
-- The same amount of vertices in total, but broken into different convexes greatly reduces the performance hit.
13+
local wire_customprops_hullsize_max = CreateConVar("wire_customprops_hullsize_max", 2048, FCVAR_ARCHIVE, "The max hull size of a custom prop")
14+
local wire_customprops_minvertexdistance = CreateConVar("wire_customprops_minvertexdistance", 0.2, FCVAR_ARCHIVE, "The min distance between two vertices in a custom prop.")
15+
local wire_customprops_vertices_max = CreateConVar("wire_customprops_vertices_max", 64, FCVAR_ARCHIVE, "How many vertices custom props can have.", 4)
16+
local wire_customprops_convexes_max = CreateConVar("wire_customprops_convexes_max", 12, FCVAR_ARCHIVE, "How many convexes custom props can have.", 1)
17+
local wire_customprops_max = CreateConVar("wire_customprops_max", 16, FCVAR_ARCHIVE, "The maximum number of custom props a player can spawn. (0 to disable)", 0)
18+
19+
WireLib = WireLib or {}
20+
WireLib.CustomProp = WireLib.CustomProp or {}
21+
22+
function ENT:Initialize()
23+
self.BaseClass.Initialize(self)
24+
25+
self:PhysicsInitMultiConvex(self.physmesh) self.physmesh = nil
26+
self:SetMoveType(MOVETYPE_VPHYSICS)
27+
self:SetSolid(SOLID_VPHYSICS)
28+
self:EnableCustomCollisions(true)
29+
self:DrawShadow(false)
30+
31+
self.customForceMode = 0
32+
self.customForceLinear = Vector()
33+
self.customForceAngular = Vector()
34+
self.customShadowForce = {
35+
pos = Vector(),
36+
angle = Angle(),
37+
secondstoarrive = 1,
38+
dampfactor = 0.2,
39+
maxangular = 1000,
40+
maxangulardamp = 1000,
41+
maxspeed = 1000,
42+
maxspeeddamp = 1000,
43+
teleportdistance = 1000,
44+
}
45+
46+
self:AddEFlags( EFL_FORCE_CHECK_TRANSMIT )
47+
end
48+
49+
function ENT:EnableCustomPhysics(mode)
50+
local ent_tbl = Ent_GetTable(self)
51+
if mode then
52+
ent_tbl.customPhysicsMode = mode
53+
if not ent_tbl.hasMotionController then
54+
self:StartMotionController()
55+
ent_tbl.hasMotionController = true
56+
end
57+
else
58+
ent_tbl.customPhysicsMode = nil
59+
if ent_tbl.hasMotionController then
60+
self:StopMotionController()
61+
ent_tbl.hasMotionController = false
62+
end
63+
end
64+
end
65+
66+
function ENT:PhysicsSimulate(physObj, dt)
67+
local ent_tbl = Ent_GetTable(self)
68+
local mode = ent_tbl.customPhysicsMode
69+
if mode == 1 then
70+
return ent_tbl.customForceAngular, ent_tbl.customForceLinear, ent_tbl.customForceMode
71+
elseif mode == 2 then
72+
ent_tbl.customShadowForce.deltatime = dt
73+
physObj:ComputeShadowControl(ent_tbl.customShadowForce)
74+
return SIM_NOTHING
75+
else
76+
return SIM_NOTHING
77+
end
78+
end
79+
80+
function ENT:UpdateTransmitState()
81+
return TRANSMIT_ALWAYS
82+
end
83+
84+
function ENT:TransmitData(recip)
85+
net.Start(shared.classname)
86+
shared.writeReliableEntity(self)
87+
local stream = net.WriteStream(self.wiremeshdata, nil, true)
88+
89+
if recip then net.Send(recip) else net.Broadcast() end
90+
return stream
91+
end
92+
93+
hook.Add("PlayerInitialSpawn","CustomProp_SpawnFunc",function(ply)
94+
for k, v in ipairs(ents.FindByClass(shared.classname)) do
95+
v:TransmitData(ply)
96+
end
97+
end)
98+
99+
local function streamToMesh(meshdata)
100+
local maxHullsize = wire_customprops_hullsize_max:GetFloat()
101+
local quantMinX, quantMinY, quantMinZ = -maxHullsize, -maxHullsize, -maxHullsize
102+
local quantMaxX, quantMaxY, quantMaxZ = maxHullsize, maxHullsize, maxHullsize
103+
local maxConvexesPerProp = wire_customprops_convexes_max:GetInt()
104+
local maxVerticesPerConvex = wire_customprops_vertices_max:GetInt()
105+
106+
local meshConvexes = {}
107+
local data = util.Decompress(meshdata, 65536)
108+
if not data or type(data) ~= "string" then return meshConvexes end
109+
110+
local pos = 1
111+
local nConvexes
112+
nConvexes, pos = shared.readInt16(data, pos)
113+
assert(nConvexes <= maxConvexesPerProp, "Exceeded the max convexes per prop (max: " .. maxConvexesPerProp .. ", got: " .. nConvexes .. ")")
114+
for iConvex = 1, nConvexes do
115+
local nVertices
116+
nVertices, pos = shared.readInt16(data, pos)
117+
assert(nVertices <= maxVerticesPerConvex, "Exceeded the max vertices per convex (max: " .. maxVerticesPerConvex .. ", got: " .. nVertices .. ")")
118+
local convex = {}
119+
for iVertex = 1, nVertices do
120+
local x, y, z
121+
x, pos = shared.readQuantizedFloat16(data, pos, quantMinX, quantMaxX)
122+
y, pos = shared.readQuantizedFloat16(data, pos, quantMinY, quantMaxY)
123+
z, pos = shared.readQuantizedFloat16(data, pos, quantMinZ, quantMaxZ)
124+
convex[iVertex] = Vector(x, y, z)
125+
end
126+
meshConvexes[iConvex] = convex
127+
end
128+
return meshConvexes
129+
end
130+
131+
local function meshToStream(meshConvexes)
132+
local maxHullsize = wire_customprops_hullsize_max:GetFloat()
133+
local quantMinX, quantMinY, quantMinZ = -maxHullsize, -maxHullsize, -maxHullsize
134+
local quantMaxX, quantMaxY, quantMaxZ = maxHullsize, maxHullsize, maxHullsize
135+
136+
local buffer = {}
137+
138+
table.insert(buffer, shared.writeInt16(#meshConvexes))
139+
for _, convex in ipairs(meshConvexes) do
140+
table.insert(buffer, shared.writeInt16(#convex))
141+
for _, vertex in ipairs(convex) do
142+
table.insert(buffer, shared.writeQuantizedFloat16(vertex.x, quantMinX, quantMaxX))
143+
table.insert(buffer, shared.writeQuantizedFloat16(vertex.y, quantMinY, quantMaxY))
144+
table.insert(buffer, shared.writeQuantizedFloat16(vertex.z, quantMinZ, quantMaxZ))
145+
end
146+
end
147+
148+
return util.Compress(table.concat(buffer))
149+
end
150+
151+
local function checkMesh(ply, meshConvexes)
152+
local maxHullSize = wire_customprops_hullsize_max:GetFloat()
153+
154+
local mindist = wire_customprops_minvertexdistance:GetFloat()
155+
local maxConvexesPerProp = wire_customprops_convexes_max:GetInt()
156+
local maxVerticesPerConvex = wire_customprops_vertices_max:GetInt()
157+
158+
assert(#meshConvexes > 0, "Invalid number of convexes (" .. #meshConvexes .. ")")
159+
assert(#meshConvexes <= maxConvexesPerProp, "Exceeded the max convexes per prop (max: " .. maxConvexesPerProp .. ", got: ".. #meshConvexes .. ")")
160+
161+
for _, convex in ipairs(meshConvexes) do
162+
assert(#convex <= maxVerticesPerConvex, "Exceeded the max vertices per convex (max: " .. maxVerticesPerConvex .. ", got: " .. #convex .. ")")
163+
assert(#convex > 4, "Invalid number of vertices (" .. #convex .. ")")
164+
165+
for k, vertex in ipairs(convex) do
166+
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)
167+
assert(vertex[1] == vertex[1] and vertex[2] == vertex[2] and vertex[3] == vertex[3], "Your mesh contains nan values!")
168+
for i = 1, k - 1 do
169+
assert(convex[i]:DistToSqr(vertex) >= mindist, "No two vertices can have a distance less than " .. math.sqrt(mindist))
170+
end
171+
end
172+
end
173+
end
174+
175+
function WireLib.CustomProp.CanSpawn(ply)
176+
ply.WireCustomPropsSpawned = ply.WireCustomPropsSpawned or 0
177+
return ply.WireCustomPropsSpawned < wire_customprops_max:GetInt()
178+
end
179+
180+
function WireLib.CustomProp.Create(ply, pos, ang, wiremeshdata)
181+
if not WireLib.CustomProp.CanSpawn(ply) then return nil, "Max amount of custom props spawned for this player reached!" end
182+
183+
local meshConvexes, meshStream
184+
185+
if isstring(wiremeshdata) then
186+
meshConvexes = streamToMesh(wiremeshdata)
187+
meshStream = wiremeshdata
188+
elseif istable(wiremeshdata) then
189+
meshConvexes = wiremeshdata
190+
meshStream = meshToStream(wiremeshdata)
191+
else
192+
assert(false, "Invalid meshdata")
193+
end
194+
195+
checkMesh(self, meshConvexes)
196+
197+
local propent = ents.Create(shared.classname)
198+
propent.physmesh = meshConvexes
199+
200+
propent.wiremeshdata = meshStream
201+
propent:Spawn()
202+
203+
local physobj = propent:GetPhysicsObject()
204+
if not physobj:IsValid() then
205+
propent:Remove()
206+
assert(false, "Custom prop has invalid physics!")
207+
end
208+
209+
propent:SetPos(pos)
210+
propent:SetAngles(ang)
211+
propent:TransmitData()
212+
213+
physobj:EnableCollisions(true)
214+
physobj:EnableDrag(true)
215+
physobj:Wake()
216+
217+
gamemode.Call("PlayerSpawnedSENT", ply, propent)
218+
219+
local totalVertices = 0
220+
for k, v in ipairs(meshConvexes) do
221+
totalVertices = totalVertices + #v
222+
end
223+
224+
propent:CallOnRemove("wire_customprop_remove",
225+
function(propent)
226+
if IsValid(ply) then
227+
ply.WireCustomPropsSpawned = (ply.WireCustomPropsSpawned or 1) - 1
228+
end
229+
end
230+
)
231+
232+
ply.WireCustomPropsSpawned = (ply.WireCustomPropsSpawned or 0) + 1
233+
234+
return propent
235+
end
236+
237+
duplicator.RegisterEntityClass(shared.classname, WireLib.CustomProp.Create, "Pos", "Ang", "wiremeshdata")

0 commit comments

Comments
 (0)