mirror of
https://github.com/shadowscion/Prop2Mesh.git
synced 2025-03-04 03:13:03 -05:00
complete refactor
This commit is contained in:
parent
0d1065e7eb
commit
e20749bb6b
364
lua/autorun/netstream.lua
Normal file
364
lua/autorun/netstream.lua
Normal file
@ -0,0 +1,364 @@
|
||||
--A net extension which allows sending large streams of data without overflowing the reliable channel
|
||||
--Keep it in lua/autorun so it will be shared between addons
|
||||
AddCSLuaFile()
|
||||
|
||||
net.Stream = {}
|
||||
net.Stream.ReadStreamQueues = {} --This holds a read stream for each player, or one read stream for the server if running on the CLIENT
|
||||
net.Stream.WriteStreams = {} --This holds the write streams
|
||||
net.Stream.SendSize = 20000 --This is the maximum size of each stream to send
|
||||
net.Stream.Timeout = 30 --How long the data should exist in the store without being used before being destroyed
|
||||
net.Stream.MaxServerReadStreams = 128 --The maximum number of keep-alives to have queued. This should prevent naughty players from flooding the network with keep-alive messages.
|
||||
net.Stream.MaxServerChunks = 3200 --Maximum number of pieces the stream can send to the server. 64 MB
|
||||
net.Stream.MaxTries = 3 --Maximum times the client may retry downloading the whole data
|
||||
net.Stream.MaxKeepalive = 15 --Maximum times the client may request data stay live
|
||||
|
||||
net.Stream.ReadStream = {}
|
||||
--Send the data sender a request for data
|
||||
function net.Stream.ReadStream:Request()
|
||||
if self.downloads == net.Stream.MaxTries * self.numchunks then self:Remove() return end
|
||||
self.downloads = self.downloads + 1
|
||||
-- print("Requesting",self.identifier,false,false,#self.chunks)
|
||||
|
||||
net.Start("NetStreamRequest")
|
||||
net.WriteUInt(self.identifier, 32)
|
||||
net.WriteBit(false)
|
||||
net.WriteBit(false)
|
||||
net.WriteUInt(#self.chunks, 32)
|
||||
if CLIENT then net.SendToServer() else net.Send(self.player) end
|
||||
|
||||
timer.Create("NetStreamReadTimeout" .. self.identifier, net.Stream.Timeout/2, 1, function() self:Request() end)
|
||||
|
||||
end
|
||||
|
||||
--Received data so process it
|
||||
function net.Stream.ReadStream:Read(size)
|
||||
timer.Remove("NetStreamReadTimeout" .. self.identifier)
|
||||
|
||||
local progress = net.ReadUInt(32)
|
||||
if self.chunks[progress] then return end
|
||||
|
||||
local crc = net.ReadString()
|
||||
local data = net.ReadData(size)
|
||||
|
||||
if crc == util.CRC(data) then
|
||||
self.chunks[progress] = data
|
||||
end
|
||||
if #self.chunks == self.numchunks then
|
||||
self.returndata = table.concat(self.chunks)
|
||||
if self.compressed then
|
||||
self.returndata = util.Decompress(self.returndata)
|
||||
end
|
||||
self:Remove()
|
||||
else
|
||||
self:Request()
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--Gets the download progress
|
||||
function net.Stream.ReadStream:GetProgress()
|
||||
return #self.chunks/self.numchunks
|
||||
end
|
||||
|
||||
--Pop the queue and start the next task
|
||||
function net.Stream.ReadStream:Remove()
|
||||
|
||||
local ok, err = xpcall(self.callback, debug.traceback, self.returndata)
|
||||
if not ok then ErrorNoHalt(err) end
|
||||
|
||||
net.Start("NetStreamRequest")
|
||||
net.WriteUInt(self.identifier, 32)
|
||||
net.WriteBit(false)
|
||||
net.WriteBit(true)
|
||||
if CLIENT then net.SendToServer() else net.Send(self.player) end
|
||||
|
||||
timer.Remove("NetStreamReadTimeout" .. self.identifier)
|
||||
timer.Remove("NetStreamKeepAlive" .. self.identifier)
|
||||
|
||||
if self == self.queue[1] then
|
||||
table.remove(self.queue, 1)
|
||||
local nextInQueue = self.queue[1]
|
||||
if nextInQueue then
|
||||
timer.Remove("NetStreamKeepAlive" .. nextInQueue.identifier)
|
||||
nextInQueue:Request()
|
||||
else
|
||||
net.Stream.ReadStreamQueues[self.player] = nil
|
||||
end
|
||||
else
|
||||
for k, v in ipairs(self.queue) do
|
||||
if v == self then
|
||||
table.remove(self.queue, k)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
net.Stream.ReadStream.__index = net.Stream.ReadStream
|
||||
|
||||
net.Stream.WriteStream = {}
|
||||
|
||||
-- The player wants some data
|
||||
function net.Stream.WriteStream:Write(ply)
|
||||
local progress = net.ReadUInt(32)+1
|
||||
local chunk = self.chunks[progress]
|
||||
if chunk then
|
||||
self.clients[ply].progress = progress
|
||||
net.Start("NetStreamDownload")
|
||||
net.WriteUInt(#chunk.data, 32)
|
||||
net.WriteUInt(progress, 32)
|
||||
net.WriteString(chunk.crc)
|
||||
net.WriteData(chunk.data, #chunk.data)
|
||||
if CLIENT then net.SendToServer() else net.Send(ply) end
|
||||
end
|
||||
end
|
||||
|
||||
-- The player notified us they finished downloading or cancelled
|
||||
function net.Stream.WriteStream:Finished(ply)
|
||||
self.clients[ply].finished = true
|
||||
if self.callback then
|
||||
local ok, err = xpcall(self.callback, debug.traceback, ply)
|
||||
if not ok then ErrorNoHalt(err) end
|
||||
end
|
||||
end
|
||||
|
||||
-- Get player's download progress
|
||||
function net.Stream.WriteStream:GetProgress(ply)
|
||||
return self.clients[ply].progress / #self.chunks
|
||||
end
|
||||
|
||||
-- If the stream owner cancels it, notify everyone who is subscribed
|
||||
function net.Stream.WriteStream:Remove()
|
||||
local sendTo = {}
|
||||
for ply, client in pairs(self.clients) do
|
||||
if not client.finished then
|
||||
client.finished = true
|
||||
if ply:IsValid() then sendTo[#sendTo+1] = ply end
|
||||
end
|
||||
end
|
||||
|
||||
net.Start("NetStreamDownload")
|
||||
net.WriteUInt(0, 32)
|
||||
net.WriteUInt(self.identifier, 32)
|
||||
if SERVER then net.Send(sendTo) else net.SendToServer() end
|
||||
net.Stream.WriteStreams[self.identifier] = nil
|
||||
end
|
||||
|
||||
net.Stream.WriteStream.__index = net.Stream.WriteStream
|
||||
|
||||
--Store the data and write the file info so receivers can request it.
|
||||
local identifier = 1
|
||||
function net.WriteStream(data, callback, dontcompress)
|
||||
|
||||
if not isstring(data) then
|
||||
error("bad argument #1 to 'WriteStream' (string expected, got " .. type(data) .. ")", 2)
|
||||
end
|
||||
if callback ~= nil and not isfunction(callback) then
|
||||
error("bad argument #2 to 'WriteStream' (function expected, got " .. type(callback) .. ")", 2)
|
||||
end
|
||||
|
||||
local compressed = not dontcompress
|
||||
if compressed then
|
||||
data = util.Compress(data) or ""
|
||||
end
|
||||
|
||||
if #data == 0 then
|
||||
net.WriteUInt(0, 32)
|
||||
return
|
||||
end
|
||||
|
||||
local numchunks = math.ceil(#data / net.Stream.SendSize)
|
||||
if CLIENT and numchunks > net.Stream.MaxServerChunks then
|
||||
ErrorNoHalt("net.WriteStream request is too large! ", #data/1048576, "MiB")
|
||||
net.WriteUInt(0, 32)
|
||||
return
|
||||
end
|
||||
|
||||
local chunks = {}
|
||||
for i=1, numchunks do
|
||||
local datachunk = string.sub(data, (i - 1) * net.Stream.SendSize + 1, i * net.Stream.SendSize)
|
||||
chunks[i] = {
|
||||
data = datachunk,
|
||||
crc = util.CRC(datachunk),
|
||||
}
|
||||
end
|
||||
|
||||
local startid = identifier
|
||||
while net.Stream.WriteStreams[identifier] do
|
||||
identifier = identifier % 1024 + 1
|
||||
if identifier == startid then
|
||||
ErrorNoHalt("Netstream is full of WriteStreams!")
|
||||
net.WriteUInt(0, 32)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local stream = {
|
||||
identifier = identifier,
|
||||
chunks = chunks,
|
||||
compressed = compressed,
|
||||
numchunks = numchunks,
|
||||
callback = callback,
|
||||
clients = setmetatable({},{__index = function(t,k)
|
||||
local r = {
|
||||
finished = false,
|
||||
downloads = 0,
|
||||
keepalives = 0,
|
||||
progress = 0,
|
||||
} t[k]=r return r
|
||||
end})
|
||||
}
|
||||
setmetatable(stream, net.Stream.WriteStream)
|
||||
|
||||
net.Stream.WriteStreams[identifier] = stream
|
||||
timer.Create("NetStreamWriteTimeout" .. identifier, net.Stream.Timeout, 1, function() stream:Remove() end)
|
||||
|
||||
net.WriteUInt(numchunks, 32)
|
||||
net.WriteUInt(identifier, 32)
|
||||
net.WriteBool(compressed)
|
||||
|
||||
return stream
|
||||
end
|
||||
|
||||
--If the receiver is a player then add it to a queue.
|
||||
--If the receiver is the server then add it to a queue for each individual player
|
||||
function net.ReadStream(ply, callback)
|
||||
|
||||
if CLIENT then
|
||||
ply = NULL
|
||||
else
|
||||
if type(ply) ~= "Player" then
|
||||
error("bad argument #1 to 'ReadStream' (Player expected, got " .. type(ply) .. ")", 2)
|
||||
elseif not ply:IsValid() then
|
||||
error("bad argument #1 to 'ReadStream' (Tried to use a NULL entity!)", 2)
|
||||
end
|
||||
end
|
||||
if not isfunction(callback) then
|
||||
error("bad argument #2 to 'ReadStream' (function expected, got " .. type(callback) .. ")", 2)
|
||||
end
|
||||
|
||||
local queue = net.Stream.ReadStreamQueues[ply]
|
||||
if queue then
|
||||
if SERVER and #queue == net.Stream.MaxServerReadStreams then
|
||||
ErrorNoHalt("Receiving too many ReadStream requests from ", ply)
|
||||
return
|
||||
end
|
||||
else
|
||||
queue = {} net.Stream.ReadStreamQueues[ply] = queue
|
||||
end
|
||||
|
||||
local numchunks = net.ReadUInt(32)
|
||||
if numchunks == nil then
|
||||
return
|
||||
elseif numchunks == 0 then
|
||||
local ok, err = xpcall(callback, debug.traceback, "")
|
||||
if not ok then ErrorNoHalt(err) end
|
||||
return
|
||||
end
|
||||
if SERVER and numchunks > net.Stream.MaxServerChunks then
|
||||
ErrorNoHalt("ReadStream requests from ", ply, " is too large! ", numchunks * net.Stream.SendSize / 1048576, "MiB")
|
||||
return
|
||||
end
|
||||
|
||||
local identifier = net.ReadUInt(32)
|
||||
local compressed = net.ReadBool()
|
||||
--print("Got info", numchunks, identifier, compressed)
|
||||
|
||||
for _, v in ipairs(queue) do
|
||||
if v.identifier == identifier then
|
||||
ErrorNoHalt("Tried to start a new ReadStream for an already existing stream!")
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local stream = {
|
||||
identifier = identifier,
|
||||
chunks = {},
|
||||
compressed = compressed,
|
||||
numchunks = numchunks,
|
||||
callback = callback,
|
||||
queue = queue,
|
||||
player = ply,
|
||||
downloads = 0
|
||||
}
|
||||
setmetatable(stream, net.Stream.ReadStream)
|
||||
|
||||
queue[#queue + 1] = stream
|
||||
if #queue > 1 then
|
||||
timer.Create("NetStreamKeepAlive" .. identifier, net.Stream.Timeout / 2, 0, function()
|
||||
net.Start("NetStreamRequest")
|
||||
net.WriteUInt(identifier, 32)
|
||||
net.WriteBit(true)
|
||||
if CLIENT then net.SendToServer() else net.Send(ply) end
|
||||
end)
|
||||
else
|
||||
stream:Request()
|
||||
end
|
||||
|
||||
return stream
|
||||
end
|
||||
|
||||
if SERVER then
|
||||
|
||||
util.AddNetworkString("NetStreamRequest")
|
||||
util.AddNetworkString("NetStreamDownload")
|
||||
|
||||
end
|
||||
|
||||
--Stream data is requested
|
||||
net.Receive("NetStreamRequest", function(len, ply)
|
||||
|
||||
local identifier = net.ReadUInt(32)
|
||||
local stream = net.Stream.WriteStreams[identifier]
|
||||
|
||||
if stream then
|
||||
ply = ply or NULL
|
||||
local client = stream.clients[ply]
|
||||
|
||||
if not client.finished then
|
||||
local keepalive = net.ReadBit() == 1
|
||||
if keepalive then
|
||||
if client.keepalives < net.Stream.MaxKeepalive then
|
||||
client.keepalives = client.keepalives + 1
|
||||
timer.Adjust("NetStreamWriteTimeout" .. identifier, net.Stream.Timeout, 1)
|
||||
end
|
||||
else
|
||||
local completed = net.ReadBit() == 1
|
||||
if completed then
|
||||
stream:Finished(ply)
|
||||
else
|
||||
if client.downloads < net.Stream.MaxTries * #stream.chunks then
|
||||
client.downloads = client.downloads + 1
|
||||
stream:Write(ply)
|
||||
timer.Adjust("NetStreamWriteTimeout" .. identifier, net.Stream.Timeout, 1)
|
||||
else
|
||||
client.finished = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end)
|
||||
|
||||
--Download the stream data
|
||||
net.Receive("NetStreamDownload", function(len, ply)
|
||||
|
||||
ply = ply or NULL
|
||||
local queue = net.Stream.ReadStreamQueues[ply]
|
||||
if queue then
|
||||
local size = net.ReadUInt(32)
|
||||
if size > 0 then
|
||||
queue[1]:Read(size)
|
||||
else
|
||||
local id = net.ReadUInt(32)
|
||||
for k, v in ipairs(queue) do
|
||||
if v.identifier == id then
|
||||
v:Remove()
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end)
|
44
lua/autorun/prop2mesh.lua
Normal file
44
lua/autorun/prop2mesh.lua
Normal file
@ -0,0 +1,44 @@
|
||||
--[[
|
||||
|
||||
]]
|
||||
if not prop2mesh then prop2mesh = {} end
|
||||
|
||||
|
||||
--[[
|
||||
|
||||
]]
|
||||
local validClasses = { ["sent_prop2mesh"] = true, ["sent_prop2mesh_legacy"] = true }
|
||||
function prop2mesh.isValid(self)
|
||||
return IsValid(self) and validClasses[self:GetClass()]
|
||||
end
|
||||
|
||||
prop2mesh.defaultmat = "hunter/myplastic"
|
||||
|
||||
|
||||
--[[
|
||||
|
||||
]]
|
||||
if SERVER then
|
||||
AddCSLuaFile("prop2mesh/cl_meshlab.lua")
|
||||
AddCSLuaFile("prop2mesh/cl_modelfixer.lua")
|
||||
AddCSLuaFile("prop2mesh/cl_editor.lua")
|
||||
|
||||
include("prop2mesh/sv_entparts.lua")
|
||||
include("prop2mesh/sv_editor.lua")
|
||||
|
||||
function prop2mesh.getEmpty()
|
||||
return {
|
||||
crc = "!none",
|
||||
uvs = 0,
|
||||
col = Color(255, 255, 255, 255),
|
||||
mat = prop2mesh.defaultmat,
|
||||
scale = Vector(1, 1, 1),
|
||||
}
|
||||
end
|
||||
|
||||
elseif CLIENT then
|
||||
include("prop2mesh/cl_meshlab.lua")
|
||||
include("prop2mesh/cl_modelfixer.lua")
|
||||
include("prop2mesh/cl_editor.lua")
|
||||
|
||||
end
|
370
lua/entities/gmod_wire_expression2/core/custom/prop2mesh.lua
Normal file
370
lua/entities/gmod_wire_expression2/core/custom/prop2mesh.lua
Normal file
@ -0,0 +1,370 @@
|
||||
E2Lib.RegisterExtension("prop2mesh", true, "Allows E2 chips to create and manipulate prop2mesh entities")
|
||||
|
||||
local E2Lib, WireLib, prop2mesh, math =
|
||||
E2Lib, WireLib, prop2mesh, math
|
||||
|
||||
|
||||
--[[
|
||||
]]
|
||||
local _COL = 1
|
||||
local _MAT = 2
|
||||
local _POS = 3
|
||||
local _ANG = 4
|
||||
local _SCALE = 5
|
||||
local _UVS = 6
|
||||
|
||||
local _PARENT = 7
|
||||
local _MODEL = 8
|
||||
local _NODRAW = 9
|
||||
local _BUILD = 10
|
||||
|
||||
local cooldowns = {}
|
||||
cooldowns[_BUILD] = 10
|
||||
cooldowns[_UVS] = 10
|
||||
|
||||
local errors = {}
|
||||
errors[_BUILD] = "\nDon't spam e:p2mBuild"
|
||||
errors[_UVS] = "\nDon't spam e:p2mSetUV"
|
||||
|
||||
local function canspam(check, wait, time)
|
||||
if not check or time - check > wait then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function antispam(self, action, index)
|
||||
if not self.prop2mesh_e2_antispam then
|
||||
self.prop2mesh_e2_antispam = {}
|
||||
end
|
||||
|
||||
local time = CurTime()
|
||||
local wait = cooldowns[action]
|
||||
|
||||
if not index then
|
||||
if self.prop2mesh_e2_antispam[action] == time then
|
||||
return false
|
||||
end
|
||||
if not wait or (wait and canspam(self.prop2mesh_e2_antispam[action], wait, time)) then
|
||||
self.prop2mesh_e2_antispam[action] = time
|
||||
return true
|
||||
end
|
||||
error(errors[action])
|
||||
return false
|
||||
else
|
||||
if not self.prop2mesh_e2_antispam[index] then
|
||||
self.prop2mesh_e2_antispam[index] = {}
|
||||
end
|
||||
if self.prop2mesh_e2_antispam[index][action] == time then
|
||||
return false
|
||||
end
|
||||
if not wait or (wait and canspam(self.prop2mesh_e2_antispam[index][action], wait, time)) then
|
||||
self.prop2mesh_e2_antispam[index][action] = time
|
||||
return true
|
||||
end
|
||||
error(errors[action])
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
local function checkvalid(context, self, action, index, restricted)
|
||||
if not E2Lib.isOwner(context, self) or not prop2mesh.isValid(self) then
|
||||
return false
|
||||
end
|
||||
if restricted and not self.prop2mesh_e2_resevoir then
|
||||
return false
|
||||
end
|
||||
if index and not self.prop2mesh_controllers[index] then
|
||||
error(string.format("\ncontroller index %d does not exist on %s!", index, tostring(self)))
|
||||
end
|
||||
if action then
|
||||
return antispam(self, action, index)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
]]
|
||||
__e2setcost(10)
|
||||
|
||||
e2function void entity:p2mSetColor(number index, vector color)
|
||||
if checkvalid(self, this, _COL, index) then
|
||||
this:SetControllerCol(index, Color(color[1], color[2], color[3]))
|
||||
end
|
||||
end
|
||||
e2function void entity:p2mSetColor(number index, vector4 color)
|
||||
if checkvalid(self, this, _COL, index) then
|
||||
this:SetControllerCol(index, Color(color[1], color[2], color[3], color[4]))
|
||||
end
|
||||
end
|
||||
e2function void entity:p2mSetMaterial(number index, string material)
|
||||
if checkvalid(self, this, _MAT, index) then
|
||||
this:SetControllerMat(index, WireLib.IsValidMaterial(material))
|
||||
end
|
||||
end
|
||||
e2function void entity:p2mSetScale(number index, vector scale)
|
||||
if checkvalid(self, this, _SCALE, index) then
|
||||
this:SetControllerScale(index, Vector(scale[1], scale[2], scale[3]))
|
||||
end
|
||||
end
|
||||
e2function void entity:p2mSetUV(number index, number uvs)
|
||||
if checkvalid(self, this, _UVS, index) then
|
||||
this:SetControllerUVS(index, math.Clamp(math.floor(math.abs(uvs)), 0, 512))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
]]
|
||||
e2function void entity:p2mSetPos(vector pos)
|
||||
if checkvalid(self, this, _POS) then
|
||||
WireLib.setPos(this, Vector(pos[1], pos[2], pos[3]))
|
||||
end
|
||||
end
|
||||
e2function void entity:p2mSetAng(angle ang)
|
||||
if checkvalid(self, this, _ANG) then
|
||||
WireLib.setAng(this, Angle(ang[1], ang[2], ang[3]))
|
||||
end
|
||||
end
|
||||
e2function void entity:p2mSetNodraw(number bool)
|
||||
if checkvalid(self, this, _NODRAW) then
|
||||
this:SetNoDraw(tobool(bool))
|
||||
end
|
||||
end
|
||||
e2function void entity:p2mSetModel(string model)
|
||||
if checkvalid(self, this, _MODEL, nil, true) then
|
||||
this:SetModel(model)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
]]
|
||||
__e2setcost(25)
|
||||
|
||||
local function Check_Parents(child, parent)
|
||||
while IsValid(parent:GetParent()) do
|
||||
parent = parent:GetParent()
|
||||
if parent == child then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
e2function void entity:p2mSetParent(entity parent)
|
||||
if not IsValid(parent) or not E2Lib.isOwner(self, parent) or not checkvalid(self, this, _PARENT) then
|
||||
return
|
||||
end
|
||||
if not Check_Parents(this, parent) then
|
||||
return
|
||||
end
|
||||
if parent:GetParent() and parent:GetParent():IsValid() and parent:GetParent() == this then
|
||||
return
|
||||
end
|
||||
this:SetParent(parent)
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
]]
|
||||
registerCallback("construct", function(self)
|
||||
self.data.prop2mesh = {}
|
||||
end)
|
||||
|
||||
registerCallback("destruct", function(self)
|
||||
for ent, mode in pairs(self.data.prop2mesh) do
|
||||
if ent then
|
||||
ent:Remove()
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
local function p2mCreate(context, pos, ang, count)
|
||||
if not count or count < 1 then
|
||||
count = 1
|
||||
end
|
||||
count = math.ceil(count)
|
||||
|
||||
if count > 16 then
|
||||
error("controller limit is 16 per entity")
|
||||
end
|
||||
|
||||
local self = ents.Create("sent_prop2mesh")
|
||||
|
||||
self:SetNoDraw(true)
|
||||
self:SetModel("models/hunter/plates/plate.mdl")
|
||||
WireLib.setPos(self, pos)
|
||||
WireLib.setAng(self, ang)
|
||||
self:Spawn()
|
||||
|
||||
if not IsValid(self) then
|
||||
return NULL
|
||||
end
|
||||
|
||||
if CPPI then
|
||||
self:CPPISetOwner(context.player)
|
||||
end
|
||||
|
||||
self:SetSolid(SOLID_NONE)
|
||||
self:SetMoveType(MOVETYPE_NONE)
|
||||
self:DrawShadow(false)
|
||||
self:Activate()
|
||||
|
||||
self:CallOnRemove("wire_expression2_p2m", function(e)
|
||||
context.data.prop2mesh[e] = nil
|
||||
end)
|
||||
|
||||
context.data.prop2mesh[self] = true
|
||||
|
||||
self.DoNotDuplicate = true
|
||||
self.prop2mesh_e2_resevoir = {}
|
||||
|
||||
for i = 1, count do
|
||||
self:AddController()
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
__e2setcost(50)
|
||||
|
||||
e2function entity p2mCreate(number count, vector pos, angle ang)
|
||||
return p2mCreate(self, Vector(pos[1], pos[2], pos[3]), Angle(ang[1], ang[2], ang[3]), count)
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
]]
|
||||
__e2setcost(100)
|
||||
|
||||
local function p2mBuild(context, self)
|
||||
for k, v in pairs(self.prop2mesh_e2_resevoir) do
|
||||
if self.prop2mesh_controllers[k] then
|
||||
self:SetControllerData(k, v)
|
||||
end
|
||||
end
|
||||
self.prop2mesh_e2_resevoir = {}
|
||||
end
|
||||
|
||||
e2function number entity:p2mBuild()
|
||||
if not checkvalid(self, this, _BUILD, nil, true) then
|
||||
return
|
||||
end
|
||||
p2mBuild(self, this)
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
]]
|
||||
__e2setcost(5)
|
||||
|
||||
local function toVec(vec)
|
||||
return vec and Vector(vec[1], vec[2], vec[3]) or Vector()
|
||||
end
|
||||
|
||||
local function toAng(ang)
|
||||
return ang and Angle(ang[1], ang[2], ang[3]) or Angle()
|
||||
end
|
||||
|
||||
local function isVector(op0)
|
||||
return istable(op0) and #op0 == 3 or type(op0) == "Vector"
|
||||
end
|
||||
|
||||
local function errorcheck(context, self, index)
|
||||
if not self.prop2mesh_controllers[index] then
|
||||
error(string.format("\ncontroller index %d does not exist on %s!", index, tostring(self)))
|
||||
end
|
||||
if not self.prop2mesh_e2_resevoir[index] then
|
||||
self.prop2mesh_e2_resevoir[index] = {}
|
||||
end
|
||||
if #self.prop2mesh_e2_resevoir[index] + 1 > 250 then
|
||||
error("model limit is 250 per controller")
|
||||
end
|
||||
end
|
||||
|
||||
local function checkClips(clips)
|
||||
if #clips == 0 or #clips % 2 ~= 0 then
|
||||
return
|
||||
end
|
||||
local swap = {}
|
||||
for i = 1, #clips, 2 do
|
||||
local op1 = clips[i]
|
||||
local op2 = clips[i + 1]
|
||||
|
||||
if not isVector(op1) or not isVector(op2) then
|
||||
goto CONTINUE
|
||||
end
|
||||
|
||||
local normal = toVec(op2)
|
||||
normal:Normalize()
|
||||
|
||||
swap[#swap + 1] = { d = toVec(op1):Dot(normal), n = normal }
|
||||
|
||||
::CONTINUE::
|
||||
end
|
||||
return swap
|
||||
end
|
||||
|
||||
local function p2mPushModel(context, self, index, model, pos, ang, scale, clips, vinside, vsmooth)
|
||||
errorcheck(context, self, index)
|
||||
|
||||
if scale then
|
||||
scale = toVec(scale)
|
||||
if scale.x == 1 and scale.y == 1 and scale.z == 1 then
|
||||
scale = nil
|
||||
end
|
||||
end
|
||||
|
||||
if clips then clips = checkClips(clips) end
|
||||
|
||||
self.prop2mesh_e2_resevoir[index][#self.prop2mesh_e2_resevoir[index] + 1] = {
|
||||
prop = model,
|
||||
pos = toVec(pos),
|
||||
ang = toAng(ang),
|
||||
scale = scale,
|
||||
clips = clips,
|
||||
vinside = tobool(vinside) and 1 or nil,
|
||||
vsmooth = tobool(vsmooth) and 1 or nil,
|
||||
}
|
||||
|
||||
return #self.prop2mesh_e2_resevoir[index]
|
||||
end
|
||||
|
||||
e2function void entity:p2mPushModel(index, string model, vector pos, angle ang)
|
||||
if checkvalid(self, this, nil, index, true) then
|
||||
p2mPushModel(self, this, index, model, pos, ang)
|
||||
end
|
||||
end
|
||||
e2function void entity:p2mPushModel(index, string model, vector pos, angle ang, number renderinside, number renderflat)
|
||||
if checkvalid(self, this, nil, index, true) then
|
||||
p2mPushModel(self, this, index, model, pos, ang, nil, nil, renderinside, renderflat)
|
||||
end
|
||||
end
|
||||
e2function void entity:p2mPushModel(index, string model, vector pos, angle ang, number renderinside, number renderflat, array clips)
|
||||
if checkvalid(self, this, nil, index, true) then
|
||||
p2mPushModel(self, this, index, model, pos, ang, nil, clips, renderinside, renderflat)
|
||||
end
|
||||
end
|
||||
e2function void entity:p2mPushModel(index, string model, vector pos, angle ang, vector scale)
|
||||
if checkvalid(self, this, nil, index, true) then
|
||||
p2mPushModel(self, this, index, model, pos, ang, scale)
|
||||
end
|
||||
end
|
||||
e2function void entity:p2mPushModel(index, string model, vector pos, angle ang, vector scale, number renderinside, number renderflat)
|
||||
if checkvalid(self, this, nil, index, true) then
|
||||
p2mPushModel(self, this, index, model, pos, ang, scale, nil, renderinside, renderflat)
|
||||
end
|
||||
end
|
||||
e2function void entity:p2mPushModel(index, string model, vector pos, angle ang, vector scale, number renderinside, array clips)
|
||||
if checkvalid(self, this, nil, index, true) then
|
||||
p2mPushModel(self, this, index, model, pos, ang, scale, clips, renderinside)
|
||||
end
|
||||
end
|
||||
e2function void entity:p2mPushModel(index, string model, vector pos, angle ang, vector scale, number renderinside, number renderflat, array clips)
|
||||
if checkvalid(self, this, nil, index, true) then
|
||||
p2mPushModel(self, this, index, model, pos, ang, scale, clips, renderinside, renderflat)
|
||||
end
|
||||
end
|
537
lua/entities/sent_prop2mesh/cl_init.lua
Normal file
537
lua/entities/sent_prop2mesh/cl_init.lua
Normal file
@ -0,0 +1,537 @@
|
||||
--[[
|
||||
|
||||
]]
|
||||
include("shared.lua")
|
||||
|
||||
local prop2mesh = prop2mesh
|
||||
|
||||
local defaultmat = Material(prop2mesh.defaultmat)
|
||||
local debugwhite = Material("models/debug/debugwhite")
|
||||
local wireframe = Material("models/wireframe")
|
||||
|
||||
local net = net
|
||||
local cam = cam
|
||||
local table = table
|
||||
local render = render
|
||||
local string = string
|
||||
|
||||
local empty = { Mesh = Mesh(), Material = Material("models/debug/debugwhite") }
|
||||
empty.Mesh:BuildFromTriangles({{pos = Vector()},{pos = Vector()},{pos = Vector()}})
|
||||
|
||||
|
||||
--[[
|
||||
|
||||
]]
|
||||
local recycle = {}
|
||||
local garbage = {}
|
||||
|
||||
function prop2mesh.getMeshInfo(crc, uvs)
|
||||
local mdata = recycle[crc] and recycle[crc].meshes[uvs]
|
||||
if mdata then
|
||||
return mdata.pcount, mdata.vcount
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
function prop2mesh.getMeshData(crc, unzip)
|
||||
local dat = recycle[crc] and recycle[crc].zip
|
||||
if not unzip or not dat then
|
||||
return dat
|
||||
end
|
||||
return util.JSONToTable(util.Decompress(dat))
|
||||
end
|
||||
|
||||
concommand.Add("prop2mesh_dump", function()
|
||||
PrintTable(recycle)
|
||||
PrintTable(garbage)
|
||||
end)
|
||||
|
||||
timer.Create("prop2trash", 1, 0, function()
|
||||
local curtime = SysTime()
|
||||
for crc, usedtime in pairs(garbage) do
|
||||
if curtime - usedtime > 3 then
|
||||
if recycle[crc] and recycle[crc].meshes then
|
||||
for uvs, meshdata in pairs(recycle[crc].meshes) do
|
||||
if meshdata.basic then
|
||||
if IsValid(meshdata.basic.Mesh) then
|
||||
print("destroying", meshdata.basic.Mesh)
|
||||
meshdata.basic.Mesh:Destroy()
|
||||
meshdata.basic.Mesh = nil
|
||||
end
|
||||
end
|
||||
if meshdata.complex then
|
||||
for m, meshpart in pairs(meshdata.complex) do
|
||||
if IsValid(meshpart) then
|
||||
print("destroying", meshpart)
|
||||
meshpart:Destroy()
|
||||
meshdata.complex[m] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
recycle[crc] = nil
|
||||
garbage[crc] = nil
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
local function checkdownload(self, crc)
|
||||
if recycle[crc] then
|
||||
return true
|
||||
end
|
||||
|
||||
recycle[crc] = { users = {}, meshes = {} }
|
||||
|
||||
net.Start("prop2mesh_download")
|
||||
net.WriteEntity(self)
|
||||
net.WriteString(crc)
|
||||
net.SendToServer()
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local function setuser(self, crc, bool)
|
||||
if not recycle[crc] then
|
||||
garbage[crc] = nil
|
||||
return
|
||||
end
|
||||
if bool then
|
||||
recycle[crc].users[self] = true
|
||||
garbage[crc] = nil
|
||||
else
|
||||
recycle[crc].users[self] = nil
|
||||
if not next(recycle[crc].users) then
|
||||
garbage[crc] = SysTime()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function checkmesh(crc, uvs)
|
||||
if not recycle[crc] or not recycle[crc].zip or recycle[crc].meshes[uvs] then
|
||||
return recycle[crc].meshes[uvs]
|
||||
end
|
||||
recycle[crc].meshes[uvs] = {}
|
||||
prop2mesh.getMesh(crc, uvs, recycle[crc].zip)
|
||||
end
|
||||
|
||||
hook.Add("prop2mesh_hook_meshdone", "prop2mesh_meshlab", function(crc, uvs, mdata)
|
||||
if not mdata or not crc or not uvs then
|
||||
return
|
||||
end
|
||||
|
||||
recycle[crc].meshes[uvs] = mdata
|
||||
|
||||
if #mdata.meshes == 1 then
|
||||
local imesh = Mesh()
|
||||
imesh:BuildFromTriangles(mdata.meshes[1])
|
||||
mdata.basic = { Mesh = imesh, Material = defaultmat }
|
||||
else
|
||||
mdata.complex = {}
|
||||
for i = 1, #mdata.meshes do
|
||||
local imesh = Mesh()
|
||||
imesh:BuildFromTriangles(mdata.meshes[i])
|
||||
mdata.complex[i] = imesh
|
||||
end
|
||||
end
|
||||
|
||||
mdata.meshes = nil
|
||||
mdata.ready = true
|
||||
|
||||
local mins = mdata.vmins
|
||||
local maxs = mdata.vmaxs
|
||||
|
||||
if mins and maxs then
|
||||
for user in pairs(recycle[crc].users) do
|
||||
for k, info in pairs(user.prop2mesh_controllers) do
|
||||
if IsValid(info.ent) and info.crc == crc and info.uvs == uvs then
|
||||
info.ent:SetRenderBounds(mins, maxs)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
--[[
|
||||
|
||||
]]
|
||||
local cvar = CreateClientConVar("prop2mesh_render_disable", 0, true, false)
|
||||
local draw_disable = cvar:GetBool()
|
||||
|
||||
cvars.AddChangeCallback("prop2mesh_render_disable", function(cvar, old, new)
|
||||
draw_disable = tobool(new)
|
||||
end, "swapdrawdisable")
|
||||
|
||||
local draw_wireframe
|
||||
concommand.Add("prop2mesh_render_wireframe", function(ply, cmd, args)
|
||||
draw_wireframe = not draw_wireframe
|
||||
end)
|
||||
|
||||
|
||||
--[[
|
||||
|
||||
]]
|
||||
local function getComplex(crc, uvs)
|
||||
local meshes = recycle[crc] and recycle[crc].meshes[uvs]
|
||||
return meshes and meshes.complex
|
||||
end
|
||||
|
||||
local vec = Vector()
|
||||
local function drawModel(self)
|
||||
if draw_disable then
|
||||
local min, max = self:GetRenderBounds()
|
||||
local color = self:GetColor()
|
||||
vec.x = color.r/255
|
||||
vec.y = color.g/255
|
||||
vec.z = color.b/255
|
||||
debugwhite:SetVector("$color", vec)
|
||||
render.SetMaterial(debugwhite)
|
||||
render.DrawBox(self:GetPos(), self:GetAngles(), min, max)
|
||||
render.DrawWireframeBox(self:GetPos(), self:GetAngles(), min, max, color_black, true)
|
||||
return
|
||||
end
|
||||
if draw_wireframe and self.isowner then
|
||||
render.SetBlend(1)
|
||||
render.SetColorModulation(1, 1, 1)
|
||||
render.ModelMaterialOverride(wireframe)
|
||||
self:DrawModel()
|
||||
render.ModelMaterialOverride()
|
||||
else
|
||||
self:DrawModel()
|
||||
end
|
||||
local complex = getComplex(self.crc, self.uvs)
|
||||
if complex then
|
||||
local matrix = self:GetWorldTransformMatrix()
|
||||
if self.scale then
|
||||
matrix:SetScale(self.scale)
|
||||
end
|
||||
cam.PushModelMatrix(matrix)
|
||||
for i = 1, #complex do
|
||||
complex[i]:Draw()
|
||||
end
|
||||
cam.PopModelMatrix()
|
||||
end
|
||||
end
|
||||
|
||||
local function drawMesh(self)
|
||||
local meshes = recycle[self.crc] and recycle[self.crc].meshes[self.uvs]
|
||||
return meshes and meshes.basic or empty
|
||||
end
|
||||
|
||||
local matrix = Matrix()
|
||||
local function refresh(self, info)
|
||||
if not IsValid(info.ent) then
|
||||
info.ent = ents.CreateClientside("base_anim")
|
||||
info.ent:SetModel("models/hunter/plates/plate.mdl")
|
||||
info.ent:DrawShadow(false)
|
||||
info.ent.Draw = drawModel
|
||||
info.ent.GetRenderMesh = drawMesh
|
||||
info.ent:Spawn()
|
||||
info.ent:Activate()
|
||||
end
|
||||
|
||||
info.ent:SetParent(self)
|
||||
info.ent:SetPos(self:GetPos())
|
||||
info.ent:SetAngles(self:GetAngles())
|
||||
info.ent:SetMaterial(info.mat)
|
||||
info.ent:SetColor(info.col)
|
||||
info.ent:SetRenderMode(info.col.a == 255 and RENDERMODE_NORMAL or RENDERMODE_TRANSCOLOR)
|
||||
info.ent.RenderGroup = info.col.a == 255 and RENDERGROUP_OPAQUE or RENDERGROUP_BOTH
|
||||
|
||||
if info.scale.x ~= 1 or info.scale.y ~= 1 or info.scale.z ~= 1 then
|
||||
matrix:SetScale(info.scale)
|
||||
info.ent:EnableMatrix("RenderMultiply", matrix)
|
||||
info.ent.scale = info.scale
|
||||
else
|
||||
info.ent:DisableMatrix("RenderMultiply")
|
||||
info.ent.scale = nil
|
||||
end
|
||||
|
||||
info.ent.crc = info.crc
|
||||
info.ent.uvs = info.uvs
|
||||
info.ent.isowner = self.isowner
|
||||
|
||||
if checkdownload(self, info.crc) then
|
||||
local mdata = checkmesh(info.crc, info.uvs)
|
||||
if mdata and mdata.ready then
|
||||
info.ent:SetRenderBounds(mdata.vmins, mdata.vmaxs)
|
||||
end
|
||||
end
|
||||
|
||||
setuser(self, info.crc, true)
|
||||
end
|
||||
|
||||
local function refreshAll(self, prop2mesh_controllers)
|
||||
for k, info in pairs(prop2mesh_controllers) do
|
||||
refresh(self, info)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function discard(self, prop2mesh_controllers)
|
||||
for _, info in pairs(prop2mesh_controllers) do
|
||||
if info.ent and IsValid(info.ent) then
|
||||
info.ent:Remove()
|
||||
info.ent = nil
|
||||
end
|
||||
setuser(self, info.crc, false)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
|
||||
]]
|
||||
function ENT:Initialize()
|
||||
self.prop2mesh_controllers = {}
|
||||
end
|
||||
|
||||
function ENT:Draw()
|
||||
self:DrawModel()
|
||||
end
|
||||
|
||||
function ENT:Think()
|
||||
if not self.prop2mesh_sync then
|
||||
if CPPI then
|
||||
self.isowner = self:CPPIGetOwner() == LocalPlayer()
|
||||
else
|
||||
self.isowner = true
|
||||
end
|
||||
|
||||
refreshAll(self, self.prop2mesh_controllers)
|
||||
|
||||
net.Start("prop2mesh_sync")
|
||||
net.WriteEntity(self)
|
||||
net.WriteString(self.prop2mesh_synctime or "")
|
||||
net.SendToServer()
|
||||
|
||||
self.prop2mesh_refresh = nil
|
||||
self.prop2mesh_sync = true
|
||||
end
|
||||
|
||||
if self.prop2mesh_refresh then
|
||||
refreshAll(self, self.prop2mesh_controllers)
|
||||
|
||||
self.prop2mesh_refresh = nil
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:OnRemove()
|
||||
local snapshot = self.prop2mesh_controllers
|
||||
if not snapshot or next(snapshot) == nil then
|
||||
return
|
||||
end
|
||||
timer.Simple(0, function()
|
||||
if IsValid(self) then
|
||||
return
|
||||
end
|
||||
discard(self, snapshot)
|
||||
end)
|
||||
end
|
||||
|
||||
function ENT:GetAllDataReady()
|
||||
for k, info in ipairs(self.prop2mesh_controllers) do
|
||||
local crc = info.crc
|
||||
if not crc or crc == "!none" then
|
||||
goto CONTINUE
|
||||
end
|
||||
|
||||
if not recycle[crc] or not recycle[crc].zip then
|
||||
return false
|
||||
else
|
||||
local meshes = recycle[crc].meshes[info.uvs]
|
||||
if meshes and not meshes.ready then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
::CONTINUE::
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function ENT:GetDownloadProgress()
|
||||
local max
|
||||
for i = 1, #self.prop2mesh_controllers do
|
||||
local stream = recycle[self.prop2mesh_controllers[i].crc].stream
|
||||
if stream then
|
||||
if not max then max = 0 end
|
||||
local progress = stream:GetProgress()
|
||||
if max < progress then
|
||||
max = progress
|
||||
end
|
||||
end
|
||||
end
|
||||
return max
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
|
||||
]]
|
||||
local kvpass = {}
|
||||
kvpass.crc = function(self, info, val)
|
||||
local crc = info.crc
|
||||
info.crc = val
|
||||
|
||||
local keepdata
|
||||
for k, v in pairs(self.prop2mesh_controllers) do
|
||||
if v.crc == crc then
|
||||
keepdata = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not keepdata then
|
||||
setuser(self, crc, false)
|
||||
end
|
||||
end
|
||||
|
||||
local function safeuvs(val)
|
||||
val = math.abs(math.floor(tonumber(val) or 0))
|
||||
if val > 512 then val = 512 end
|
||||
return val
|
||||
end
|
||||
|
||||
kvpass.uvs = function(self, info, val)
|
||||
info.uvs = safeuvs(val)
|
||||
end
|
||||
|
||||
-- https:--github.com/wiremod/wire/blob/1a0c31105d5a02a243cf042ea413867fb569ab4c/lua/wire/wireshared.lua#L56
|
||||
local function normalizedFilepath(path)
|
||||
local null = string.find(path, "\x00", 1, true)
|
||||
|
||||
if null then
|
||||
path = string.sub(path, 1, null - 1)
|
||||
end
|
||||
|
||||
local tbl = string.Explode("[/\\]+", path, true)
|
||||
local i = 1
|
||||
|
||||
while i <= #tbl do
|
||||
if tbl[i] == "." or tbl[i] == "" then
|
||||
table.remove(tbl, i)
|
||||
elseif tbl[i] == ".." then
|
||||
table.remove(tbl, i)
|
||||
|
||||
if i > 1 then
|
||||
i = i - 1
|
||||
table.remove(tbl, i)
|
||||
end
|
||||
else
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
return table.concat(tbl, "/")
|
||||
end
|
||||
|
||||
local baddies = {
|
||||
["effects/ar2_altfire1"] = true,
|
||||
["engine/writez"] = true,
|
||||
["pp/copy"] = true,
|
||||
}
|
||||
|
||||
local function safemat(val)
|
||||
val = string.sub(val, 1, 260)
|
||||
local path = string.StripExtension(normalizedFilepath(string.lower(val)))
|
||||
if baddies[path] then return "" end
|
||||
return val
|
||||
end
|
||||
|
||||
kvpass.mat = function(self, info, val)
|
||||
info.mat = safemat(val)
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
|
||||
]]
|
||||
net.Receive("prop2mesh_update", function(len)
|
||||
local self = net.ReadEntity()
|
||||
if not prop2mesh.isValid(self) then
|
||||
return
|
||||
end
|
||||
|
||||
local synctime = net.ReadString()
|
||||
|
||||
for index, update in pairs(net.ReadTable()) do
|
||||
local info = self.prop2mesh_controllers[index]
|
||||
if not info then
|
||||
self.prop2mesh_sync = nil
|
||||
return
|
||||
end
|
||||
for key, val in pairs(update) do
|
||||
if kvpass[key] then kvpass[key](self, info, val) else info[key] = val end
|
||||
end
|
||||
refresh(self, info)
|
||||
end
|
||||
|
||||
self.prop2mesh_synctime = synctime
|
||||
self.prop2mesh_triggertool = true
|
||||
self.prop2mesh_triggereditor = prop2mesh.editor and true
|
||||
end)
|
||||
|
||||
net.Receive("prop2mesh_sync", function(len)
|
||||
local self = net.ReadEntity()
|
||||
if not prop2mesh.isValid(self) then
|
||||
return
|
||||
end
|
||||
|
||||
discard(self, self.prop2mesh_controllers)
|
||||
|
||||
self.prop2mesh_synctime = net.ReadString()
|
||||
self.prop2mesh_controllers = {}
|
||||
|
||||
for i = 1, net.ReadUInt(8) do
|
||||
self.prop2mesh_controllers[i] = {
|
||||
crc = net.ReadString(),
|
||||
uvs = safeuvs(net.ReadUInt(12)),
|
||||
mat = safemat(net.ReadString()),
|
||||
col = Color(net.ReadUInt(8), net.ReadUInt(8), net.ReadUInt(8), net.ReadUInt(8)),
|
||||
scale = Vector(net.ReadFloat(), net.ReadFloat(), net.ReadFloat()),
|
||||
index = i,
|
||||
}
|
||||
end
|
||||
|
||||
self.prop2mesh_refresh = true
|
||||
self.prop2mesh_triggertool = true
|
||||
self.prop2mesh_triggereditor = prop2mesh.editor and true
|
||||
end)
|
||||
|
||||
prop2mesh.downloads = 0
|
||||
net.Receive("prop2mesh_download", function(len)
|
||||
local crc = net.ReadString()
|
||||
recycle[crc].stream = net.ReadStream(nil, function(data)
|
||||
if not recycle[crc] then
|
||||
recycle[crc] = { users = {}, meshes = {} }
|
||||
end
|
||||
if crc == util.CRC(data) then
|
||||
recycle[crc].zip = data
|
||||
for user in pairs(recycle[crc].users) do
|
||||
for k, info in pairs(user.prop2mesh_controllers) do
|
||||
if info.crc == crc then
|
||||
checkmesh(crc, info.uvs)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
garbage[crc] = SysTime() + 500
|
||||
end
|
||||
recycle[crc].stream = nil
|
||||
prop2mesh.downloads = math.max(0, prop2mesh.downloads - 1)
|
||||
end)
|
||||
prop2mesh.downloads = prop2mesh.downloads + 1
|
||||
end)
|
||||
|
||||
hook.Add("NotifyShouldTransmit", "prop2mesh_sync", function(self, bool)
|
||||
if bool then self.prop2mesh_sync = nil end
|
||||
end)
|
||||
|
||||
hook.Add("OnGamemodeLoaded", "prop2mesh_sync", function()
|
||||
for k, self in ipairs(ents.FindByClass("sent_prop2mesh*")) do
|
||||
self.prop2mesh_sync = nil
|
||||
end
|
||||
end)
|
407
lua/entities/sent_prop2mesh/init.lua
Normal file
407
lua/entities/sent_prop2mesh/init.lua
Normal file
@ -0,0 +1,407 @@
|
||||
--[[
|
||||
|
||||
]]
|
||||
AddCSLuaFile("cl_init.lua")
|
||||
AddCSLuaFile("shared.lua")
|
||||
include("shared.lua")
|
||||
|
||||
local net = net
|
||||
local util = util
|
||||
local table = table
|
||||
local pairs = pairs
|
||||
local ipairs = ipairs
|
||||
local prop2mesh = prop2mesh
|
||||
|
||||
|
||||
--[[
|
||||
|
||||
]]
|
||||
util.AddNetworkString("prop2mesh_sync")
|
||||
util.AddNetworkString("prop2mesh_update")
|
||||
util.AddNetworkString("prop2mesh_download")
|
||||
|
||||
net.Receive("prop2mesh_sync", function(len, pl)
|
||||
local self = net.ReadEntity()
|
||||
if not prop2mesh.isValid(self) then
|
||||
return
|
||||
end
|
||||
if not self.prop2mesh_syncwith then
|
||||
self.prop2mesh_syncwith = {}
|
||||
end
|
||||
self.prop2mesh_syncwith[pl] = net.ReadString()
|
||||
end)
|
||||
|
||||
net.Receive("prop2mesh_download", function(len, pl)
|
||||
local self = net.ReadEntity()
|
||||
if not prop2mesh.isValid(self) then
|
||||
return
|
||||
end
|
||||
local crc = net.ReadString()
|
||||
if self.prop2mesh_partlists[crc] then
|
||||
net.Start("prop2mesh_download")
|
||||
net.WriteString(crc)
|
||||
net.WriteStream(self.prop2mesh_partlists[crc])
|
||||
net.Send(pl)
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
--[[
|
||||
|
||||
]]
|
||||
function ENT:Initialize()
|
||||
self:DrawShadow(false)
|
||||
self:PhysicsInit(SOLID_VPHYSICS)
|
||||
self:SetMoveType(MOVETYPE_VPHYSICS)
|
||||
self:SetSolid(SOLID_VPHYSICS)
|
||||
|
||||
self.prop2mesh_controllers = {}
|
||||
self.prop2mesh_partlists = {}
|
||||
self.prop2mesh_sync = true
|
||||
end
|
||||
|
||||
|
||||
function ENT:Think()
|
||||
if self.prop2mesh_upload_queue then
|
||||
self:SetNetworkedBool("uploading", true)
|
||||
return
|
||||
end
|
||||
|
||||
if self.prop2mesh_sync then
|
||||
self.prop2mesh_updates = nil
|
||||
self.prop2mesh_synctime = SysTime() .. ""
|
||||
self.prop2mesh_syncwith = nil
|
||||
self.prop2mesh_sync = nil
|
||||
|
||||
self:SendControllers()
|
||||
else
|
||||
if self.prop2mesh_syncwith then
|
||||
local syncwith = {}
|
||||
for pl, pltime in pairs(self.prop2mesh_syncwith) do
|
||||
if IsValid(pl) and pltime ~= self.prop2mesh_synctime then
|
||||
syncwith[#syncwith + 1] = pl
|
||||
end
|
||||
end
|
||||
|
||||
if next(syncwith) then
|
||||
self:SendControllers(syncwith)
|
||||
end
|
||||
|
||||
self.prop2mesh_syncwith = nil
|
||||
end
|
||||
if self.prop2mesh_updates then
|
||||
self.prop2mesh_synctime = SysTime() .. ""
|
||||
|
||||
net.Start("prop2mesh_update")
|
||||
net.WriteEntity(self)
|
||||
net.WriteString(self.prop2mesh_synctime)
|
||||
|
||||
for index, update in pairs(self.prop2mesh_updates) do
|
||||
for key in pairs(update) do
|
||||
update[key] = self.prop2mesh_controllers[index][key]
|
||||
end
|
||||
end
|
||||
|
||||
net.WriteTable(self.prop2mesh_updates)
|
||||
net.Broadcast()
|
||||
|
||||
self.prop2mesh_updates = nil
|
||||
else
|
||||
if self:GetNetworkedBool("uploading") then
|
||||
self:SetNetworkedBool("uploading", false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:PreEntityCopy()
|
||||
duplicator.StoreEntityModifier(self, "prop2mesh", {
|
||||
[1] = self.prop2mesh_controllers,
|
||||
[2] = self.prop2mesh_partlists,
|
||||
})
|
||||
end
|
||||
|
||||
function ENT:PostEntityCopy()
|
||||
duplicator.ClearEntityModifier(self, "prop2mesh")
|
||||
end
|
||||
|
||||
function ENT:PostEntityPaste()
|
||||
duplicator.ClearEntityModifier(self, "prop2mesh")
|
||||
end
|
||||
|
||||
duplicator.RegisterEntityModifier("prop2mesh", function(ply, self, dupe)
|
||||
if not prop2mesh.isValid(self) then
|
||||
return
|
||||
end
|
||||
local dupe_controllers = dupe[1]
|
||||
if istable(dupe_controllers) and next(dupe_controllers) and table.IsSequential(dupe_controllers) then
|
||||
self.prop2mesh_sync = true
|
||||
|
||||
local dupe_data = dupe[2]
|
||||
local dupe_lookup = {}
|
||||
|
||||
self.prop2mesh_controllers = {}
|
||||
for k, v in ipairs(dupe_controllers) do
|
||||
local info = self:AddController()
|
||||
self:SetControllerCol(k, v.col)
|
||||
self:SetControllerMat(k, v.mat)
|
||||
self:SetControllerUVS(k, v.uvs)
|
||||
self:SetControllerScale(k, v.scale)
|
||||
|
||||
if dupe_data and dupe_data[v.crc] then
|
||||
dupe_lookup[v.crc] = true
|
||||
info.crc = v.crc
|
||||
end
|
||||
end
|
||||
|
||||
self.prop2mesh_partlists = {}
|
||||
for crc in pairs(dupe_lookup) do
|
||||
self.prop2mesh_partlists[crc] = dupe_data[crc]
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
function ENT:AddController()
|
||||
table.insert(self.prop2mesh_controllers, prop2mesh.getEmpty())
|
||||
self.prop2mesh_sync = true
|
||||
return self.prop2mesh_controllers[#self.prop2mesh_controllers]
|
||||
end
|
||||
|
||||
function ENT:RemoveController(index)
|
||||
if not self.prop2mesh_controllers[index] then
|
||||
return false
|
||||
end
|
||||
|
||||
local crc = self.prop2mesh_controllers[index].crc
|
||||
table.remove(self.prop2mesh_controllers, index)
|
||||
|
||||
local keepdata
|
||||
for k, info in pairs(self.prop2mesh_controllers) do
|
||||
if info.crc == crc then
|
||||
keepdata = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not keepdata then
|
||||
self.prop2mesh_partlists[crc] = nil
|
||||
end
|
||||
|
||||
self.prop2mesh_sync = true
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function ENT:SendControllers(syncwith)
|
||||
net.Start("prop2mesh_sync")
|
||||
|
||||
net.WriteEntity(self)
|
||||
net.WriteString(self.prop2mesh_synctime)
|
||||
net.WriteUInt(#self.prop2mesh_controllers, 8)
|
||||
|
||||
for i = 1, #self.prop2mesh_controllers do
|
||||
local info = self.prop2mesh_controllers[i]
|
||||
net.WriteString(info.crc)
|
||||
net.WriteUInt(info.uvs, 12)
|
||||
net.WriteString(info.mat)
|
||||
net.WriteUInt(info.col.r, 8)
|
||||
net.WriteUInt(info.col.g, 8)
|
||||
net.WriteUInt(info.col.b, 8)
|
||||
net.WriteUInt(info.col.a, 8)
|
||||
net.WriteFloat(info.scale.x)
|
||||
net.WriteFloat(info.scale.y)
|
||||
net.WriteFloat(info.scale.z)
|
||||
end
|
||||
|
||||
if syncwith then
|
||||
net.Send(syncwith)
|
||||
else
|
||||
net.Broadcast()
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:AddControllerUpdate(index, key)
|
||||
if self.prop2mesh_sync or not self.prop2mesh_controllers[index] then
|
||||
return
|
||||
end
|
||||
if not self.prop2mesh_updates then self.prop2mesh_updates = {} end
|
||||
if not self.prop2mesh_updates[index] then self.prop2mesh_updates[index] = {} end
|
||||
self.prop2mesh_updates[index][key] = true
|
||||
end
|
||||
|
||||
function ENT:SetControllerCol(index, val)
|
||||
local info = self.prop2mesh_controllers[index]
|
||||
if (info and IsColor(val)) and (info.col.r ~= val.r or info.col.g ~= val.g or info.col.b ~= val.b or info.col.a ~= val.a) then
|
||||
info.col.r = val.r
|
||||
info.col.g = val.g
|
||||
info.col.b = val.b
|
||||
info.col.a = val.a
|
||||
self:AddControllerUpdate(index, "col")
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:SetControllerMat(index, val)
|
||||
local info = self.prop2mesh_controllers[index]
|
||||
if (info and isstring(val)) and (info.mat ~= val) then
|
||||
info.mat = val
|
||||
self:AddControllerUpdate(index, "mat")
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:SetControllerScale(index, val)
|
||||
local info = self.prop2mesh_controllers[index]
|
||||
if (info and isvector(val)) and (info.scale.x ~= val.x or info.scale.y ~= val.y or info.scale.z ~= val.z) then
|
||||
info.scale.x = val.x
|
||||
info.scale.y = val.y
|
||||
info.scale.z = val.z
|
||||
self:AddControllerUpdate(index, "scale")
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:SetControllerUVS(index, val)
|
||||
local info = self.prop2mesh_controllers[index]
|
||||
if (info and isnumber(val)) and (info.uvs ~= val) then
|
||||
info.uvs = val
|
||||
self:AddControllerUpdate(index, "uvs")
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:ResetControllerData(index)
|
||||
if self.prop2mesh_controllers[index] then
|
||||
self.prop2mesh_controllers[index].crc = "!none"
|
||||
self:AddControllerUpdate(index, "crc")
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:SetControllerData(index, partlist, uvs)
|
||||
local info = self.prop2mesh_controllers[index]
|
||||
if not info or not partlist then
|
||||
return
|
||||
end
|
||||
|
||||
if not next(partlist) then
|
||||
self:ResetControllerData(index)
|
||||
return
|
||||
end
|
||||
|
||||
prop2mesh.sanitizeCustom(partlist)
|
||||
|
||||
local json = util.TableToJSON(partlist)
|
||||
if not json then
|
||||
return
|
||||
end
|
||||
|
||||
local data = util.Compress(json)
|
||||
local dcrc = util.CRC(data)
|
||||
local icrc = info.crc
|
||||
|
||||
if icrc == dcrc then
|
||||
return
|
||||
end
|
||||
|
||||
self.prop2mesh_partlists[dcrc] = data
|
||||
|
||||
info.crc = dcrc
|
||||
self:AddControllerUpdate(index, "crc")
|
||||
if uvs then
|
||||
info.uvs = uvs
|
||||
self:AddControllerUpdate(index, "uvs")
|
||||
end
|
||||
|
||||
local keepdata
|
||||
for k, v in pairs(self.prop2mesh_controllers) do
|
||||
if v.crc == icrc then
|
||||
keepdata = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not keepdata then
|
||||
self.prop2mesh_partlists[icrc] = nil
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:GetControllerData(index, nodecomp)
|
||||
if not self.prop2mesh_controllers[index] then
|
||||
return
|
||||
end
|
||||
local ret = self.prop2mesh_partlists[self.prop2mesh_controllers[index].crc]
|
||||
if not ret or nodecomp then
|
||||
return ret
|
||||
end
|
||||
return util.JSONToTable(util.Decompress(ret))
|
||||
end
|
||||
|
||||
function ENT:ToolDataByINDEX(index, tool)
|
||||
if not self.prop2mesh_controllers[index] then
|
||||
return false
|
||||
end
|
||||
|
||||
local pos = self:GetPos()
|
||||
local ang = self:GetAngles()
|
||||
|
||||
if tool:GetClientNumber("tool_setautocenter") ~= 0 then
|
||||
pos = Vector()
|
||||
local num = 0
|
||||
for ent, _ in pairs(tool.selection) do
|
||||
pos = pos + ent:GetPos()
|
||||
num = num + 1
|
||||
end
|
||||
pos = pos * (1 / num)
|
||||
end
|
||||
|
||||
self:SetControllerData(index, prop2mesh.partsFromEnts(tool.selection, pos, ang), tool:GetClientNumber("tool_setuvsize"))
|
||||
end
|
||||
|
||||
function ENT:ToolDataAUTO(tool)
|
||||
local autocenter = tool:GetClientNumber("tool_setautocenter") ~= 0
|
||||
local pos, ang, num
|
||||
|
||||
if autocenter then
|
||||
pos = Vector()
|
||||
ang = self:GetAngles()
|
||||
num = 0
|
||||
else
|
||||
pos = self:GetPos()
|
||||
ang = self:GetAngles()
|
||||
end
|
||||
|
||||
local sorted = {}
|
||||
for k, v in pairs(tool.selection) do
|
||||
local vmat = v.mat
|
||||
if vmat == "" then vmat = prop2mesh.defaultmat end
|
||||
|
||||
if not sorted[vmat] then
|
||||
sorted[vmat] = {}
|
||||
end
|
||||
local key = string.format("%d %d %d %d", v.col.r, v.col.g, v.col.b, v.col.a)
|
||||
if not sorted[vmat][key] then
|
||||
sorted[vmat][key] = {}
|
||||
end
|
||||
table.insert(sorted[vmat][key], k)
|
||||
if autocenter then
|
||||
pos = pos + k:GetPos()
|
||||
num = num + 1
|
||||
end
|
||||
end
|
||||
|
||||
if autocenter then
|
||||
pos = pos * (1 / num)
|
||||
end
|
||||
|
||||
local uvs = tool:GetClientNumber("tool_setuvsize")
|
||||
|
||||
for kmat, vmat in pairs(sorted) do
|
||||
for kcol, vcol in pairs(vmat) do
|
||||
local parts = prop2mesh.partsFromEnts(vcol, pos, ang)
|
||||
if parts then
|
||||
local info = self:AddController()
|
||||
local temp = string.Explode(" ", kcol)
|
||||
info.col = Color(temp[1], temp[2], temp[3], temp[4])
|
||||
info.mat = kmat
|
||||
info.uvs = parts.uvs or uvs
|
||||
if parts.uvs then parts.uvs = nil end
|
||||
self:SetControllerData(#self.prop2mesh_controllers, parts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
98
lua/entities/sent_prop2mesh/shared.lua
Normal file
98
lua/entities/sent_prop2mesh/shared.lua
Normal file
@ -0,0 +1,98 @@
|
||||
--[[
|
||||
|
||||
]]
|
||||
DEFINE_BASECLASS("base_anim")
|
||||
|
||||
ENT.PrintName = "sent_prop2mesh"
|
||||
ENT.Author = "shadowscion"
|
||||
ENT.AdminOnly = false
|
||||
ENT.Spawnable = true
|
||||
ENT.Category = "prop2mesh"
|
||||
ENT.RenderGroup = RENDERGROUP_BOTH
|
||||
|
||||
cleanup.Register("sent_prop2mesh")
|
||||
|
||||
function ENT:SpawnFunction(ply, tr, ClassName)
|
||||
if not tr.Hit then
|
||||
return
|
||||
end
|
||||
|
||||
local ent = ents.Create(ClassName)
|
||||
ent:SetModel("models/p2m/cube.mdl")
|
||||
ent:SetPos(tr.HitPos + tr.HitNormal)
|
||||
ent:Spawn()
|
||||
ent:Activate()
|
||||
|
||||
return ent
|
||||
end
|
||||
|
||||
function ENT:GetControllerCol(index)
|
||||
return self.prop2mesh_controllers[index] and self.prop2mesh_controllers[index].col
|
||||
end
|
||||
|
||||
function ENT:GetControllerMat(index)
|
||||
return self.prop2mesh_controllers[index] and self.prop2mesh_controllers[index].mat
|
||||
end
|
||||
|
||||
function ENT:GetControllerUVS(index)
|
||||
return self.prop2mesh_controllers[index] and self.prop2mesh_controllers[index].uvs
|
||||
end
|
||||
|
||||
function ENT:GetControllerCRC(index)
|
||||
return self.prop2mesh_controllers[index] and self.prop2mesh_controllers[index].crc
|
||||
end
|
||||
|
||||
function ENT:GetControllerScale(index)
|
||||
return self.prop2mesh_controllers[index] and self.prop2mesh_controllers[index].scale
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
|
||||
]]
|
||||
properties.Add("prop2mesh", {
|
||||
MenuLabel = "Edit prop2mesh",
|
||||
MenuIcon = "icon16/image_edit.png",
|
||||
PrependSpacer = true,
|
||||
Order = 3001,
|
||||
|
||||
Filter = function(self, ent, pl)
|
||||
return prop2mesh.isValid(ent) and gamemode.Call("CanProperty", pl, "prop2mesh", ent)
|
||||
end,
|
||||
|
||||
Action = function(self, ent) -- CLIENT
|
||||
if not self:Filter(ent, LocalPlayer()) then
|
||||
if IsValid(prop2mesh.editor) then
|
||||
prop2mesh.editor:Remove()
|
||||
end
|
||||
return
|
||||
end
|
||||
if not IsValid(prop2mesh.editor) then
|
||||
prop2mesh.editor = g_ContextMenu:Add("prop2mesh_editor")
|
||||
elseif prop2mesh.editor.Entity == ent then
|
||||
return
|
||||
end
|
||||
|
||||
local h = math.floor(ScrH() - 90)
|
||||
local w = 420
|
||||
|
||||
prop2mesh.editor:SetPos(ScrW() - w - 30, ScrH() - h - 30)
|
||||
prop2mesh.editor:SetSize(w, h)
|
||||
prop2mesh.editor:SetDraggable(false)
|
||||
|
||||
if IsValid(prop2mesh.editor.Entity) then
|
||||
prop2mesh.editor.Entity:RemoveCallOnRemove("prop2mesh_editor_close")
|
||||
end
|
||||
|
||||
prop2mesh.editor.Entity = ent
|
||||
prop2mesh.editor.Entity:CallOnRemove("prop2mesh_editor_close", function()
|
||||
prop2mesh.editor:Remove()
|
||||
end)
|
||||
|
||||
prop2mesh.editor:SetTitle(tostring(prop2mesh.editor.Entity))
|
||||
prop2mesh.editor:RemakeTree()
|
||||
end,
|
||||
|
||||
Receive = function(self, len, pl) -- SERVER
|
||||
end
|
||||
})
|
139
lua/entities/sent_prop2mesh_legacy.lua
Normal file
139
lua/entities/sent_prop2mesh_legacy.lua
Normal file
@ -0,0 +1,139 @@
|
||||
AddCSLuaFile()
|
||||
DEFINE_BASECLASS("sent_prop2mesh")
|
||||
|
||||
ENT.PrintName = "prop2mesh_legacy"
|
||||
ENT.Author = "shadowscion"
|
||||
ENT.AdminOnly = false
|
||||
ENT.Spawnable = true
|
||||
ENT.Category = "prop2mesh"
|
||||
ENT.RenderGroup = RENDERGROUP_BOTH
|
||||
|
||||
cleanup.Register("sent_prop2mesh_legacy")
|
||||
|
||||
if CLIENT then
|
||||
return
|
||||
end
|
||||
|
||||
function ENT:Think()
|
||||
local info = self.prop2mesh_controllers[1]
|
||||
if info then
|
||||
local val = self:GetColor()
|
||||
if info.col.r ~= val.r or info.col.g ~= val.g or info.col.b ~= val.b or info.col.a ~= val.a then
|
||||
info.col.r = val.r
|
||||
info.col.g = val.g
|
||||
info.col.b = val.b
|
||||
info.col.a = val.a
|
||||
self:AddControllerUpdate(1, "col")
|
||||
end
|
||||
local val = self:GetMaterial()
|
||||
if info.mat ~= val then
|
||||
info.mat = val
|
||||
self:AddControllerUpdate(1, "mat")
|
||||
end
|
||||
end
|
||||
BaseClass.Think(self)
|
||||
end
|
||||
|
||||
function ENT:AddController(uvs, scale)
|
||||
for k, v in pairs(self.prop2mesh_controllers) do
|
||||
self:RemoveController(k)
|
||||
end
|
||||
|
||||
BaseClass.AddController(self)
|
||||
|
||||
self:SetControllerUVS(1, uvs)
|
||||
self:SetControllerScale(1, scale)
|
||||
|
||||
return self.prop2mesh_controllers[1]
|
||||
end
|
||||
|
||||
function ENT:PostEntityPaste()
|
||||
duplicator.ClearEntityModifier(self, "p2m_mods")
|
||||
duplicator.ClearEntityModifier(self, "p2m_packets")
|
||||
duplicator.ClearEntityModifier(self, "prop2mesh")
|
||||
end
|
||||
|
||||
|
||||
-- COMPATIBILITY
|
||||
local function getLegacyMods(data)
|
||||
local uvs, scale
|
||||
if istable(data) then
|
||||
uvs = tonumber(data.tscale)
|
||||
scale = tonumber(data.mscale)
|
||||
if scale then
|
||||
scale = Vector(scale, scale, scale)
|
||||
end
|
||||
end
|
||||
return uvs, scale
|
||||
end
|
||||
|
||||
local function getLegacyParts(data)
|
||||
local parts
|
||||
if istable(data) then
|
||||
local zip = {}
|
||||
for i = 1, #data do
|
||||
zip[#zip + 1] = data[i][1]
|
||||
end
|
||||
zip = table.concat(zip)
|
||||
if util.CRC(zip) == data.crc then
|
||||
local json = util.JSONToTable(util.Decompress(zip))
|
||||
if next(json) then
|
||||
parts = {}
|
||||
for k, v in ipairs(json) do
|
||||
local part = { pos = v.pos, ang = v.ang, clips = v.clips, bodygroup = v.bgrp }
|
||||
|
||||
if v.scale and (v.scale.x ~= 1 or v.scale.y ~= 1 or v.scale.z ~= 1) then
|
||||
part.scale = v.scale
|
||||
end
|
||||
|
||||
if v.obj then
|
||||
local crc = util.CRC(v.obj)
|
||||
if not parts.custom then parts.custom = {} end
|
||||
parts.custom[crc] = v.obj
|
||||
|
||||
part.objd = crc
|
||||
part.objn = v.name or crc
|
||||
part.vsmooth = tonumber(v.smooth)
|
||||
part.vinvert = v.flip and 1 or nil
|
||||
else
|
||||
if v.holo then part.holo = v.mdl else part.prop = v.mdl end
|
||||
|
||||
part.vinside = v.inv and 1 or nil
|
||||
part.vsmooth = v.flat and 1 or nil
|
||||
end
|
||||
|
||||
parts[#parts + 1] = part
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return parts
|
||||
end
|
||||
|
||||
local function getLegacyInfo(data)
|
||||
if not data then return nil end
|
||||
local uvs, scale = getLegacyMods(data.p2m_mods)
|
||||
return uvs, scale, getLegacyParts(data.p2m_packets)
|
||||
end
|
||||
|
||||
duplicator.RegisterEntityClass("gmod_ent_p2m", function(ply, data)
|
||||
local compat = ents.Create("sent_prop2mesh_legacy")
|
||||
if not IsValid(compat) then
|
||||
return false
|
||||
end
|
||||
|
||||
duplicator.DoGeneric(compat, data)
|
||||
compat:Spawn()
|
||||
compat:Activate()
|
||||
|
||||
if CPPI and compat.CPPISetOwner then
|
||||
compat:CPPISetOwner(ply)
|
||||
end
|
||||
|
||||
local uvs, scale, parts = getLegacyInfo(data.EntityMods)
|
||||
|
||||
compat:AddController(uvs, scale)
|
||||
compat:SetControllerData(1, parts)
|
||||
|
||||
return compat
|
||||
end, "Data")
|
690
lua/prop2mesh/cl_editor.lua
Normal file
690
lua/prop2mesh/cl_editor.lua
Normal file
@ -0,0 +1,690 @@
|
||||
local string = string
|
||||
local table = table
|
||||
local math = math
|
||||
local net = net
|
||||
|
||||
|
||||
--[[
|
||||
|
||||
uploader
|
||||
|
||||
]]
|
||||
local filecache_data = {}
|
||||
local filecache_keys = {}
|
||||
|
||||
local upstreams = {}
|
||||
|
||||
net.Receive("prop2mesh_upload_start", function(len)
|
||||
local eid = net.ReadUInt(16)
|
||||
for i = 1, net.ReadUInt(8) do
|
||||
local crc = net.ReadString()
|
||||
if filecache_keys[crc] and filecache_keys[crc].data then
|
||||
net.Start("prop2mesh_upload")
|
||||
net.WriteUInt(eid, 16)
|
||||
net.WriteString(crc)
|
||||
upstreams[crc] = net.WriteStream(filecache_keys[crc].data)
|
||||
net.SendToServer()
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
local function upstreamProgress()
|
||||
local max = 0
|
||||
for crc, stream in pairs(upstreams) do
|
||||
local client = next(stream.clients)
|
||||
if client and stream.clients[client] then
|
||||
client = stream.clients[client]
|
||||
if client.finished then
|
||||
upstreams[crc] = nil
|
||||
else
|
||||
local progress = client.progress / stream.numchunks
|
||||
if max < progress then
|
||||
max = progress
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return max
|
||||
end
|
||||
|
||||
local function formatOBJ(filestr)
|
||||
local vcount = 0
|
||||
local condensed = {}
|
||||
|
||||
for line in string.gmatch(filestr, "(.-)\n") do
|
||||
local temp = string.Explode(" ", string.gsub(string.Trim(line), "%s+", " "))
|
||||
local head = table.remove(temp, 1)
|
||||
|
||||
if head == "f" then
|
||||
local v1 = string.Explode("/", temp[1])
|
||||
local v2 = string.Explode("/", temp[2])
|
||||
for i = 3, #temp do
|
||||
local v3 = string.Explode("/", temp[i])
|
||||
condensed[#condensed + 1] = string.format("f %d %d %d\n", v1[1], v2[1], v3[1])
|
||||
v2 = v3
|
||||
end
|
||||
else
|
||||
if head == "v" then
|
||||
local x = tonumber(temp[1])
|
||||
local y = tonumber(temp[2])
|
||||
local z = tonumber(temp[3])
|
||||
|
||||
x = math.abs(x) < 1e-4 and 0 or x
|
||||
y = math.abs(y) < 1e-4 and 0 or y
|
||||
z = math.abs(z) < 1e-4 and 0 or z
|
||||
|
||||
condensed[#condensed + 1] = string.format("v %s %s %s\n", x, y, z)
|
||||
vcount = vcount + 1
|
||||
end
|
||||
end
|
||||
|
||||
if vcount > 63999 then return end
|
||||
end
|
||||
|
||||
return table.concat(condensed)
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
|
||||
skin and panel overrides
|
||||
|
||||
]]
|
||||
local theme = {}
|
||||
theme.font = "prop2mesheditor"
|
||||
surface.CreateFont(theme.font, { size = 15, weight = 400, font = "Roboto Mono" })
|
||||
|
||||
theme.colorText_add = Color(100, 200, 100)
|
||||
theme.colorText_edit = Color(100, 100, 255)
|
||||
theme.colorText_kill = Color(255, 100, 100)
|
||||
theme.colorText_default = Color(100, 100, 100)
|
||||
theme.colorMain = Color(75, 75, 75)
|
||||
theme.colorTree = Color(245, 245, 245)
|
||||
|
||||
local TreeAddNode, NodeAddNode
|
||||
function TreeAddNode(self, text, icon, font)
|
||||
local node = DTree.AddNode(self, string.lower(text), icon)
|
||||
node.Label:SetFont(font or theme.font)
|
||||
node.Label:SetTextColor(theme.colorText_default)
|
||||
node.AddNode = NodeAddNode
|
||||
return node
|
||||
end
|
||||
function NodeAddNode(self, text, icon, font)
|
||||
local node = DTree_Node.AddNode(self, string.lower(text), icon)
|
||||
node.Label:SetFont(font or theme.font)
|
||||
node.Label:SetTextColor(theme.colorText_default)
|
||||
node.AddNode = NodeAddNode
|
||||
return node
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
|
||||
editor components
|
||||
|
||||
]]
|
||||
local function changetable(partnode, key, diff)
|
||||
if not partnode.mod then
|
||||
if partnode.set then
|
||||
partnode.set[key] = diff and partnode.new[key] or nil
|
||||
|
||||
local color = next(partnode.set) and theme.colorText_edit
|
||||
partnode.Label:SetTextColor(color or theme.colorText_default)
|
||||
partnode.Icon:SetImageColor(color or color_white)
|
||||
end
|
||||
return
|
||||
end
|
||||
if diff then
|
||||
if not partnode.mod[partnode.num] then
|
||||
partnode.mod[partnode.num] = {}
|
||||
end
|
||||
partnode.mod[partnode.num][key] = partnode.new[key]
|
||||
local color = partnode.mod[partnode.num].kill and theme.colorText_kill or theme.colorText_edit
|
||||
partnode.Label:SetTextColor(color)
|
||||
partnode.Icon:SetImageColor(color)
|
||||
elseif partnode.mod[partnode.num] then
|
||||
partnode.mod[partnode.num][key] = nil
|
||||
if not next(partnode.mod[partnode.num]) then
|
||||
partnode.mod[partnode.num] = nil
|
||||
partnode.Label:SetTextColor(theme.colorText_default)
|
||||
partnode.Icon:SetImageColor(color_white)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function callbackVector(partnode, name, text, key, i, val)
|
||||
if partnode.new[key][i] == val then
|
||||
return
|
||||
end
|
||||
|
||||
partnode.new[key][i] = val
|
||||
|
||||
if partnode.new[key][i] ~= partnode.old[key][i] then
|
||||
name.Label:SetTextColor((partnode.mod or partnode.set) and theme.colorText_edit or theme.colorText_add)
|
||||
text:SetTextColor((partnode.mod or partnode.set) and theme.colorText_edit or theme.colorText_add)
|
||||
|
||||
changetable(partnode, key, true)
|
||||
else
|
||||
name.Label:SetTextColor(theme.colorText_default)
|
||||
text:SetTextColor(theme.colorText_default)
|
||||
|
||||
changetable(partnode, key, partnode.new[key] ~= partnode.old[key])
|
||||
end
|
||||
end
|
||||
|
||||
local function registerVector(partnode, name, key)
|
||||
local node = partnode:AddNode(name, "icon16/bullet_black.png"):AddNode("")
|
||||
node.ShowIcons = function() return false end
|
||||
node:SetDrawLines(false)
|
||||
|
||||
local x = vgui.Create("DTextEntry", node)
|
||||
local y = vgui.Create("DTextEntry", node)
|
||||
local z = vgui.Create("DTextEntry", node)
|
||||
|
||||
node.PerformLayout = function(self, w, h)
|
||||
DTree_Node.PerformLayout(self, w, h)
|
||||
|
||||
local spacing = 4
|
||||
local cellWidth = math.ceil((w - 48) / 3) - spacing
|
||||
|
||||
x:SetPos(24, 0)
|
||||
x:SetSize(cellWidth, h)
|
||||
|
||||
y:SetPos(24 + cellWidth + spacing, 0)
|
||||
y:SetSize(cellWidth, h)
|
||||
|
||||
z:SetPos(24 + (cellWidth + spacing) * 2, 0)
|
||||
z:SetSize(cellWidth, h)
|
||||
end
|
||||
|
||||
for i, v in ipairs({x, y, z}) do
|
||||
v:SetFont(theme.font)
|
||||
v:SetNumeric(true)
|
||||
v.OnValueChange = function(self, val)
|
||||
if not tonumber(val) then
|
||||
self:SetText(string.format("%.4f", partnode.new[key][i]))
|
||||
return
|
||||
end
|
||||
self:SetText(string.format("%.4f", val))
|
||||
callbackVector(partnode, node:GetParentNode(), self, key, i, val)
|
||||
end
|
||||
v:SetValue(partnode.new[key][i])
|
||||
end
|
||||
end
|
||||
|
||||
local function callbackBoolean(partnode, name, key, val)
|
||||
if partnode.new[key] == val then
|
||||
return
|
||||
end
|
||||
|
||||
partnode.new[key] = val
|
||||
|
||||
if partnode.new[key] ~= partnode.old[key] then
|
||||
name:SetTextColor((partnode.mod or partnode.set) and theme.colorText_edit or theme.colorText_add)
|
||||
|
||||
changetable(partnode, key, true)
|
||||
else
|
||||
name:SetTextColor(theme.colorText_default)
|
||||
|
||||
changetable(partnode, key, false)
|
||||
end
|
||||
end
|
||||
|
||||
local function registerBoolean(partnode, name, key)
|
||||
local node = partnode:AddNode("")
|
||||
node.ShowIcons = function() return false end
|
||||
|
||||
local x = vgui.Create("DCheckBoxLabel", node)
|
||||
x:SetText(name)
|
||||
x:Dock(LEFT)
|
||||
x:DockMargin(24, 0, 4, 0)
|
||||
x.Label:SetDisabled(true)
|
||||
|
||||
x.OnChange = function(self, val)
|
||||
callbackBoolean(partnode, self, key, val and 1 or 0)
|
||||
end
|
||||
|
||||
x:SetValue(partnode.new[key] == 1)
|
||||
x:SetTextColor(theme.colorText_default)
|
||||
x:SetFont(theme.font)
|
||||
end
|
||||
|
||||
local function registerFloat(partnode, name, key, min, max)
|
||||
local node = partnode:AddNode("")
|
||||
node.ShowIcons = function() return false end
|
||||
|
||||
local x = vgui.Create("DCheckBoxLabel", node)
|
||||
x:Dock(LEFT)
|
||||
x:DockMargin(24, 0, 4, 0)
|
||||
x:SetText(name)
|
||||
x:SetFont(theme.font)
|
||||
x:SetTextColor(theme.colorText_default)
|
||||
|
||||
local s = vgui.Create("DNumSlider", node)
|
||||
s.Scratch:SetVisible(false)
|
||||
s.Label:SetVisible(false)
|
||||
s:SetWide(128)
|
||||
s:DockMargin(24, 0, 4, 0)
|
||||
s:Dock(LEFT)
|
||||
s:SetMin(min)
|
||||
s:SetMax(max)
|
||||
s:SetDecimals(0)
|
||||
|
||||
s.OnValueChanged = function(self, val)
|
||||
x:SetChecked(val > 0)
|
||||
callbackBoolean(partnode, x, key, math.Round(val))
|
||||
end
|
||||
|
||||
x.OnChange = function(self, value)
|
||||
self:SetChecked(s:GetValue() > 0)
|
||||
end
|
||||
|
||||
s:SetValue(partnode.new[key])
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
|
||||
menus
|
||||
|
||||
]]
|
||||
local function installEditors(partnode)
|
||||
registerVector(partnode, "pos", "pos")
|
||||
registerVector(partnode, "ang", "ang")
|
||||
registerVector(partnode, "scale", "scale")
|
||||
registerBoolean(partnode, "render_inside", "vinside")
|
||||
|
||||
if partnode.new.objd then
|
||||
registerBoolean(partnode, "render_invert", "vinvert")
|
||||
registerFloat(partnode, "render_smooth", "vsmooth", 0, 180)
|
||||
else
|
||||
registerBoolean(partnode, "render_flat", "vsmooth")
|
||||
end
|
||||
|
||||
partnode:ExpandRecurse(true)
|
||||
partnode.edited = true
|
||||
end
|
||||
|
||||
local function partcopy(from)
|
||||
local a = { pos = Vector(), ang = Angle(), scale = Vector(1,1,1), vinvert = 0, vinside = 0, vsmooth = 0 }
|
||||
local b = { pos = Vector(), ang = Angle(), scale = Vector(1,1,1), vinvert = 0, vinside = 0, vsmooth = 0 }
|
||||
for k, v in pairs(from) do
|
||||
if isnumber(v) or isstring(v) then
|
||||
a[k] = v
|
||||
b[k] = v
|
||||
elseif isvector(v) then
|
||||
a[k] = Vector(v)
|
||||
b[k] = Vector(v)
|
||||
elseif isangle(v) then
|
||||
a[k] = Angle(v)
|
||||
b[k] = Angle(v)
|
||||
end
|
||||
end
|
||||
return a, b
|
||||
end
|
||||
|
||||
local function partmenu(frame, partnode)
|
||||
local menu = DermaMenu()
|
||||
|
||||
if not partnode.edited then
|
||||
menu:AddOption("edit part", function()
|
||||
installEditors(partnode)
|
||||
end):SetIcon("icon16/brick_edit.png")
|
||||
end
|
||||
|
||||
menu:AddOption(partnode.new.kill and "undo remove part" or "remove part", function()
|
||||
partnode.new.kill = not partnode.new.kill
|
||||
changetable(partnode, "kill", partnode.new.kill)
|
||||
partnode:SetExpanded(false, true)
|
||||
end):SetIcon("icon16/brick_delete.png")
|
||||
|
||||
menu:AddOption("cancel"):SetIcon("icon16/cancel.png")
|
||||
menu:Open()
|
||||
end
|
||||
|
||||
local function objmenu(frame, objnode)
|
||||
local menu = DermaMenu()
|
||||
|
||||
menu:AddOption("remove model", function()
|
||||
objnode:Remove()
|
||||
objnode = nil
|
||||
end):SetIcon("icon16/brick_delete.png")
|
||||
|
||||
menu:AddOption("cancel"):SetIcon("icon16/cancel.png")
|
||||
menu:Open()
|
||||
end
|
||||
|
||||
local function attach(pathnode)
|
||||
local filepath = pathnode.path
|
||||
local filestr = file.Read(pathnode.path)
|
||||
local filecrc = tostring(util.CRC(filestr))
|
||||
|
||||
if not filecache_data[filecrc] then
|
||||
local valid, contents = pcall(formatOBJ, filestr)
|
||||
if not valid then
|
||||
chat.AddText(Color(255, 125, 125), "unexpected error!")
|
||||
return
|
||||
end
|
||||
if not contents then
|
||||
chat.AddText(Color(255, 125, 125), ".obj must have fewer than 64000 vertices!")
|
||||
return
|
||||
end
|
||||
local crc = tostring(util.CRC(contents))
|
||||
filecache_data[filecrc] = { crc = crc, data = util.Compress(contents) }
|
||||
filecache_keys[crc] = filecache_data[filecrc]
|
||||
end
|
||||
if not filecache_data[filecrc] then
|
||||
chat.AddText(Color(255, 125, 125), "unexpected error!")
|
||||
return
|
||||
end
|
||||
|
||||
local filename = string.GetFileFromFilename(pathnode.path)
|
||||
local rootnode = pathnode:GetParentNode()
|
||||
local partnode = rootnode.list:AddNode(string.format("[new!] %s", filename), "icon16/brick.png")
|
||||
partnode.Label:SetTextColor(theme.colorText_add)
|
||||
partnode.Icon:SetImageColor(theme.colorText_add)
|
||||
|
||||
partnode.menu = objmenu
|
||||
partnode.new, partnode.old = partcopy({ objn = filename, objd = filecache_data[filecrc].crc })
|
||||
rootnode.add[partnode] = true
|
||||
|
||||
installEditors(partnode)
|
||||
|
||||
partnode:ExpandTo(true)
|
||||
end
|
||||
|
||||
local function filemenu(frame, pathnode)
|
||||
local menu = DermaMenu()
|
||||
|
||||
menu:AddOption("attach model", function()
|
||||
attach(pathnode)
|
||||
end):SetIcon("icon16/brick_add.png")
|
||||
|
||||
menu:AddOption("cancel"):SetIcon("icon16/cancel.png")
|
||||
menu:Open()
|
||||
end
|
||||
|
||||
local function conmenu(frame, conroot)
|
||||
end
|
||||
|
||||
|
||||
|
||||
--[[
|
||||
|
||||
derma
|
||||
|
||||
]]
|
||||
local PANEL = {}
|
||||
|
||||
function PANEL:CreateGhost()
|
||||
self.Ghost = ents.CreateClientside("base_anim")
|
||||
self.Ghost:SetMaterial("models/wireframe")
|
||||
self.Ghost:SetNoDraw(true)
|
||||
|
||||
self.Ghost.Draw = function(ent)
|
||||
cam.IgnoreZ(true)
|
||||
ent:DrawModel()
|
||||
cam.IgnoreZ(false)
|
||||
end
|
||||
|
||||
self.Ghost:Spawn()
|
||||
end
|
||||
|
||||
function PANEL:Init()
|
||||
self:CreateGhost()
|
||||
|
||||
self.updates = {}
|
||||
|
||||
self.contree = vgui.Create("DTree", self)
|
||||
self.contree:Dock(FILL)
|
||||
self.contree.AddNode = TreeAddNode
|
||||
self.contree:SetClickOnDragHover(true)
|
||||
self.contree.DoRightClick = function(pnl, node)
|
||||
if node.menu then node.menu(self, node) end
|
||||
end
|
||||
self.contree.Paint = function(pnl, w, h)
|
||||
surface.SetDrawColor(theme.colorTree)
|
||||
surface.DrawRect(0, 0, w, h)
|
||||
surface.SetDrawColor(0, 0, 0)
|
||||
surface.DrawOutlinedRect(0, 0, w, h)
|
||||
end
|
||||
self.contree:DockMargin(1, 1, 1, 1)
|
||||
|
||||
self.confirm = vgui.Create("DButton", self)
|
||||
self.confirm:Dock(BOTTOM)
|
||||
self.confirm:DockMargin(0, 2, 0, 0)
|
||||
self.confirm:SetText("Confirm changes")
|
||||
self.confirm.DoClick = function()
|
||||
if not IsValid(self.Entity) then
|
||||
return false
|
||||
end
|
||||
|
||||
local set = {}
|
||||
local add = {}
|
||||
local mod = {}
|
||||
|
||||
for k, v in pairs(self.updates) do
|
||||
if next(v.set) then
|
||||
if not set[k] then
|
||||
set[k] = {}
|
||||
end
|
||||
for i, j in pairs(v.set) do
|
||||
set[k][i] = j
|
||||
end
|
||||
if not next(set[k]) then
|
||||
set[k] = nil
|
||||
end
|
||||
end
|
||||
if next(v.add) then
|
||||
if not add[k] then
|
||||
add[k] = {}
|
||||
end
|
||||
for i in pairs(v.add) do
|
||||
if IsValid(i) then
|
||||
add[k][#add[k] + 1] = i.new
|
||||
end
|
||||
end
|
||||
if not next(add[k]) then
|
||||
add[k] = nil
|
||||
end
|
||||
end
|
||||
if next(v.mod) then
|
||||
if not mod[k] then
|
||||
mod[k] = {}
|
||||
end
|
||||
for i, j in pairs(v.mod) do
|
||||
mod[k][i] = j.kill and { kill = true } or j
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if next(set) or next(add) or next(mod) then
|
||||
net.Start("prop2mesh_upload_start")
|
||||
net.WriteUInt(self.Entity:EntIndex(), 16)
|
||||
for k, v in ipairs({set, add, mod}) do
|
||||
if next(v) then
|
||||
net.WriteBool(true)
|
||||
net.WriteTable(v)
|
||||
else
|
||||
net.WriteBool(false)
|
||||
end
|
||||
end
|
||||
net.SendToServer()
|
||||
end
|
||||
end
|
||||
self.confirm:DockMargin(1, 1, 1, 1)
|
||||
|
||||
self.progress = vgui.Create("DPanel", self)
|
||||
self.progress:Dock(BOTTOM)
|
||||
self.progress:DockMargin(1, 1, 1, 1)
|
||||
self.progress:SetTall(16)
|
||||
self.progress.Paint = function(pnl, w, h)
|
||||
surface.SetDrawColor(theme.colorTree)
|
||||
surface.DrawRect(0, 0, w, h)
|
||||
|
||||
if pnl.frac then
|
||||
surface.SetDrawColor(0, 255, 0)
|
||||
surface.DrawRect(0, 0, pnl.frac*w, h)
|
||||
|
||||
if pnl.text then
|
||||
draw.SimpleText(pnl.text, theme.font, w*0.5, h*0.5, theme.colorText_default, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
end
|
||||
|
||||
surface.SetDrawColor(0, 0, 0)
|
||||
surface.DrawOutlinedRect(0, 0, w, h)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:Paint(w, h)
|
||||
surface.SetDrawColor(theme.colorMain)
|
||||
surface.DrawRect(0, 0, w, h)
|
||||
surface.SetDrawColor(0, 0, 0)
|
||||
surface.DrawOutlinedRect(0, 0, w, h)
|
||||
end
|
||||
|
||||
function PANEL:OnRemove()
|
||||
if IsValid(self.Entity) then
|
||||
self.Entity:RemoveCallOnRemove("prop2mesh_editor_close")
|
||||
self.Entity.prop2mesh_triggereditor = nil
|
||||
end
|
||||
if IsValid(self.Ghost) then
|
||||
self.Ghost:Remove()
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:Think()
|
||||
if not IsValid(self.Entity) then
|
||||
return
|
||||
end
|
||||
|
||||
if self.Entity.prop2mesh_triggereditor then
|
||||
self.Entity.prop2mesh_triggereditor = nil
|
||||
self:RemakeTree()
|
||||
return
|
||||
end
|
||||
|
||||
if self.Entity:GetNetworkedBool("uploading", false) then
|
||||
if not self.contree:GetDisabled() or not self.disable then
|
||||
self.contree:SetDisabled(true)
|
||||
self.confirm:SetDisabled(true)
|
||||
self.disable = true
|
||||
end
|
||||
self.progress.frac = upstreamProgress()
|
||||
self.progress.text = "uploading..."
|
||||
else
|
||||
if self.contree:GetDisabled() or self.disable then
|
||||
if self.Entity:GetAllDataReady() then
|
||||
self.contree:SetDisabled(false)
|
||||
self.confirm:SetDisabled(false)
|
||||
self.disable = nil
|
||||
self:RemakeTree()
|
||||
self.progress.frac = nil
|
||||
else
|
||||
local frac = self.Entity:GetDownloadProgress()
|
||||
|
||||
self.progress.frac = frac or 1
|
||||
self.progress.text = frac and "downloading..." or "building mesh..."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local matrix = Matrix()
|
||||
local function onPartHover(label)
|
||||
local self = prop2mesh.editor
|
||||
if not self then
|
||||
return
|
||||
end
|
||||
|
||||
local partnode = label:GetParent()
|
||||
if self.contree:GetSelectedItem() ~= partnode then
|
||||
self.contree:SetSelectedItem(partnode)
|
||||
|
||||
if partnode.new and (partnode.new.holo or partnode.new.prop) then
|
||||
self.Ghost:SetNoDraw(false)
|
||||
self.Ghost:SetModel(partnode.new.holo or partnode.new.prop)
|
||||
|
||||
local pos, ang = LocalToWorld(partnode.new.pos, partnode.new.ang, self.Entity:GetPos(), self.Entity:GetAngles())
|
||||
|
||||
self.Ghost:SetParent(self.Entity)
|
||||
self.Ghost:SetPos(pos)
|
||||
self.Ghost:SetAngles(ang)
|
||||
|
||||
if partnode.new.scale then
|
||||
matrix:SetScale(partnode.new.scale)
|
||||
self.Ghost:EnableMatrix("RenderMultiply", matrix)
|
||||
else
|
||||
self.GHost:DisableMatrix("RenderMultiply")
|
||||
end
|
||||
else
|
||||
self.Ghost:SetNoDraw(true)
|
||||
end
|
||||
|
||||
label:InvalidateLayout(true)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:RemakeTree()
|
||||
self.contree:Clear()
|
||||
self.updates = {}
|
||||
|
||||
local files, filenodes = {}, {}
|
||||
for k, v in ipairs(file.Find("p2m/*.txt", "DATA")) do table.insert(files, v) end
|
||||
for k, v in ipairs(file.Find("p2m/*.obj", "DATA")) do table.insert(files, v) end
|
||||
|
||||
for i = 1, #self.Entity.prop2mesh_controllers do
|
||||
self.updates[i] = { mod = {}, add = {}, set = {} }
|
||||
|
||||
local condata = prop2mesh.getMeshData(self.Entity.prop2mesh_controllers[i].crc, true) or {}
|
||||
local conroot = self.contree:AddNode(string.format("controller %d [%d]", i, #condata), "icon16/image.png")
|
||||
|
||||
conroot.num = i
|
||||
conroot.menu = conmenu
|
||||
|
||||
local setroot = conroot:AddNode("settings", "icon16/cog.png")
|
||||
|
||||
setroot.set = self.updates[i].set
|
||||
setroot.old = { uvs = self.Entity.prop2mesh_controllers[i].uvs, scale = Vector(self.Entity.prop2mesh_controllers[i].scale) }
|
||||
setroot.new = { uvs = self.Entity.prop2mesh_controllers[i].uvs, scale = Vector(self.Entity.prop2mesh_controllers[i].scale) }
|
||||
|
||||
registerVector(setroot, "mesh scale", "scale")
|
||||
registerFloat(setroot, "texture size", "uvs", 0, 512)
|
||||
|
||||
setroot:ExpandRecurse(true)
|
||||
|
||||
local objroot = conroot:AddNode(".obj", "icon16/pictures.png")
|
||||
local objfile = objroot:AddNode("files", "icon16/bullet_disk.png")
|
||||
local objlist = objroot:AddNode("attachments", "icon16/bullet_picture.png")
|
||||
local mdllist = conroot:AddNode(".mdl", "icon16/images.png")
|
||||
|
||||
filenodes[#filenodes + 1] = objfile
|
||||
objfile.list = objlist
|
||||
objfile.add = self.updates[i].add
|
||||
|
||||
for k, v in ipairs(condata) do
|
||||
local root = v.objd and objlist or mdllist
|
||||
local part = root:AddNode(string.format("[%d] %s", k, string.GetFileFromFilename(v.objn or v.objd or v.prop or v.holo)))
|
||||
part:SetIcon("icon16/brick.png")
|
||||
|
||||
part.Label.OnCursorEntered = onPartHover
|
||||
part.menu = partmenu
|
||||
part.new, part.old = partcopy(v)
|
||||
part.mod = self.updates[i].mod
|
||||
part.num = k
|
||||
end
|
||||
end
|
||||
|
||||
for k, v in SortedPairs(files) do
|
||||
local path = string.format("p2m/%s", v)
|
||||
for _, filenode in pairs(filenodes) do
|
||||
local pathnode = filenode:AddNode(path, "icon16/page_white_text.png")
|
||||
pathnode.menu = filemenu
|
||||
pathnode.path = path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
vgui.Register("prop2mesh_editor", PANEL, "DFrame")
|
||||
|
676
lua/prop2mesh/cl_meshlab.lua
Normal file
676
lua/prop2mesh/cl_meshlab.lua
Normal file
@ -0,0 +1,676 @@
|
||||
--[[
|
||||
|
||||
]]
|
||||
local prop2mesh = prop2mesh
|
||||
|
||||
local util = util
|
||||
local string = string
|
||||
local coroutine = coroutine
|
||||
local notification = notification
|
||||
|
||||
local next = next
|
||||
local SysTime = SysTime
|
||||
local tonumber = tonumber
|
||||
|
||||
local Vector = Vector
|
||||
local vec = Vector()
|
||||
local div = vec.Div
|
||||
local mul = vec.Mul
|
||||
local add = vec.Add
|
||||
local dot = vec.Dot
|
||||
local cross = vec.Cross
|
||||
local normalize = vec.Normalize
|
||||
local rotate = vec.Rotate
|
||||
|
||||
local math_cos = math.cos
|
||||
local math_rad = math.rad
|
||||
local math_abs = math.abs
|
||||
local math_min = math.min
|
||||
local math_max = math.max
|
||||
|
||||
local string_format = string.format
|
||||
local string_explode = string.Explode
|
||||
local string_gsub = string.gsub
|
||||
local string_trim = string.Trim
|
||||
local table_concat = table.concat
|
||||
local table_remove = table.remove
|
||||
|
||||
local a90 = Angle(0, -90, 0)
|
||||
|
||||
|
||||
--[[
|
||||
|
||||
]]
|
||||
local function calcbounds(min, max, pos)
|
||||
if pos.x < min.x then min.x = pos.x elseif pos.x > max.x then max.x = pos.x end
|
||||
if pos.y < min.y then min.y = pos.y elseif pos.y > max.y then max.y = pos.y end
|
||||
if pos.z < min.z then min.z = pos.z elseif pos.z > max.z then max.z = pos.z end
|
||||
end
|
||||
|
||||
local function copy(v)
|
||||
return {
|
||||
pos = Vector(v.pos),
|
||||
normal = Vector(v.normal),
|
||||
u = v.u,
|
||||
v = v.v,
|
||||
rotate = v.rotate,
|
||||
}
|
||||
end
|
||||
|
||||
local function sign(n)
|
||||
if n == 0 then
|
||||
return 0
|
||||
else
|
||||
return n > 0 and 1 or -1
|
||||
end
|
||||
end
|
||||
|
||||
local function getBoxDir(vec)
|
||||
local x, y, z = math_abs(vec.x), math_abs(vec.y), math_abs(vec.z)
|
||||
if x > y and x > z then
|
||||
return vec.x < -0 and -1 or 1
|
||||
elseif y > z then
|
||||
return vec.y < 0 and -2 or 2
|
||||
end
|
||||
return vec.z < 0 and -3 or 3
|
||||
end
|
||||
|
||||
local function getBoxUV(vert, dir, scale)
|
||||
if dir == -1 or dir == 1 then
|
||||
return vert.z * sign(dir) * scale, vert.y * scale
|
||||
elseif dir == -2 or dir == 2 then
|
||||
return vert.x * scale, vert.z * sign(dir) * scale
|
||||
else
|
||||
return vert.x * -sign(dir) * scale, vert.y * scale
|
||||
end
|
||||
end
|
||||
|
||||
local function clip(v1, v2, plane, length, getUV)
|
||||
local d1 = dot(v1.pos, plane) - length
|
||||
local d2 = dot(v2.pos, plane) - length
|
||||
local t = d1 / (d1 - d2)
|
||||
local vert = {
|
||||
pos = v1.pos + t * (v2.pos - v1.pos),
|
||||
normal = v1.normal + t * (v2.normal - v1.normal),
|
||||
rotate = v1.rotate or v2.rotate,
|
||||
}
|
||||
if getUV then
|
||||
vert.u = v1.u + t * (v2.u - v1.u)
|
||||
vert.v = v1.v + t * (v2.v - v1.v)
|
||||
end
|
||||
return vert
|
||||
end
|
||||
|
||||
-- method https:--github.com/chenchenyuyu/DEMO/blob/b6bf971a302c71403e0e34e091402982dfa3cd2d/app/src/pages/vr/decal/decalGeometry.js#L102
|
||||
local function applyClippingPlane(verts, plane, length, getUV)
|
||||
local temp = {}
|
||||
for i = 1, #verts, 3 do
|
||||
local d1 = length - dot(verts[i + 0].pos, plane)
|
||||
local d2 = length - dot(verts[i + 1].pos, plane)
|
||||
local d3 = length - dot(verts[i + 2].pos, plane)
|
||||
|
||||
local ov1 = d1 > 0
|
||||
local ov2 = d2 > 0
|
||||
local ov3 = d3 > 0
|
||||
|
||||
local total = (ov1 and 1 or 0) + (ov2 and 1 or 0) + (ov3 and 1 or 0)
|
||||
|
||||
local nv1, nv2, nv3, nv4
|
||||
|
||||
if total == 0 then
|
||||
temp[#temp + 1] = verts[i + 0]
|
||||
temp[#temp + 1] = verts[i + 1]
|
||||
temp[#temp + 1] = verts[i + 2]
|
||||
elseif total == 1 then
|
||||
if ov1 then
|
||||
nv1 = verts[i + 1]
|
||||
nv2 = verts[i + 2]
|
||||
nv3 = clip(verts[i + 0], nv1, plane, length, getUV)
|
||||
nv4 = clip(verts[i + 0], nv2, plane, length, getUV)
|
||||
|
||||
temp[#temp + 1] = copy(nv1)
|
||||
temp[#temp + 1] = copy(nv2)
|
||||
temp[#temp + 1] = nv3
|
||||
temp[#temp + 1] = nv4
|
||||
temp[#temp + 1] = copy(nv3)
|
||||
temp[#temp + 1] = copy(nv2)
|
||||
elseif ov2 then
|
||||
nv1 = verts[i + 0]
|
||||
nv2 = verts[i + 2]
|
||||
nv3 = clip(verts[i + 1], nv1, plane, length, getUV)
|
||||
nv4 = clip(verts[i + 1], nv2, plane, length, getUV)
|
||||
|
||||
temp[#temp + 1] = nv3
|
||||
temp[#temp + 1] = copy(nv2)
|
||||
temp[#temp + 1] = copy(nv1)
|
||||
temp[#temp + 1] = copy(nv2)
|
||||
temp[#temp + 1] = copy(nv3)
|
||||
temp[#temp + 1] = nv4
|
||||
elseif ov3 then
|
||||
nv1 = verts[i + 0]
|
||||
nv2 = verts[i + 1]
|
||||
nv3 = clip(verts[i + 2], nv1, plane, length, getUV)
|
||||
nv4 = clip(verts[i + 2], nv2, plane, length, getUV)
|
||||
|
||||
temp[#temp + 1] = copy(nv1)
|
||||
temp[#temp + 1] = copy(nv2)
|
||||
temp[#temp + 1] = nv3
|
||||
temp[#temp + 1] = nv4
|
||||
temp[#temp + 1] = copy(nv3)
|
||||
temp[#temp + 1] = copy(nv2)
|
||||
end
|
||||
elseif total == 2 then
|
||||
if not ov1 then
|
||||
nv1 = copy(verts[i + 0])
|
||||
nv2 = clip(nv1, verts[i + 1], plane, length, getUV)
|
||||
nv3 = clip(nv1, verts[i + 2], plane, length, getUV)
|
||||
|
||||
temp[#temp + 1] = nv1
|
||||
temp[#temp + 1] = nv2
|
||||
temp[#temp + 1] = nv3
|
||||
elseif not ov2 then
|
||||
nv1 = copy(verts[i + 1])
|
||||
nv2 = clip(nv1, verts[i + 2], plane, length, getUV)
|
||||
nv3 = clip(nv1, verts[i + 0], plane, length, getUV)
|
||||
|
||||
temp[#temp + 1] = nv1
|
||||
temp[#temp + 1] = nv2
|
||||
temp[#temp + 1] = nv3
|
||||
elseif not ov3 then
|
||||
nv1 = copy(verts[i + 2])
|
||||
nv2 = clip(nv1, verts[i + 0], plane, length, getUV)
|
||||
nv3 = clip(nv1, verts[i + 1], plane, length, getUV)
|
||||
|
||||
temp[#temp + 1] = nv1
|
||||
temp[#temp + 1] = nv2
|
||||
temp[#temp + 1] = nv3
|
||||
end
|
||||
end
|
||||
end
|
||||
return temp
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
|
||||
]]
|
||||
local meshmodelcache
|
||||
local function getVertsFromMDL(partnext, meshtex, vmins, vmaxs)
|
||||
local modelpath = partnext.prop or partnext.holo
|
||||
if prop2mesh.isBlockedModel(modelpath) then
|
||||
return
|
||||
end
|
||||
|
||||
local submeshes
|
||||
if meshmodelcache[modelpath] then
|
||||
submeshes = meshmodelcache[modelpath][partnext.bodygroup or 0]
|
||||
else
|
||||
meshmodelcache[modelpath] = {}
|
||||
end
|
||||
if not submeshes then
|
||||
submeshes = util.GetModelMeshes(modelpath, 0, partnext.bodygroup or 0)
|
||||
if not submeshes then
|
||||
return
|
||||
end
|
||||
submeshes.modelfixer = prop2mesh.getModelFix(modelpath)
|
||||
submeshes.modelfixergeneric = isbool(submeshes.modelfixer)
|
||||
meshmodelcache[modelpath][partnext.bodygroup or 0] = submeshes
|
||||
end
|
||||
|
||||
local partpos = partnext.pos
|
||||
local partang = partnext.ang
|
||||
local partscale = partnext.scale
|
||||
local partclips = partnext.clips
|
||||
|
||||
local submeshfixlookup
|
||||
if submeshes.modelfixer then
|
||||
local rotated = Angle(partang)
|
||||
rotated:RotateAroundAxis(rotated:Up(), 90)
|
||||
|
||||
submeshfixlookup = {}
|
||||
for submeshid = 1, #submeshes do
|
||||
if submeshes.modelfixergeneric then
|
||||
submeshfixlookup[submeshid] = { ang = rotated }
|
||||
else
|
||||
local ang = submeshes.modelfixer(submeshid, #submeshes, rotated, partang) or rotated
|
||||
submeshfixlookup[submeshid] = { ang = ang, diff = ang ~= rotated }
|
||||
end
|
||||
end
|
||||
|
||||
if partscale then
|
||||
if partnext.holo then
|
||||
partscale = Vector(partscale.y, partscale.x, partscale.z)
|
||||
else
|
||||
partscale = Vector(partscale.x, partscale.z, partscale.y)
|
||||
end
|
||||
end
|
||||
|
||||
if partclips then
|
||||
local clips = {}
|
||||
for clipid = 1, #partclips do
|
||||
local normal = Vector(partclips[clipid].n)
|
||||
rotate(normal, a90)
|
||||
clips[#clips + 1] = {
|
||||
d = partclips[clipid].d,
|
||||
no = partclips[clipid].n,
|
||||
n = normal,
|
||||
}
|
||||
end
|
||||
partclips = clips
|
||||
end
|
||||
end
|
||||
|
||||
local partverts = {}
|
||||
local modeluv = not meshtex
|
||||
|
||||
for submeshid = 1, #submeshes do
|
||||
local submeshdata = submeshes[submeshid].triangles
|
||||
local submeshfix = submeshfixlookup and submeshfixlookup[submeshid]
|
||||
local submeshverts = {}
|
||||
|
||||
for vertid = 1, #submeshdata do
|
||||
local vert = submeshdata[vertid]
|
||||
local pos = Vector(vert.pos)
|
||||
local normal = Vector(vert.normal)
|
||||
|
||||
if partscale then
|
||||
if submeshfix and submeshfix.diff then
|
||||
pos.x = pos.x * partnext.scale.x
|
||||
pos.y = pos.y * partnext.scale.y
|
||||
pos.z = pos.z * partnext.scale.z
|
||||
else
|
||||
pos.x = pos.x * partscale.x
|
||||
pos.y = pos.y * partscale.y
|
||||
pos.z = pos.z * partscale.z
|
||||
end
|
||||
end
|
||||
|
||||
local vcopy = {
|
||||
pos = pos,
|
||||
normal = normal,
|
||||
rotate = submeshfix,
|
||||
}
|
||||
|
||||
if modeluv then
|
||||
vcopy.u = vert.u
|
||||
vcopy.v = vert.v
|
||||
end
|
||||
|
||||
submeshverts[#submeshverts + 1] = vcopy
|
||||
end
|
||||
|
||||
if partclips then
|
||||
if submeshfix then
|
||||
for clipid = 1, #partclips do
|
||||
submeshverts = applyClippingPlane(submeshverts, submeshfix.diff and partclips[clipid].no or partclips[clipid].n, partclips[clipid].d, modeluv)
|
||||
end
|
||||
else
|
||||
for clipid = 1, #partclips do
|
||||
submeshverts = applyClippingPlane(submeshverts, partclips[clipid].n, partclips[clipid].d, modeluv)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for vertid = 1, #submeshverts do
|
||||
local vert = submeshverts[vertid]
|
||||
if vert.rotate then
|
||||
rotate(vert.normal, vert.rotate.ang or partang)
|
||||
rotate(vert.pos, vert.rotate.ang or partang)
|
||||
vert.rotate = nil
|
||||
else
|
||||
rotate(vert.normal, partang)
|
||||
rotate(vert.pos, partang)
|
||||
end
|
||||
add(vert.pos, partpos)
|
||||
partverts[#partverts + 1] = vert
|
||||
calcbounds(vmins, vmaxs, vert.pos)
|
||||
end
|
||||
end
|
||||
|
||||
if #partverts == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local nflat = partnext.vsmooth == 1
|
||||
if meshtex or nflat then
|
||||
for pv = 1, #partverts, 3 do
|
||||
local normal = cross(partverts[pv + 2].pos - partverts[pv].pos, partverts[pv + 1].pos - partverts[pv].pos)
|
||||
normalize(normal)
|
||||
|
||||
if nflat then
|
||||
partverts[pv ].normal = Vector(normal)
|
||||
partverts[pv + 1].normal = Vector(normal)
|
||||
partverts[pv + 2].normal = Vector(normal)
|
||||
end
|
||||
|
||||
if meshtex then
|
||||
local boxDir = getBoxDir(normal)
|
||||
partverts[pv ].u, partverts[pv ].v = getBoxUV(partverts[pv ].pos, boxDir, meshtex)
|
||||
partverts[pv + 1].u, partverts[pv + 1].v = getBoxUV(partverts[pv + 1].pos, boxDir, meshtex)
|
||||
partverts[pv + 2].u, partverts[pv + 2].v = getBoxUV(partverts[pv + 2].pos, boxDir, meshtex)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return partverts
|
||||
end
|
||||
|
||||
local function getVertsFromOBJ(custom, partnext, meshtex, vmins, vmaxs)
|
||||
local modeluid = tonumber(partnext.objd)
|
||||
local modelobj = custom[modeluid]
|
||||
|
||||
if not modelobj then
|
||||
return
|
||||
end
|
||||
|
||||
local pos = partnext.pos
|
||||
local ang = partnext.ang
|
||||
local scale = partnext.scale
|
||||
|
||||
if pos.x == 0 and pos.y == 0 and pos.z == 0 then pos = nil end
|
||||
if ang.p == 0 and ang.y == 0 and ang.r == 0 then ang = nil end
|
||||
if scale then
|
||||
if scale.x == 1 and scale.y == 1 and scale.z == 1 then scale = nil end
|
||||
end
|
||||
|
||||
local vlook = {}
|
||||
local vmesh = {}
|
||||
|
||||
local meshtex = meshtex or 1 / 48
|
||||
local invert = partnext.vinvert
|
||||
local smooth = partnext.vsmooth
|
||||
|
||||
for line in string.gmatch(modelobj, "(.-)\n") do
|
||||
local temp = string_explode(" ", string_gsub(string_trim(line), "%s+", " "))
|
||||
local head = table_remove(temp, 1)
|
||||
|
||||
if head == "f" then
|
||||
local f1 = string_explode("/", temp[1])
|
||||
local f2 = string_explode("/", temp[2])
|
||||
|
||||
for i = 3, #temp do
|
||||
local f3 = string_explode("/", temp[i])
|
||||
|
||||
local v1, v2, v3
|
||||
|
||||
if invert then
|
||||
v1 = { pos = Vector(vlook[tonumber(f3[1])]) }
|
||||
v2 = { pos = Vector(vlook[tonumber(f2[1])]) }
|
||||
v3 = { pos = Vector(vlook[tonumber(f1[1])]) }
|
||||
else
|
||||
v1 = { pos = Vector(vlook[tonumber(f1[1])]) }
|
||||
v2 = { pos = Vector(vlook[tonumber(f2[1])]) }
|
||||
v3 = { pos = Vector(vlook[tonumber(f3[1])]) }
|
||||
end
|
||||
|
||||
local normal = cross(v3.pos - v1.pos, v2.pos - v1.pos)
|
||||
normalize(normal)
|
||||
|
||||
v1.normal = Vector(normal)
|
||||
v2.normal = Vector(normal)
|
||||
v3.normal = Vector(normal)
|
||||
|
||||
local boxDir = getBoxDir(normal)
|
||||
v1.u, v1.v = getBoxUV(v1.pos, boxDir, meshtex)
|
||||
v2.u, v2.v = getBoxUV(v2.pos, boxDir, meshtex)
|
||||
v3.u, v3.v = getBoxUV(v3.pos, boxDir, meshtex)
|
||||
|
||||
vmesh[#vmesh + 1] = v1
|
||||
vmesh[#vmesh + 1] = v2
|
||||
vmesh[#vmesh + 1] = v3
|
||||
|
||||
f2 = f3
|
||||
end
|
||||
end
|
||||
if head == "v" then
|
||||
local vert = Vector(tonumber(temp[1]), tonumber(temp[2]), tonumber(temp[3]))
|
||||
if scale then
|
||||
vert.x = vert.x * scale.x
|
||||
vert.y = vert.y * scale.y
|
||||
vert.z = vert.z * scale.z
|
||||
end
|
||||
if ang then
|
||||
rotate(vert, ang)
|
||||
end
|
||||
if pos then
|
||||
add(vert, pos)
|
||||
end
|
||||
vlook[#vlook + 1] = vert
|
||||
calcbounds(vmins, vmaxs, vert)
|
||||
end
|
||||
|
||||
coroutine.yield(false)
|
||||
end
|
||||
|
||||
-- https:--github.com/thegrb93/StarfallEx/blob/b6de9fbe84040e9ebebcbe858c30adb9f7d937b5/lua/starfall/libs_sh/mesh.lua#L229
|
||||
-- credit to Sevii
|
||||
if smooth and smooth ~= 0 then
|
||||
local smoothrad = math_cos(math_rad(smooth))
|
||||
if smoothrad ~= 1 then
|
||||
local norms = setmetatable({},{__index = function(t,k) local r=setmetatable({},{__index=function(t,k) local r=setmetatable({},{__index=function(t,k) local r={} t[k]=r return r end}) t[k]=r return r end}) t[k]=r return r end})
|
||||
for _, vertex in ipairs(vmesh) do
|
||||
local pos = vertex.pos
|
||||
local norm = norms[pos[1]][pos[2]][pos[3]]
|
||||
norm[#norm+1] = vertex.normal
|
||||
end
|
||||
|
||||
for _, vertex in ipairs(vmesh) do
|
||||
local normal = Vector()
|
||||
local count = 0
|
||||
local pos = vertex.pos
|
||||
|
||||
for _, norm in ipairs(norms[pos[1]][pos[2]][pos[3]]) do
|
||||
if dot(vertex.normal, norm) >= smoothrad then
|
||||
add(normal, norm)
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
|
||||
if count > 1 then
|
||||
div(normal, count)
|
||||
vertex.normal = normal
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return #vmesh > 0 and vmesh
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
|
||||
]]
|
||||
local function getMeshFromData(data, uvs)
|
||||
if not data or not uvs then
|
||||
coroutine.yield(true)
|
||||
end
|
||||
local partlist = util.JSONToTable(util.Decompress(data))
|
||||
if not partlist then
|
||||
coroutine.yield(true)
|
||||
end
|
||||
|
||||
prop2mesh.loadModelFixer()
|
||||
if not meshmodelcache then
|
||||
meshmodelcache = {}
|
||||
end
|
||||
|
||||
local meshpcount = 0
|
||||
local meshvcount = 0
|
||||
local vmins = Vector()
|
||||
local vmaxs = Vector()
|
||||
local meshtex = (uvs and uvs ~= 0) and (1 / uvs) or nil
|
||||
|
||||
local meshlist = { {} }
|
||||
local meshnext = meshlist[1]
|
||||
|
||||
local partcount = #partlist
|
||||
|
||||
for partid = 1, partcount do
|
||||
local partnext = partlist[partid]
|
||||
local partverts
|
||||
|
||||
if partnext.prop or partnext.holo then
|
||||
partverts = getVertsFromMDL(partnext, meshtex, vmins, vmaxs)
|
||||
elseif partnext.objd and partlist.custom then
|
||||
local valid, opv = pcall(getVertsFromOBJ, partlist.custom, partnext, meshtex, vmins, vmaxs)
|
||||
if valid and opv then
|
||||
partverts = opv
|
||||
end
|
||||
end
|
||||
|
||||
if partverts then
|
||||
meshpcount = meshpcount + 1
|
||||
|
||||
--[[
|
||||
local nflat = partnext.vsmooth == 0
|
||||
if not partnext.objd and (meshtex or nflat) then
|
||||
for pv = 1, #partverts, 3 do
|
||||
local normal = cross(partverts[pv + 2].pos - partverts[pv].pos, partverts[pv + 1].pos - partverts[pv].pos)
|
||||
normalize(normal)
|
||||
|
||||
if nflat then
|
||||
partverts[pv ].normal = Vector(normal)
|
||||
partverts[pv + 1].normal = Vector(normal)
|
||||
partverts[pv + 2].normal = Vector(normal)
|
||||
end
|
||||
|
||||
if meshtex then
|
||||
local boxDir = getBoxDir(normal)
|
||||
partverts[pv ].u, partverts[pv ].v = getBoxUV(partverts[pv ].pos, boxDir, meshtex)
|
||||
partverts[pv + 1].u, partverts[pv + 1].v = getBoxUV(partverts[pv + 1].pos, boxDir, meshtex)
|
||||
partverts[pv + 2].u, partverts[pv + 2].v = getBoxUV(partverts[pv + 2].pos, boxDir, meshtex)
|
||||
end
|
||||
end
|
||||
end
|
||||
]]
|
||||
|
||||
if partnext.vinside then
|
||||
for pv = #partverts, 1, -1 do
|
||||
local vdupe = copy(partverts[pv])
|
||||
vdupe.normal = -vdupe.normal
|
||||
partverts[#partverts + 1] = vdupe
|
||||
end
|
||||
end
|
||||
|
||||
local partvcount = #partverts
|
||||
if #meshnext + partvcount > 63999 then
|
||||
meshlist[#meshlist + 1] = {}
|
||||
meshnext = meshlist[#meshlist]
|
||||
end
|
||||
for pv = 1, partvcount do
|
||||
meshnext[#meshnext + 1] = partverts[pv]
|
||||
end
|
||||
meshvcount = meshvcount + partvcount
|
||||
end
|
||||
|
||||
coroutine.yield(false)
|
||||
end
|
||||
|
||||
coroutine.yield(true, { meshes = meshlist, vcount = meshvcount, vmins = vmins, vmaxs = vmaxs, pcount = meshpcount })
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
|
||||
]]
|
||||
local meshlabs = {}
|
||||
function prop2mesh.getMesh(crc, uvs, data)
|
||||
if not crc or not uvs or not data then
|
||||
return false
|
||||
end
|
||||
|
||||
local key = string.format("%s_%s", crc, uvs)
|
||||
if not meshlabs[key] then
|
||||
meshlabs[key] = { crc = crc, uvs = uvs, data = data, coro = coroutine.create(getMeshFromData) }
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local message
|
||||
local function setmessage(text)
|
||||
if not IsValid(message) then
|
||||
local parent
|
||||
if GetOverlayPanel then parent = GetOverlayPanel() end
|
||||
|
||||
message = vgui.Create("DPanel", parent)
|
||||
|
||||
local green = Color(255, 255, 0)
|
||||
local black = Color(0,0,0)
|
||||
local font = "prop2mesheditor"
|
||||
|
||||
message.Paint = function(self, w, h)
|
||||
draw.SimpleTextOutlined(self.text, font, 0, 0, green, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP, 1, black)
|
||||
end
|
||||
|
||||
message.SetText = function(self, txt)
|
||||
if self.text ~= txt then
|
||||
self.text = txt
|
||||
|
||||
surface.SetFont(font)
|
||||
local w, h = surface.GetTextSize(self.text)
|
||||
|
||||
self:SetPos(ScrW() - w - 1, ScrH() - h - 1)
|
||||
self:SetSize(w, h)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
message:SetText(text)
|
||||
message.time = SysTime()
|
||||
end
|
||||
|
||||
local function pluralf(pattern, number)
|
||||
return string.format(pattern, number, number == 1 and "" or "s")
|
||||
end
|
||||
|
||||
hook.Add("Think", "prop2mesh_meshlab", function()
|
||||
if prop2mesh.downloads > 0 then
|
||||
setmessage(pluralf("prop2mesh %d server download%s remaining", prop2mesh.downloads))
|
||||
end
|
||||
if message and SysTime() - message.time > 0.25 then
|
||||
if IsValid(message) then
|
||||
message:Remove()
|
||||
end
|
||||
message = nil
|
||||
end
|
||||
|
||||
local key, lab = next(meshlabs)
|
||||
if not key or not lab then
|
||||
return
|
||||
end
|
||||
|
||||
local curtime = SysTime()
|
||||
while SysTime() - curtime < 0.05 do
|
||||
local ok, err, mdata = coroutine.resume(lab.coro, lab.data, lab.uvs)
|
||||
|
||||
if not ok then
|
||||
print(err)
|
||||
meshlabs[key] = nil
|
||||
break
|
||||
end
|
||||
|
||||
if err then
|
||||
hook.Run("prop2mesh_hook_meshdone", lab.crc, lab.uvs, mdata)
|
||||
meshlabs[key] = nil
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if next(meshlabs) == nil then
|
||||
prop2mesh.unloadModelFixer()
|
||||
meshmodelcache = nil
|
||||
else
|
||||
if not message then
|
||||
setmessage(pluralf("prop2mesh %d mesh build%s remaining", table.Count(meshlabs)))
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
|
||||
|
||||
print( pluralf("prop2mesh %d server download%s remaining", 1, "s") )
|
||||
print( pluralf("prop2mesh %d server download%s remaining", 0, "s") )
|
446
lua/prop2mesh/cl_modelfixer.lua
Normal file
446
lua/prop2mesh/cl_modelfixer.lua
Normal file
@ -0,0 +1,446 @@
|
||||
--[[
|
||||
|
||||
]]
|
||||
local prop2mesh = prop2mesh
|
||||
local string = string
|
||||
|
||||
local genericmodel, genericfolder, genericmodellist, genericfolderlist, blockedmodel
|
||||
local specialmodel, specialfolder = {}, {}
|
||||
|
||||
|
||||
--[[
|
||||
|
||||
]]
|
||||
function prop2mesh.isBlockedModel(modelpath)
|
||||
return blockedmodel[modelpath]
|
||||
end
|
||||
|
||||
function prop2mesh.loadModelFixer()
|
||||
if not genericmodel then
|
||||
genericmodel = {}
|
||||
for s in string.gmatch(genericmodellist, "[^\r\n]+") do
|
||||
genericmodel[s] = true
|
||||
end
|
||||
end
|
||||
if not genericfolder then
|
||||
genericfolder = {}
|
||||
for s in string.gmatch(genericfolderlist, "[^\r\n]+") do
|
||||
genericfolder[s] = true
|
||||
end
|
||||
end
|
||||
--print("Loading model fixers")
|
||||
end
|
||||
|
||||
function prop2mesh.unloadModelFixer()
|
||||
if genericmodel then
|
||||
genericmodel = nil
|
||||
end
|
||||
if genericfolder then
|
||||
genericfolder = nil
|
||||
end
|
||||
--print("Unloading model fixers")
|
||||
end
|
||||
|
||||
function prop2mesh.getModelFix(modelpath)
|
||||
if specialmodel[modelpath] or genericmodel[modelpath] then
|
||||
return specialmodel[modelpath] or genericmodel[modelpath]
|
||||
end
|
||||
local trunc = string.GetPathFromFilename(modelpath)
|
||||
return specialfolder[trunc] or genericfolder[trunc]
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
BADDIES
|
||||
]]
|
||||
blockedmodel = {
|
||||
["models/lubprops/seat/raceseat2.mdl"] = true,
|
||||
["models/lubprops/seat/raceseat.mdl"] = true,
|
||||
}
|
||||
|
||||
|
||||
--[[
|
||||
SPECIAL FOLDERS
|
||||
]]
|
||||
specialfolder["models/sprops/trans/wheel_b/"] = function(partnum, numparts, rotated, normal)
|
||||
if partnum == 1 then return rotated else return normal end
|
||||
end
|
||||
|
||||
specialfolder["models/sprops/trans/wheel_d/"] = function(partnum, numparts, rotated, normal)
|
||||
if partnum == 1 or partnum == 2 then return rotated else return normal end
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
SPECIAL MODELS
|
||||
]]
|
||||
local fix = function(partnum, numparts, rotated, normal)
|
||||
if partnum == 1 then return rotated else return normal end
|
||||
end
|
||||
specialmodel["models/sprops/trans/miscwheels/thin_moto15.mdl"] = fix
|
||||
specialmodel["models/sprops/trans/miscwheels/thin_moto20.mdl"] = fix
|
||||
specialmodel["models/sprops/trans/miscwheels/thin_moto25.mdl"] = fix
|
||||
specialmodel["models/sprops/trans/miscwheels/thin_moto30.mdl"] = fix
|
||||
specialmodel["models/sprops/trans/miscwheels/thick_moto15.mdl"] = fix
|
||||
specialmodel["models/sprops/trans/miscwheels/thick_moto20.mdl"] = fix
|
||||
specialmodel["models/sprops/trans/miscwheels/thick_moto25.mdl"] = fix
|
||||
specialmodel["models/sprops/trans/miscwheels/thick_moto30.mdl"] = fix
|
||||
|
||||
local fix = function(partnum, numparts, rotated, normal)
|
||||
if partnum == 1 or partnum == 2 then return rotated else return normal end
|
||||
end
|
||||
specialmodel["models/sprops/trans/miscwheels/tank15.mdl"] = fix
|
||||
specialmodel["models/sprops/trans/miscwheels/tank20.mdl"] = fix
|
||||
specialmodel["models/sprops/trans/miscwheels/tank25.mdl"] = fix
|
||||
specialmodel["models/sprops/trans/miscwheels/tank30.mdl"] = fix
|
||||
|
||||
local fix = function(partnum, numparts, rotated, normal)
|
||||
local angle = Angle(rotated)
|
||||
angle:RotateAroundAxis(angle:Forward(), 90)
|
||||
return angle
|
||||
end
|
||||
specialmodel["models/props_mining/diesel_generator_crank.mdl"] = fix
|
||||
specialmodel["models/props/de_nuke/hr_nuke/nuke_vent_bombsite/nuke_vent_bombsite_breakable_a.mdl"] = fix
|
||||
specialmodel["models/props/de_nuke/hr_nuke/nuke_vent_bombsite/nuke_vent_bombsite_breakable_b.mdl"] = fix
|
||||
specialmodel["models/props/de_nuke/hr_nuke/nuke_vent_bombsite/nuke_vent_bombsite_breakable_c.mdl"] = fix
|
||||
|
||||
|
||||
--[[
|
||||
GENERIC MODELS
|
||||
]]
|
||||
genericmodellist =
|
||||
[[models/autocannon/semiautocannon_25mm.mdl
|
||||
models/autocannon/semiautocannon_37mm.mdl
|
||||
models/autocannon/semiautocannon_45mm.mdl
|
||||
models/autocannon/semiautocannon_57mm.mdl
|
||||
models/autocannon/semiautocannon_76mm.mdl
|
||||
models/balloons/balloon_classicheart.mdl
|
||||
models/balloons/balloon_dog.mdl
|
||||
models/balloons/balloon_star.mdl
|
||||
models/balloons/hot_airballoon.mdl
|
||||
models/balloons/hot_airballoon_basket.mdl
|
||||
models/blacknecro/ledboard60.mdl
|
||||
models/blacknecro/tv_plasma_4_3.mdl
|
||||
models/chairs/armchair.mdl
|
||||
models/cheeze/wires/gyroscope.mdl
|
||||
models/cheeze/wires/ram.mdl
|
||||
models/cheeze/wires/router.mdl
|
||||
models/cheeze/wires/wireless_card.mdl
|
||||
models/combinecannon/cironwall.mdl
|
||||
models/combinecannon/remnants.mdl
|
||||
models/dynamite/dynamite.mdl
|
||||
models/engines/emotorlarge.mdl
|
||||
models/engines/emotormed.mdl
|
||||
models/engines/emotorsmall.mdl
|
||||
models/engines/gasturbine_l.mdl
|
||||
models/engines/gasturbine_m.mdl
|
||||
models/engines/gasturbine_s.mdl
|
||||
models/engines/linear_l.mdl
|
||||
models/engines/linear_m.mdl
|
||||
models/engines/linear_s.mdl
|
||||
models/engines/radial7l.mdl
|
||||
models/engines/radial7m.mdl
|
||||
models/engines/radial7s.mdl
|
||||
models/engines/transaxial_l.mdl
|
||||
models/engines/transaxial_m.mdl
|
||||
models/engines/transaxial_s.mdl
|
||||
models/engines/turbine_l.mdl
|
||||
models/engines/turbine_m.mdl
|
||||
models/engines/turbine_s.mdl
|
||||
models/engines/wankel_2_med.mdl
|
||||
models/engines/wankel_2_small.mdl
|
||||
models/engines/wankel_3_med.mdl
|
||||
models/engines/wankel_4_med.mdl
|
||||
models/extras/info_speech.mdl
|
||||
models/food/burger.mdl
|
||||
models/food/hotdog.mdl
|
||||
models/gears/planet_16.mdl
|
||||
models/gears/planet_mount.mdl
|
||||
models/gibs/helicopter_brokenpiece_01.mdl
|
||||
models/gibs/helicopter_brokenpiece_02.mdl
|
||||
models/gibs/helicopter_brokenpiece_03.mdl
|
||||
models/gibs/helicopter_brokenpiece_04_cockpit.mdl
|
||||
models/gibs/helicopter_brokenpiece_05_tailfan.mdl
|
||||
models/gibs/helicopter_brokenpiece_06_body.mdl
|
||||
models/gibs/shield_scanner_gib1.mdl
|
||||
models/gibs/shield_scanner_gib2.mdl
|
||||
models/gibs/shield_scanner_gib3.mdl
|
||||
models/gibs/shield_scanner_gib4.mdl
|
||||
models/gibs/shield_scanner_gib5.mdl
|
||||
models/gibs/shield_scanner_gib6.mdl
|
||||
models/gibs/strider_gib1.mdl
|
||||
models/gibs/strider_gib2.mdl
|
||||
models/gibs/strider_gib3.mdl
|
||||
models/gibs/strider_gib4.mdl
|
||||
models/gibs/strider_gib5.mdl
|
||||
models/gibs/strider_gib6.mdl
|
||||
models/gibs/strider_gib7.mdl
|
||||
models/holograms/hexagon.mdl
|
||||
models/holograms/icosphere.mdl
|
||||
models/holograms/icosphere2.mdl
|
||||
models/holograms/icosphere3.mdl
|
||||
models/holograms/prism.mdl
|
||||
models/holograms/sphere.mdl
|
||||
models/holograms/tetra.mdl
|
||||
models/howitzer/howitzer_75mm.mdl
|
||||
models/howitzer/howitzer_105mm.mdl
|
||||
models/howitzer/howitzer_122mm.mdl
|
||||
models/howitzer/howitzer_155mm.mdl
|
||||
models/howitzer/howitzer_203mm.mdl
|
||||
models/howitzer/howitzer_240mm.mdl
|
||||
models/howitzer/howitzer_290mm.mdl
|
||||
models/hunter/plates/plate05x05_rounded.mdl
|
||||
models/hunter/plates/plate1x3x1trap.mdl
|
||||
models/hunter/plates/plate1x4x2trap.mdl
|
||||
models/hunter/plates/plate1x4x2trap1.mdl
|
||||
models/items/357ammo.mdl
|
||||
models/items/357ammobox.mdl
|
||||
models/items/ammocrate_ar2.mdl
|
||||
models/items/ammocrate_grenade.mdl
|
||||
models/items/ammocrate_rockets.mdl
|
||||
models/items/ammocrate_smg1.mdl
|
||||
models/items/ammopack_medium.mdl
|
||||
models/items/ammopack_small.mdl
|
||||
models/items/crossbowrounds.mdl
|
||||
models/items/cs_gift.mdl
|
||||
models/lamps/torch.mdl
|
||||
models/machinegun/machinegun_20mm_compact.mdl
|
||||
models/machinegun/machinegun_30mm_compact.mdl
|
||||
models/machinegun/machinegun_40mm_compact.mdl
|
||||
models/maxofs2d/button_01.mdl
|
||||
models/maxofs2d/button_03.mdl
|
||||
models/maxofs2d/button_04.mdl
|
||||
models/maxofs2d/button_06.mdl
|
||||
models/maxofs2d/button_slider.mdl
|
||||
models/maxofs2d/camera.mdl
|
||||
models/maxofs2d/logo_gmod_b.mdl
|
||||
models/mechanics/articulating/arm_base_b.mdl
|
||||
models/nova/airboat_seat.mdl
|
||||
models/nova/chair_office01.mdl
|
||||
models/nova/chair_office02.mdl
|
||||
models/nova/chair_plastic01.mdl
|
||||
models/nova/chair_wood01.mdl
|
||||
models/nova/jalopy_seat.mdl
|
||||
models/nova/jeep_seat.mdl
|
||||
models/props/coop_kashbah/coop_stealth_boat/coop_stealth_boat_animated.mdl
|
||||
models/props/de_inferno/hr_i/inferno_vintage_radio/inferno_vintage_radio.mdl
|
||||
models/props_c17/doll01.mdl
|
||||
models/props_c17/door01_left.mdl
|
||||
models/props_c17/door02_double.mdl
|
||||
models/props_c17/suitcase_passenger_physics.mdl
|
||||
models/props_c17/trappropeller_blade.mdl
|
||||
models/props_c17/tv_monitor01.mdl
|
||||
models/props_canal/mattpipe.mdl
|
||||
models/props_canal/winch01b.mdl
|
||||
models/props_canal/winch02b.mdl
|
||||
models/props_canal/winch02c.mdl
|
||||
models/props_canal/winch02d.mdl
|
||||
models/props_combine/breenbust.mdl
|
||||
models/props_combine/breenbust_chunk01.mdl
|
||||
models/props_combine/breenbust_chunk02.mdl
|
||||
models/props_combine/breenbust_chunk04.mdl
|
||||
models/props_combine/breenbust_chunk05.mdl
|
||||
models/props_combine/breenbust_chunk06.mdl
|
||||
models/props_combine/breenbust_chunk07.mdl
|
||||
models/props_combine/breenchair.mdl
|
||||
models/props_combine/breenclock.mdl
|
||||
models/props_combine/breenpod.mdl
|
||||
models/props_combine/breenpod_inner.mdl
|
||||
models/props_combine/breen_tube.mdl
|
||||
models/props_combine/bunker_gun01.mdl
|
||||
models/props_combine/bustedarm.mdl
|
||||
models/props_combine/cell_01_pod_cheap.mdl
|
||||
models/props_combine/combinebutton.mdl
|
||||
models/props_combine/combinethumper001a.mdl
|
||||
models/props_combine/combinethumper002.mdl
|
||||
models/props_combine/combine_ballsocket.mdl
|
||||
models/props_combine/combine_mine01.mdl
|
||||
models/props_combine/combine_tptimer.mdl
|
||||
models/props_combine/eli_pod_inner.mdl
|
||||
models/props_combine/health_charger001.mdl
|
||||
models/props_combine/introomarea.mdl
|
||||
models/props_combine/soldier_bed.mdl
|
||||
models/props_combine/stalkerpod_physanim.mdl
|
||||
models/props_doors/door01_dynamic.mdl
|
||||
models/props_doors/door03_slotted_left.mdl
|
||||
models/props_doors/doorklab01.mdl
|
||||
models/props_junk/ravenholmsign.mdl
|
||||
models/props_lab/blastdoor001a.mdl
|
||||
models/props_lab/blastdoor001b.mdl
|
||||
models/props_lab/blastdoor001c.mdl
|
||||
models/props_lab/citizenradio.mdl
|
||||
models/props_lab/clipboard.mdl
|
||||
models/props_lab/crematorcase.mdl
|
||||
models/props_lab/hevplate.mdl
|
||||
models/props_lab/huladoll.mdl
|
||||
models/props_lab/kennel_physics.mdl
|
||||
models/props_lab/keypad.mdl
|
||||
models/props_lab/ravendoor.mdl
|
||||
models/props_lab/tpplug.mdl
|
||||
models/props_lab/tpswitch.mdl
|
||||
models/props_mining/ceiling_winch01.mdl
|
||||
models/props_mining/control_lever01.mdl
|
||||
models/props_mining/diesel_generator.mdl
|
||||
models/props_mining/elevator_winch_cog.mdl
|
||||
models/props_mining/switch01.mdl
|
||||
models/props_mining/switch_updown01.mdl
|
||||
models/props_phx/amraam.mdl
|
||||
models/props_phx/box_amraam.mdl
|
||||
models/props_phx/box_torpedo.mdl
|
||||
models/props_phx/cannon.mdl
|
||||
models/props_phx/carseat2.mdl
|
||||
models/props_phx/carseat3.mdl
|
||||
models/props_phx/construct/metal_angle90.mdl
|
||||
models/props_phx/construct/metal_angle180.mdl
|
||||
models/props_phx/construct/metal_dome90.mdl
|
||||
models/props_phx/construct/metal_dome180.mdl
|
||||
models/props_phx/construct/metal_plate1.mdl
|
||||
models/props_phx/construct/metal_plate1x2.mdl
|
||||
models/props_phx/construct/metal_plate2x2.mdl
|
||||
models/props_phx/construct/metal_plate2x4.mdl
|
||||
models/props_phx/construct/metal_plate4x4.mdl
|
||||
models/props_phx/construct/metal_plate_curve.mdl
|
||||
models/props_phx/construct/metal_plate_curve2.mdl
|
||||
models/props_phx/construct/metal_plate_curve2x2.mdl
|
||||
models/props_phx/construct/metal_plate_curve180.mdl
|
||||
models/props_phx/construct/metal_wire1x1x1.mdl
|
||||
models/props_phx/construct/metal_wire1x1x2.mdl
|
||||
models/props_phx/construct/metal_wire1x1x2b.mdl
|
||||
models/props_phx/construct/metal_wire1x2.mdl
|
||||
models/props_phx/construct/metal_wire1x2b.mdl
|
||||
models/props_phx/construct/metal_wire_angle90x1.mdl
|
||||
models/props_phx/construct/metal_wire_angle90x2.mdl
|
||||
models/props_phx/construct/metal_wire_angle180x1.mdl
|
||||
models/props_phx/construct/metal_wire_angle180x2.mdl
|
||||
models/props_phx/facepunch_logo.mdl
|
||||
models/props_phx/games/chess/black_king.mdl
|
||||
models/props_phx/games/chess/black_knight.mdl
|
||||
models/props_phx/games/chess/board.mdl
|
||||
models/props_phx/games/chess/white_king.mdl
|
||||
models/props_phx/games/chess/white_knight.mdl
|
||||
models/props_phx/gears/bevel9.mdl
|
||||
models/props_phx/gears/rack9.mdl
|
||||
models/props_phx/gears/rack18.mdl
|
||||
models/props_phx/gears/rack36.mdl
|
||||
models/props_phx/gears/rack70.mdl
|
||||
models/props_phx/gears/spur9.mdl
|
||||
models/props_phx/huge/road_curve.mdl
|
||||
models/props_phx/huge/road_long.mdl
|
||||
models/props_phx/huge/road_medium.mdl
|
||||
models/props_phx/huge/road_short.mdl
|
||||
models/props_phx/mechanics/slider1.mdl
|
||||
models/props_phx/mechanics/slider2.mdl
|
||||
models/props_phx/mk-82.mdl
|
||||
models/props_phx/playfield.mdl
|
||||
models/props_phx/torpedo.mdl
|
||||
models/props_phx/trains/double_wheels_base.mdl
|
||||
models/props_phx/trains/fsd-overrun.mdl
|
||||
models/props_phx/trains/fsd-overrun2.mdl
|
||||
models/props_phx/trains/monorail1.mdl
|
||||
models/props_phx/trains/monorail_curve.mdl
|
||||
models/props_phx/trains/trackslides_both.mdl
|
||||
models/props_phx/trains/trackslides_inner.mdl
|
||||
models/props_phx/trains/trackslides_outer.mdl
|
||||
models/props_phx/trains/wheel_base.mdl
|
||||
models/props_phx/wheels/breakable_tire.mdl
|
||||
models/props_phx/wheels/magnetic_large_base.mdl
|
||||
models/props_phx/wheels/magnetic_med_base.mdl
|
||||
models/props_phx/wheels/magnetic_small_base.mdl
|
||||
models/props_phx/ww2bomb.mdl
|
||||
models/props_placeable/witch_hatch_lid.mdl
|
||||
models/props_survival/repulsor/repulsor.mdl
|
||||
models/props_trainstation/passengercar001.mdl
|
||||
models/props_trainstation/passengercar001_dam01a.mdl
|
||||
models/props_trainstation/passengercar001_dam01c.mdl
|
||||
models/props_trainstation/train_outro_car01.mdl
|
||||
models/props_trainstation/train_outro_porch01.mdl
|
||||
models/props_trainstation/train_outro_porch02.mdl
|
||||
models/props_trainstation/train_outro_porch03.mdl
|
||||
models/props_trainstation/wrecked_train.mdl
|
||||
models/props_trainstation/wrecked_train_02.mdl
|
||||
models/props_trainstation/wrecked_train_divider_01.mdl
|
||||
models/props_trainstation/wrecked_train_door.mdl
|
||||
models/props_trainstation/wrecked_train_panel_01.mdl
|
||||
models/props_trainstation/wrecked_train_panel_02.mdl
|
||||
models/props_trainstation/wrecked_train_panel_03.mdl
|
||||
models/props_trainstation/wrecked_train_rack_01.mdl
|
||||
models/props_trainstation/wrecked_train_rack_02.mdl
|
||||
models/props_trainstation/wrecked_train_seat.mdl
|
||||
models/props_vehicles/mining_car.mdl
|
||||
models/props_vehicles/van001a_nodoor_physics.mdl
|
||||
models/props_wasteland/cranemagnet01a.mdl
|
||||
models/props_wasteland/wood_fence01a.mdl
|
||||
models/props_wasteland/wood_fence01b.mdl
|
||||
models/props_wasteland/wood_fence01c.mdl
|
||||
models/quarterlife/fsd-overrun-toy.mdl
|
||||
models/radar/radar_sp_big.mdl
|
||||
models/radar/radar_sp_mid.mdl
|
||||
models/radar/radar_sp_sml.mdl
|
||||
models/rotarycannon/kw/14_5mmrac.mdl
|
||||
models/rotarycannon/kw/20mmrac.mdl
|
||||
models/rotarycannon/kw/30mmrac.mdl
|
||||
models/segment.mdl
|
||||
models/segment2.mdl
|
||||
models/segment3.mdl
|
||||
models/shells/shell_9mm.mdl
|
||||
models/shells/shell_12gauge.mdl
|
||||
models/shells/shell_57.mdl
|
||||
models/shells/shell_338mag.mdl
|
||||
models/shells/shell_556.mdl
|
||||
models/shells/shell_762nato.mdl
|
||||
models/sprops/trans/fender_a/a_fender30.mdl
|
||||
models/sprops/trans/fender_a/a_fender35.mdl
|
||||
models/sprops/trans/fender_a/a_fender40.mdl
|
||||
models/sprops/trans/fender_a/a_fender45.mdl
|
||||
models/sprops/trans/train/double_24.mdl
|
||||
models/sprops/trans/train/double_36.mdl
|
||||
models/sprops/trans/train/double_48.mdl
|
||||
models/sprops/trans/train/double_72.mdl
|
||||
models/sprops/trans/train/single_24.mdl
|
||||
models/sprops/trans/train/single_36.mdl
|
||||
models/sprops/trans/train/single_48.mdl
|
||||
models/sprops/trans/train/single_72.mdl
|
||||
models/thrusters/jetpack.mdl
|
||||
models/vehicles/pilot_seat.mdl
|
||||
models/vehicles/prisoner_pod.mdl
|
||||
models/vehicles/prisoner_pod_inner.mdl
|
||||
models/vehicles/vehicle_van.mdl
|
||||
models/vehicles/vehicle_vandoor.mdl
|
||||
models/wingf0x/altisasocket.mdl
|
||||
models/wingf0x/ethernetplug.mdl
|
||||
models/wingf0x/ethernetsocket.mdl
|
||||
models/wingf0x/hdmiplug.mdl
|
||||
models/wingf0x/hdmisocket.mdl
|
||||
models/wingf0x/isaplug.mdl
|
||||
models/wingf0x/isasocket.mdl]]
|
||||
|
||||
|
||||
--[[
|
||||
GENERIC FOLDERS
|
||||
]]
|
||||
genericfolderlist =
|
||||
[[models/bull/gates/
|
||||
models/bull/various/
|
||||
models/cheeze/pcb/
|
||||
models/combine_turrets/
|
||||
models/engine/
|
||||
models/fueltank/
|
||||
models/jaanus/wiretool/
|
||||
models/kobilica/
|
||||
models/misc/
|
||||
models/phxtended/
|
||||
models/props_phx/construct/glass/
|
||||
models/props_phx/construct/plastic/
|
||||
models/props_phx/construct/windows/
|
||||
models/props_phx/construct/wood/
|
||||
models/props_phx/misc/
|
||||
models/props_phx/trains/tracks/
|
||||
models/sprops/trans/wheels_g/
|
||||
models/sprops/trans/wheel_big_g/
|
||||
models/sprops/trans/wheel_f/
|
||||
models/squad/sf_bars/
|
||||
models/squad/sf_plates/
|
||||
models/squad/sf_tris/
|
||||
models/squad/sf_tubes/
|
||||
models/weapons/
|
||||
models/wings/]]
|
321
lua/prop2mesh/sv_editor.lua
Normal file
321
lua/prop2mesh/sv_editor.lua
Normal file
@ -0,0 +1,321 @@
|
||||
--[[
|
||||
|
||||
]]
|
||||
util.AddNetworkString("prop2mesh_upload")
|
||||
util.AddNetworkString("prop2mesh_upload_start")
|
||||
|
||||
local net = net
|
||||
local util = util
|
||||
local table = table
|
||||
local next = next
|
||||
local pairs = pairs
|
||||
local pcall = pcall
|
||||
local isnumber = isnumber
|
||||
local isvector = isvector
|
||||
local isangle = isangle
|
||||
|
||||
local function canUpload(pl, self)
|
||||
if not IsValid(pl) then return false end
|
||||
if not prop2mesh.isValid(self) then return false end
|
||||
if CPPI and self:CPPIGetOwner() ~= pl then return false end
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
|
||||
]]
|
||||
local kvpass = {}
|
||||
|
||||
kvpass.vsmooth = function(data, index, val)
|
||||
if isnumber(val) then
|
||||
data[index].vsmooth = (data[index].objd and val) or (val ~= 0 and 1 or nil)
|
||||
end
|
||||
end
|
||||
|
||||
kvpass.vinvert = function(data, index, val)
|
||||
if isnumber(val) then
|
||||
data[index].vinvert = val ~= 0 and 1 or nil
|
||||
end
|
||||
end
|
||||
|
||||
kvpass.vinside = function(data, index, val)
|
||||
if isnumber(val) then
|
||||
data[index].vinside = val ~= 0 and 1 or nil
|
||||
end
|
||||
end
|
||||
|
||||
kvpass.pos = function(data, index, val)
|
||||
if isvector(val) then
|
||||
data[index].pos = val
|
||||
end
|
||||
end
|
||||
|
||||
kvpass.ang = function(data, index, val)
|
||||
if isangle(val) then
|
||||
data[index].ang = val
|
||||
end
|
||||
end
|
||||
|
||||
kvpass.scale = function(data, index, val)
|
||||
if isvector(val) and (val.x ~= 1 or val.y ~= 1 or val.z ~= 1) then
|
||||
data[index].scale = val
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
|
||||
]]
|
||||
local function makeSettingChanges(self, index, updates, forceSet)
|
||||
|
||||
end
|
||||
|
||||
local function makeUpdateChanges(self, index, updates, forceSet)
|
||||
local currentData = self:GetControllerData(index)
|
||||
if not currentData then
|
||||
return
|
||||
end
|
||||
|
||||
for pi, pdata in pairs(updates) do
|
||||
if pdata.kill then
|
||||
currentData[pi] = nil
|
||||
else
|
||||
for k, v in pairs(pdata) do
|
||||
local func = kvpass[k]
|
||||
if func then
|
||||
func(currentData, pi, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local updatedData = { custom = currentData.custom }
|
||||
|
||||
for k, v in pairs(currentData) do
|
||||
if tonumber(k) then
|
||||
updatedData[#updatedData + 1] = v
|
||||
end
|
||||
end
|
||||
|
||||
if forceSet then
|
||||
if next(updatedData) then
|
||||
self:SetControllerData(index, updatedData)
|
||||
else
|
||||
self:ResetControllerData(index)
|
||||
end
|
||||
end
|
||||
|
||||
return updatedData
|
||||
end
|
||||
|
||||
local function insertUpdateChanges(self, index, partlist, updates)
|
||||
local currentData
|
||||
if updates then
|
||||
currentData = makeUpdateChanges(self, index, updates, false)
|
||||
else
|
||||
currentData = self:GetControllerData(index)
|
||||
end
|
||||
if not currentData then
|
||||
return
|
||||
end
|
||||
|
||||
for i = 1, #currentData do
|
||||
partlist[#partlist + 1] = currentData[i]
|
||||
end
|
||||
if currentData.custom then
|
||||
for crc, data in pairs(currentData.custom) do
|
||||
partlist.custom[crc] = data
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function applyUpload(self)
|
||||
local finalData = {}
|
||||
|
||||
for id, upload in pairs(self.prop2mesh_upload_ready) do
|
||||
for index, changes in pairs(upload.controllers) do
|
||||
if not finalData[index] then
|
||||
finalData[index] = { custom = {} }
|
||||
end
|
||||
|
||||
pcall(insertUpdateChanges, self, index, finalData[index], changes.modme)
|
||||
|
||||
if changes.setme then
|
||||
if changes.setme.uvs then self:SetControllerUVS(index, changes.setme.uvs) end
|
||||
if changes.setme.scale then self:SetControllerScale(index, changes.setme.scale) end
|
||||
end
|
||||
|
||||
finalData[index].custom[id] = upload.data
|
||||
|
||||
for i = 1, #changes.addme do
|
||||
finalData[index][#finalData[index] + 1] = changes.addme[i]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for index, partlist in pairs(finalData) do
|
||||
if next(partlist) then
|
||||
self:SetControllerData(index, partlist)
|
||||
else
|
||||
self:ResetControllerData(index)
|
||||
end
|
||||
end
|
||||
|
||||
self.prop2mesh_upload_ready = nil
|
||||
self.prop2mesh_upload_queue = nil
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
|
||||
]]
|
||||
net.Receive("prop2mesh_upload_start", function(len, pl)
|
||||
if pl.prop2mesh_antispam then
|
||||
local wait = SysTime() - pl.prop2mesh_antispam
|
||||
if wait < 1 then
|
||||
pl:ChatPrint(string.format("Wait %d more seconds before uploading again", 1 - wait))
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local self = Entity(net.ReadUInt(16) or 0)
|
||||
if not canUpload(pl, self) then
|
||||
return
|
||||
end
|
||||
if self.prop2mesh_upload_queue then
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
local set, add, mod
|
||||
if net.ReadBool() then set = net.ReadTable() else set = {} end
|
||||
if net.ReadBool() then add = net.ReadTable() else add = {} end
|
||||
if net.ReadBool() then mod = net.ReadTable() else mod = {} end
|
||||
|
||||
if not next(add) then
|
||||
if next(set) or next(mod) then
|
||||
self:SetNetworkedBool("uploading", true)
|
||||
|
||||
for index, updates in pairs(set) do
|
||||
if updates.uvs then
|
||||
self:SetControllerUVS(index, updates.uvs)
|
||||
end
|
||||
if updates.scale then
|
||||
self:SetControllerScale(index, updates.scale)
|
||||
end
|
||||
end
|
||||
|
||||
for index, updates in pairs(mod) do
|
||||
pcall(makeUpdateChanges, self, index, updates, true)
|
||||
end
|
||||
|
||||
self.prop2mesh_upload_queue = true
|
||||
timer.Simple(0, function()
|
||||
self.prop2mesh_upload_queue = nil
|
||||
end)
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
pl.prop2mesh_antispam = SysTime()
|
||||
|
||||
local uploadQueue = {}
|
||||
local uploadReady = {}
|
||||
|
||||
for index, parts in pairs(add) do
|
||||
if not self.prop2mesh_controllers[index] then
|
||||
goto CONTINUE
|
||||
end
|
||||
|
||||
local data = self.prop2mesh_partlists[self.prop2mesh_controllers[index].crc]
|
||||
if data then
|
||||
data = util.JSONToTable(util.Decompress(data)) -- dangerrrrr
|
||||
data = data.custom
|
||||
end
|
||||
|
||||
for k, part in pairs(parts) do
|
||||
local crc = part.objd
|
||||
if crc then
|
||||
local datagrab = data and data[tonumber(crc)] or nil
|
||||
local utable = datagrab and uploadReady or uploadQueue
|
||||
|
||||
if not utable[crc] then
|
||||
utable[crc] = { controllers = {}, data = datagrab }
|
||||
end
|
||||
if not utable[crc].controllers[index] then
|
||||
utable[crc].controllers[index] = {
|
||||
addme = {},
|
||||
setme = set[index],
|
||||
modme = mod[index],
|
||||
}
|
||||
end
|
||||
|
||||
for key, val in pairs(part) do
|
||||
local func = kvpass[key]
|
||||
if func then
|
||||
func(parts, k, val)
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(utable[crc].controllers[index].addme, part)
|
||||
end
|
||||
end
|
||||
|
||||
::CONTINUE::
|
||||
end
|
||||
|
||||
if next(uploadQueue) then
|
||||
self.prop2mesh_upload_queue = uploadQueue
|
||||
self.prop2mesh_upload_ready = uploadReady
|
||||
|
||||
local keys = table.GetKeys(uploadQueue)
|
||||
net.Start("prop2mesh_upload_start")
|
||||
net.WriteUInt(self:EntIndex(), 16)
|
||||
net.WriteUInt(#keys, 8)
|
||||
for i = 1, #keys do
|
||||
net.WriteString(keys[i])
|
||||
end
|
||||
net.Send(pl)
|
||||
|
||||
elseif next(uploadReady) then
|
||||
self:SetNetworkedBool("uploading", true)
|
||||
|
||||
self.prop2mesh_upload_queue = uploadQueue
|
||||
self.prop2mesh_upload_ready = uploadReady
|
||||
|
||||
applyUpload(self)
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("prop2mesh_upload", function(len, pl)
|
||||
local self = Entity(net.ReadUInt(16) or 0)
|
||||
if not canUpload(pl, self) then
|
||||
return
|
||||
end
|
||||
|
||||
local crc = net.ReadString()
|
||||
|
||||
net.ReadStream(pl, function(data)
|
||||
if not canUpload(pl, self) then
|
||||
return
|
||||
end
|
||||
|
||||
local decomp = util.Decompress(data)
|
||||
if crc == tostring(util.CRC(decomp)) then
|
||||
self.prop2mesh_upload_ready[crc] = self.prop2mesh_upload_queue[crc]
|
||||
self.prop2mesh_upload_ready[crc].data = decomp
|
||||
self.prop2mesh_upload_queue[crc] = nil
|
||||
else
|
||||
self.prop2mesh_upload_queue[crc] = nil
|
||||
end
|
||||
|
||||
if next(self.prop2mesh_upload_queue) then
|
||||
return
|
||||
end
|
||||
|
||||
applyUpload(self)
|
||||
end)
|
||||
end)
|
||||
|
248
lua/prop2mesh/sv_entparts.lua
Normal file
248
lua/prop2mesh/sv_entparts.lua
Normal file
@ -0,0 +1,248 @@
|
||||
--[[
|
||||
-- one of
|
||||
prop -- modelpath
|
||||
holo -- modelpath
|
||||
objd -- crc of uncompressed data
|
||||
|
||||
-- required
|
||||
pos -- local pos
|
||||
ang -- local ang
|
||||
|
||||
-- optional
|
||||
scale -- x y z scales
|
||||
bodygroup -- mask
|
||||
vsmooth -- unset to use model normals, 0 to use flat shading, degrees to calculate
|
||||
vinvert -- flip normals
|
||||
vinside -- render inside
|
||||
objn -- prettyprint
|
||||
]]
|
||||
|
||||
local prop2mesh = prop2mesh
|
||||
|
||||
local next = next
|
||||
local TypeID = TypeID
|
||||
local IsValid = IsValid
|
||||
local WorldToLocal = WorldToLocal
|
||||
local LocalToWorld = LocalToWorld
|
||||
|
||||
local entclass = {}
|
||||
|
||||
|
||||
--[[
|
||||
|
||||
]]
|
||||
function prop2mesh.partsFromEnts(entlist, worldpos, worldang)
|
||||
local partlist = {}
|
||||
|
||||
for k, v in pairs(entlist) do
|
||||
local ent
|
||||
if TypeID(k) == TYPE_ENTITY then ent = k else ent = v end
|
||||
|
||||
local class = IsValid(ent) and entclass[ent:GetClass()]
|
||||
if class then
|
||||
class(partlist, ent, worldpos, worldang)
|
||||
end
|
||||
end
|
||||
|
||||
return next(partlist) and partlist
|
||||
end
|
||||
|
||||
function prop2mesh.sanitizeCustom(partlist) -- remove unused obj data
|
||||
if not partlist.custom then
|
||||
return
|
||||
end
|
||||
|
||||
local lookup = {}
|
||||
for crc, data in pairs(partlist.custom) do
|
||||
lookup[crc .. ""] = data
|
||||
end
|
||||
|
||||
local custom = {}
|
||||
for index, part in ipairs(partlist) do
|
||||
if part.objd then
|
||||
local crc = part.objd .. ""
|
||||
if crc and lookup[crc] then
|
||||
custom[crc] = lookup[crc]
|
||||
lookup[crc] = nil
|
||||
end
|
||||
end
|
||||
if not next(lookup) then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
partlist.custom = custom
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
|
||||
]]
|
||||
local function getBodygroupMask(ent)
|
||||
local mask = 0
|
||||
local offset = 1
|
||||
|
||||
for index = 0, ent:GetNumBodyGroups() - 1 do
|
||||
local bg = ent:GetBodygroup(index)
|
||||
mask = mask + offset * bg
|
||||
offset = offset * ent:GetBodygroupCount(index)
|
||||
end
|
||||
|
||||
return mask
|
||||
end
|
||||
|
||||
entclass.prop_physics = function(partlist, ent, worldpos, worldang)
|
||||
local part = { prop = ent:GetModel() }
|
||||
|
||||
part.pos, part.ang = WorldToLocal(ent:GetPos(), ent:GetAngles(), worldpos, worldang)
|
||||
|
||||
local bodygroup = getBodygroupMask(ent)
|
||||
if bodygroup ~= 0 then
|
||||
part.bodygroup = bodygroup
|
||||
end
|
||||
|
||||
local scale = ent:GetManipulateBoneScale(0)
|
||||
if scale.x ~= 1 or scale.y ~= 1 or scale.z ~= 1 then
|
||||
part.scale = scale
|
||||
end
|
||||
|
||||
local clips = ent.ClipData or ent.EntityMods and ent.EntityMods.clips
|
||||
if clips then
|
||||
local pclips = {}
|
||||
for _, clip in ipairs(clips) do
|
||||
if not clip.n or not clip.d then
|
||||
goto badclip
|
||||
end
|
||||
if clip.inside then
|
||||
part.vinside = 1
|
||||
end
|
||||
local normal = clip.n:Forward()
|
||||
pclips[#pclips + 1] = { n = normal, d = clip.d + normal:Dot(ent:OBBCenter()) }
|
||||
|
||||
::badclip::
|
||||
end
|
||||
if next(pclips) then
|
||||
part.clips = pclips
|
||||
end
|
||||
end
|
||||
|
||||
partlist[#partlist + 1] = part
|
||||
end
|
||||
|
||||
entclass.gmod_wire_hologram = function(partlist, ent, worldpos, worldang)
|
||||
local holo = ent.E2HoloData
|
||||
if not holo then
|
||||
return
|
||||
end
|
||||
|
||||
local part = { holo = ent:GetModel() }
|
||||
|
||||
part.pos, part.ang = WorldToLocal(ent:GetPos(), ent:GetAngles(), worldpos, worldang)
|
||||
|
||||
local bodygroup = getBodygroupMask(ent)
|
||||
if bodygroup ~= 0 then
|
||||
part.bodygroup = bodygroup
|
||||
end
|
||||
|
||||
if holo.scale and (holo.scale.x ~= 1 or holo.scale.y ~= 1 or holo.scale.z ~= 1) then
|
||||
part.scale = Vector(holo.scale)
|
||||
end
|
||||
|
||||
if holo.clips then
|
||||
local pclips = {}
|
||||
for _, clip in pairs(holo.clips) do
|
||||
if clip.localentid == 0 then -- this is a global clip... what to do here?
|
||||
goto badclip
|
||||
end
|
||||
local clipTo = Entity(clip.localentid)
|
||||
if not IsValid(clipTo) then
|
||||
goto badclip
|
||||
end
|
||||
|
||||
local normal = ent:WorldToLocal(clipTo:LocalToWorld(clip.normal:GetNormalized()) - clipTo:GetPos() + ent:GetPos())
|
||||
local origin = ent:WorldToLocal(clipTo:LocalToWorld(clip.origin))
|
||||
pclips[#pclips + 1] = { n = normal, d = normal:Dot(origin) }
|
||||
|
||||
::badclip::
|
||||
end
|
||||
if next(pclips) then
|
||||
part.clips = pclips
|
||||
end
|
||||
end
|
||||
|
||||
partlist[#partlist + 1] = part
|
||||
end
|
||||
|
||||
entclass.starfall_hologram = function(partlist, ent, worldpos, worldang)
|
||||
local part = { holo = ent:GetModel() }
|
||||
|
||||
part.pos, part.ang = WorldToLocal(ent:GetPos(), ent:GetAngles(), worldpos, worldang)
|
||||
|
||||
local bodygroup = getBodygroupMask(ent)
|
||||
if bodygroup ~= 0 then
|
||||
part.bodygroup = bodygroup
|
||||
end
|
||||
|
||||
if ent.scale then
|
||||
part.scale = ent:GetScale()
|
||||
end
|
||||
|
||||
if ent.clips then
|
||||
local pclips = {}
|
||||
for _, clip in pairs(holo.clips) do
|
||||
if not IsValid(clip.entity) then
|
||||
goto badclip
|
||||
end
|
||||
|
||||
local normal = ent:WorldToLocal(clip.entity:LocalToWorld(clip.normal:GetNormalized()) - clip.entity:GetPos() + ent:GetPos())
|
||||
local origin = ent:WorldToLocal(clip.entity:LocalToWorld(clip.origin))
|
||||
pclips[#pclips + 1] = { n = normal, d = normal:Dot(origin) }
|
||||
|
||||
::badclip::
|
||||
end
|
||||
if next(pclips) then
|
||||
part.clips = pclips
|
||||
end
|
||||
end
|
||||
|
||||
partlist[#partlist + 1] = part
|
||||
end
|
||||
|
||||
local function transformPartlist(ent, index, worldpos, worldang)
|
||||
local partlist = ent:GetControllerData(index)
|
||||
if not partlist then
|
||||
return
|
||||
end
|
||||
|
||||
local localpos = ent:GetPos()
|
||||
local localang = ent:GetAngles()
|
||||
|
||||
for k, v in ipairs(partlist) do
|
||||
v.pos, v.ang = LocalToWorld(v.pos, v.ang, localpos, localang)
|
||||
v.pos, v.ang = WorldToLocal(v.pos, v.ang, worldpos, worldang)
|
||||
end
|
||||
|
||||
return partlist
|
||||
end
|
||||
|
||||
entclass.sent_prop2mesh_legacy = function(partlist, ent, worldpos, worldang)
|
||||
local ok, err = pcall(transformPartlist, ent, 1, worldpos, worldang)
|
||||
|
||||
if ok then
|
||||
if err.custom then
|
||||
if not partlist.custom then
|
||||
partlist.custom = {}
|
||||
end
|
||||
for k, v in pairs(err.custom) do
|
||||
partlist.custom[k] = v
|
||||
end
|
||||
end
|
||||
for i = 1, #err do
|
||||
partlist[#partlist + 1] = err[i]
|
||||
end
|
||||
|
||||
partlist.uvs = ent:GetControllerUVS(1)
|
||||
else
|
||||
print(err)
|
||||
end
|
||||
end
|
1132
lua/weapons/gmod_tool/stools/prop2mesh.lua
Normal file
1132
lua/weapons/gmod_tool/stools/prop2mesh.lua
Normal file
File diff suppressed because it is too large
Load Diff
7
materials/p2m/cube.vmt
Normal file
7
materials/p2m/cube.vmt
Normal file
@ -0,0 +1,7 @@
|
||||
"UnlitGeneric"
|
||||
{
|
||||
"$baseTexture" "p2m/cube"
|
||||
"$selfillum" "1"
|
||||
"$reflectivity" "[0 0 0]"
|
||||
"$nodecal" "1"
|
||||
}
|
BIN
materials/p2m/cube.vtf
Normal file
BIN
materials/p2m/cube.vtf
Normal file
Binary file not shown.
BIN
models/p2m/cube.dx80.vtx
Normal file
BIN
models/p2m/cube.dx80.vtx
Normal file
Binary file not shown.
BIN
models/p2m/cube.dx90.vtx
Normal file
BIN
models/p2m/cube.dx90.vtx
Normal file
Binary file not shown.
BIN
models/p2m/cube.mdl
Normal file
BIN
models/p2m/cube.mdl
Normal file
Binary file not shown.
BIN
models/p2m/cube.phy
Normal file
BIN
models/p2m/cube.phy
Normal file
Binary file not shown.
BIN
models/p2m/cube.sw.vtx
Normal file
BIN
models/p2m/cube.sw.vtx
Normal file
Binary file not shown.
BIN
models/p2m/cube.vvd
Normal file
BIN
models/p2m/cube.vvd
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user