From 4d5744976616f81f23bc797ea06466b179f3edcd Mon Sep 17 00:00:00 2001 From: Chev Date: Sat, 4 Nov 2023 13:40:49 -0700 Subject: [PATCH] New addon: SprayMesh Extended --- addons/README.md | 1 + addons/spraymesh_extended/addon.json | 6 + .../lua/autorun/client/cl_spraymesh.lua | 2 + .../lua/autorun/server/sv_spraymesh.lua | 11 + .../lua/spraymesh/client/cl_derma_utils.lua | 107 ++ .../lua/spraymesh/client/cl_init.lua | 997 ++++++++++++++++++ .../client/cl_sandbox_context_menu.lua | 21 + .../lua/spraymesh/client/cl_spray_list_db.lua | 49 + .../lua/spraymesh/server/sv_init.lua | 208 ++++ .../lua/spraymesh/sh_config.lua | 83 ++ .../lua/spraymesh/sh_init.lua | 109 ++ .../lua/vgui/dsprayconfiguration.lua | 498 +++++++++ .../lua/vgui/dsprayhelp.lua | 225 ++++ .../lua/vgui/dsprayviewer.lua | 147 +++ .../materials/icon64/spraymesh.png | Bin 0 -> 5553 bytes .../materials/spraymesh/fake_transparent.png | Bin 0 -> 211 bytes images/icons/spraymesh-extended.png | Bin 0 -> 474340 bytes images/icons/spraymesh-extended.xcf | Bin 0 -> 4009550 bytes images/screenshots/spraymesh-extended-01.jpg | Bin 0 -> 327549 bytes images/screenshots/spraymesh-extended-02.jpg | Bin 0 -> 115804 bytes images/screenshots/spraymesh-extended-03.jpg | Bin 0 -> 464473 bytes images/screenshots/spraymesh-extended-04.png | Bin 0 -> 617369 bytes .../spraymesh-extended-04_unedited.jpg | Bin 0 -> 92215 bytes images/screenshots/spraymesh-extended-05.png | Bin 0 -> 531853 bytes images/screenshots/spraymesh-extended-06.png | Bin 0 -> 614301 bytes .../spraymesh-extended-06_unedited.jpg | Bin 0 -> 116942 bytes images/screenshots/spraymesh-extended-07.png | Bin 0 -> 1403001 bytes .../spraymesh-extended-07_unedited.jpg | Bin 0 -> 202946 bytes .../screenshots/spraymesh-extended-icon.jpg | Bin 0 -> 473399 bytes 29 files changed, 2464 insertions(+) create mode 100644 addons/spraymesh_extended/addon.json create mode 100644 addons/spraymesh_extended/lua/autorun/client/cl_spraymesh.lua create mode 100644 addons/spraymesh_extended/lua/autorun/server/sv_spraymesh.lua create mode 100644 addons/spraymesh_extended/lua/spraymesh/client/cl_derma_utils.lua create mode 100644 addons/spraymesh_extended/lua/spraymesh/client/cl_init.lua create mode 100644 addons/spraymesh_extended/lua/spraymesh/client/cl_sandbox_context_menu.lua create mode 100644 addons/spraymesh_extended/lua/spraymesh/client/cl_spray_list_db.lua create mode 100644 addons/spraymesh_extended/lua/spraymesh/server/sv_init.lua create mode 100644 addons/spraymesh_extended/lua/spraymesh/sh_config.lua create mode 100644 addons/spraymesh_extended/lua/spraymesh/sh_init.lua create mode 100644 addons/spraymesh_extended/lua/vgui/dsprayconfiguration.lua create mode 100644 addons/spraymesh_extended/lua/vgui/dsprayhelp.lua create mode 100644 addons/spraymesh_extended/lua/vgui/dsprayviewer.lua create mode 100644 addons/spraymesh_extended/materials/icon64/spraymesh.png create mode 100644 addons/spraymesh_extended/materials/spraymesh/fake_transparent.png create mode 100644 images/icons/spraymesh-extended.png create mode 100644 images/icons/spraymesh-extended.xcf create mode 100644 images/screenshots/spraymesh-extended-01.jpg create mode 100644 images/screenshots/spraymesh-extended-02.jpg create mode 100644 images/screenshots/spraymesh-extended-03.jpg create mode 100644 images/screenshots/spraymesh-extended-04.png create mode 100644 images/screenshots/spraymesh-extended-04_unedited.jpg create mode 100644 images/screenshots/spraymesh-extended-05.png create mode 100644 images/screenshots/spraymesh-extended-06.png create mode 100644 images/screenshots/spraymesh-extended-06_unedited.jpg create mode 100644 images/screenshots/spraymesh-extended-07.png create mode 100644 images/screenshots/spraymesh-extended-07_unedited.jpg create mode 100644 images/screenshots/spraymesh-extended-icon.jpg diff --git a/addons/README.md b/addons/README.md index d205eee..49df45a 100644 --- a/addons/README.md +++ b/addons/README.md @@ -18,5 +18,6 @@ Listed below is all of the addons you can find in this repo. - `ror2_hud`: A Risk of Rain 2-inspired replacement to the default HUD. [Steam Workshop](https://steamcommunity.com/sharedfiles/filedetails/?id=2210715789) - `screenshot_editor`: Lets you customize & save your Garry's Mod screenshots by applying effects and filters. [Steam Workshop](https://steamcommunity.com/sharedfiles/filedetails/?id=2910871996) - `simple_bunnyhop`: A gamemode designed for bunnyhopping. [Steam Workshop](https://steamcommunity.com/sharedfiles/filedetails/?id=1767781900) +- `spraymesh_extended`: An improvement to the original SprayMesh with various new features, bug fixes and optimizations. [Steam Workshop](https://steamcommunity.com/sharedfiles/filedetails/?id=3072351693) - `vector_loc`: A developer tool that allows you to visualize the locations of vector coordinates on a map. [Steam Workshop](https://steamcommunity.com/sharedfiles/filedetails/?id=1782161573) - `vm_velocity`: Makes viewmodels move up and down depending on how fast you moving vertically. [Steam Workshop](https://steamcommunity.com/sharedfiles/filedetails/?id=2294290206) diff --git a/addons/spraymesh_extended/addon.json b/addons/spraymesh_extended/addon.json new file mode 100644 index 0000000..5eecfec --- /dev/null +++ b/addons/spraymesh_extended/addon.json @@ -0,0 +1,6 @@ +{ + "title": "SprayMesh Extended", + "type": "effects", + "tags": ["fun", "build"], + "ignore": [] +} diff --git a/addons/spraymesh_extended/lua/autorun/client/cl_spraymesh.lua b/addons/spraymesh_extended/lua/autorun/client/cl_spraymesh.lua new file mode 100644 index 0000000..4e3c49c --- /dev/null +++ b/addons/spraymesh_extended/lua/autorun/client/cl_spraymesh.lua @@ -0,0 +1,2 @@ +-- Initialize SprayMesh Extended on the client +include("spraymesh/client/cl_init.lua") diff --git a/addons/spraymesh_extended/lua/autorun/server/sv_spraymesh.lua b/addons/spraymesh_extended/lua/autorun/server/sv_spraymesh.lua new file mode 100644 index 0000000..0928939 --- /dev/null +++ b/addons/spraymesh_extended/lua/autorun/server/sv_spraymesh.lua @@ -0,0 +1,11 @@ +-- Initialize SprayMesh Extended on the server +include("spraymesh/server/sv_init.lua") + +-- Send Lua files to the client +AddCSLuaFile("spraymesh/sh_init.lua") +AddCSLuaFile("spraymesh/sh_config.lua") + +AddCSLuaFile("spraymesh/client/cl_init.lua") +AddCSLuaFile("spraymesh/client/cl_spray_list_db.lua") +AddCSLuaFile("spraymesh/client/cl_derma_utils.lua") +AddCSLuaFile("spraymesh/client/cl_sandbox_context_menu.lua") diff --git a/addons/spraymesh_extended/lua/spraymesh/client/cl_derma_utils.lua b/addons/spraymesh_extended/lua/spraymesh/client/cl_derma_utils.lua new file mode 100644 index 0000000..d28d474 --- /dev/null +++ b/addons/spraymesh_extended/lua/spraymesh/client/cl_derma_utils.lua @@ -0,0 +1,107 @@ +spraymesh_derma_utils = spraymesh_derma_utils or {} + +-- Enables the maximize button on DFrame panels, used by SprayMesh Extended panels +function spraymesh_derma_utils.EnableMaximizeButton(dframe) + dframe.btnMaxim.Maximized = false + dframe.btnMaxim.OriginalSize = {panelWidth, panelHeight} + dframe.btnMaxim.OriginalPos = {dframe:GetX(), dframe:GetY()} + dframe.btnMaxim:SetDisabled(false) + dframe.btnMaxim.DoClick = function(pnl) + local targetSize = {512, 512} + local targetPos = {0, 0} + + -- If we're maximized, unmaximize + if pnl.Maximized then + targetSize = pnl.OriginalSize + targetPos = pnl.OriginalPos + -- If we're unmaximized, maximize + else + -- Store current position and size if the user decides to unmaximize later + pnl.OriginalSize = {dframe:GetSize()} + pnl.OriginalPos = {dframe:GetPos()} + + targetSize = {ScrW(), ScrH()} + targetPos = {0, 0} + end + + pnl.Maximized = not pnl.Maximized + + -- Don't allow the button to be clicked while the transition animation plays + pnl:SetEnabled(false) + local animData = dframe:NewAnimation(0.4, 0, 0.3, function(animTable, tgtPanel) + if IsValid(pnl) then + pnl:SetEnabled(true) + end + end) + animData.StartSize = {dframe:GetSize()} + animData.EndSize = targetSize + animData.StartPos = {dframe:GetPos()} + animData.EndPos = targetPos + + animData.Think = function(animTable, tgtPanel, fraction) + local easedFraction = math.ease.OutSine(fraction) + + local easedPosX = Lerp(easedFraction, animTable.StartPos[1], animTable.EndPos[1]) + local easedPosY = Lerp(easedFraction, animTable.StartPos[2], animTable.EndPos[2]) + local easedSizeW = Lerp(easedFraction, animTable.StartSize[1], animTable.EndSize[1]) + local easedSizeH = Lerp(easedFraction, animTable.StartSize[2], animTable.EndSize[2]) + + tgtPanel:SetPos(math.Round(easedPosX, 0), math.Round(easedPosY, 0)) + tgtPanel:SetSize(math.Round(easedSizeW, 0), math.Round(easedSizeH, 0)) + end + end +end + +-- Get preview HTML to preview sprays using DHTML +local PREVIEW_HTML_BASE = [=[ + + + + + + + + %s + + +]=] + +local PREVIEW_HTML_IMAGE = [=[]=] + +function spraymesh_derma_utils.GetPreviewHTML(previewSize, sprayURL) + local elementFormatted = "" + + if spraymesh.IsVideoExtension(sprayURL) then + elementFormatted = Format(PREVIEW_HTML_VIDEO, string.JavascriptSafe(sprayURL)) + elseif spraymesh.IsImageExtension(sprayURL) then + elementFormatted = Format(PREVIEW_HTML_IMAGE, string.JavascriptSafe(sprayURL)) + -- If we can't figure out the type, assume it's an image + else + elementFormatted = Format(PREVIEW_HTML_IMAGE, string.JavascriptSafe(sprayURL)) + + spraymesh.DebugPrint("(spraymesh_derma_utils.GetPreviewHTML) Could not figure out image/video type for URL " .. sprayURL) + end + + return Format( + PREVIEW_HTML_BASE, + previewSize, + previewSize, + elementFormatted + ) +end diff --git a/addons/spraymesh_extended/lua/spraymesh/client/cl_init.lua b/addons/spraymesh_extended/lua/spraymesh/client/cl_init.lua new file mode 100644 index 0000000..76a1a8f --- /dev/null +++ b/addons/spraymesh_extended/lua/spraymesh/client/cl_init.lua @@ -0,0 +1,997 @@ +-- Include shared Lua files +include("spraymesh/sh_init.lua") + +-- Include clientside Lua files (usually dependencies before we run the main script here) +include("spraymesh/client/cl_spray_list_db.lua") +include("spraymesh/client/cl_derma_utils.lua") +include("spraymesh/client/cl_sandbox_context_menu.lua") + +-- +-- Create ConVars +-- +local CVAR_ENABLE_SPRAYS = CreateClientConVar("spraymesh_enablesprays", "1", true, false, "Whether or not to show all player sprays.", 0, 1) +local CVAR_ENABLE_ANIMATED_SPRAYS = CreateClientConVar("spraymesh_enableanimated", "1", true, false, "Whether or not to show animated sprays.", 0, 1) +CreateClientConVar("spraymesh_url", spraymesh.SPRAY_URL_DEFAULT, true, true, "The URL to use for your spray.") + +-- +-- Clientside variables and such +-- + +-- Used by the client to render sprays in order +-- Done so sprays can be "overwritten", and also for performance +spraymesh.RENDER_ITER_CLIENT = spraymesh.RENDER_ITER_CLIENT or {} + +setmetatable(spraymesh.SPRAYDATA, { + -- Reset render iteration table cache when the main spraymesh table is modified + __newindex = function(tb, key, value) + rawset(tb, key, value) + + spraymesh.RENDER_ITER_CLIENT = nil + end, +}) + +-- Whether or not we're currently rendering names over player sprays (via spraymesh_shownames) +local SPRAY_SHOWING_NAMES = false + +-- Sprays that need to be reloaded will be put in here +local SPRAY_RELOAD_QUEUE = {} + +function spraymesh.ReloadSprays() + spraymesh.RemoveSprays() + + for id64, data in pairs(spraymesh.SPRAYDATA) do + SPRAY_RELOAD_QUEUE[id64] = data + end +end + +function spraymesh.ReloadSpray(id64) + if not spraymesh.SPRAYDATA[id64] then return end + + SPRAY_RELOAD_QUEUE[id64] = spraymesh.SPRAYDATA[id64] +end + +function spraymesh.RemoveSpray(id64) + if not spraymesh.SPRAYDATA[id64] then return end + + local meshData = spraymesh.SPRAYDATA[id64].meshdata + if meshData and meshData.mesh and IsValid(meshData.mesh) then + meshData.mesh:Destroy() + meshData.mesh = nil + end + + spraymesh.SPRAYDATA[id64] = nil +end + +function spraymesh.Instructions() + chat.AddText( + spraymesh.PRIMARY_CHAT_COLOR, "This server uses ", + spraymesh.ACCENT_CHAT_COLOR, "SprayMesh Extended! ", + spraymesh.PRIMARY_CHAT_COLOR, "Use /spraymesh to change your spray." + ) +end + +-- URL material solver +local imats = {} + +-- Where panels are during loading +local htmlpanels = {} + +-- Where panels are for animation, after loading +local htmlpanelsanim = {} + +local RT_SPRAY_PENDING = GetRenderTargetEx( + "spraymesh_pending_spray", + spraymesh.IMAGE_RESOLUTION, + spraymesh.IMAGE_RESOLUTION, + RT_SIZE_DEFAULT, + MATERIAL_RT_DEPTH_SEPARATE, + bit.bor(4, 8, 16, 256), + 0, + IMAGE_FORMAT_BGR888 +) + +local MAT_SPRAY_PENDING = CreateMaterial("spraymesh/pending_spray_placeholder", "UnlitGeneric", { + ["$basetexture"] = RT_SPRAY_PENDING:GetName(), + + -- Allows custom coloring + ["$vertexcolor"] = 1, + ["$vertexalpha"] = 1, + ["$model"] = 1, + ["$nocull"] = 1, + ["$receiveflashlight"] = 1 +}) + +local RT_SPRAY_DISABLEDVIDEO = GetRenderTargetEx( + "spraymesh_disabled_video", + spraymesh.IMAGE_RESOLUTION, + spraymesh.IMAGE_RESOLUTION, + RT_SIZE_DEFAULT, + MATERIAL_RT_DEPTH_SEPARATE, + bit.bor(4, 8, 16, 256), + 0, + IMAGE_FORMAT_BGR888 +) + +local RT_SPRAY_DISABLEDSPRAY = GetRenderTargetEx( + "spraymesh_disabled_spray", + spraymesh.IMAGE_RESOLUTION, + spraymesh.IMAGE_RESOLUTION, + RT_SIZE_DEFAULT, + MATERIAL_RT_DEPTH_SEPARATE, + bit.bor(4, 8, 16, 256), + 0, + IMAGE_FORMAT_BGR888 +) + +-- "url" is the URL with ?uniquerequest= stuff added at the end and https:// added to the front +-- "urloriginal" is simply the original URL +local function generateHTMLPanel(url, urloriginal, callback) + if not string.find(url, "^https?://", 0, false) then + url = "https://" .. url + end + + spraymesh.DebugPrint("Generating HTML panel: ", url) + + -- Use spray image resolution from config + local size = spraymesh.IMAGE_RESOLUTION + + -- Persisting container, for cutting short anims but also drawing an overlay + local panelContainer = {} + + local panelHTML = vgui.Create("DHTML") + panelHTML:SetSize(size, size) + panelHTML:SetAllowLua(false) + panelHTML:SetAlpha(0) + panelHTML:SetMouseInputEnabled(false) + panelHTML:SetScrollbars(false) + panelHTML.ConsoleMessage = function(panel, msg) + spraymesh.DebugPrint("HTML ConsoleMessage: " .. tostring(msg)) + end + + panelContainer.panel = panelHTML + + -- Set image/video HTML for the panel + spraymesh.HTMLHandlers.Get(url, size, panelContainer) + panelContainer.origurl = urloriginal + panelContainer.callback = callback + + panelContainer.IsAnimated = (spraymesh.GetURLInfo(urloriginal) == SPRAYTYPE_VIDEO or string.EndsWith(urloriginal, ".gif")) + + panelContainer.RT = GetRenderTargetEx( + "SprayMesh_URL_" .. util.SHA256(url), + size, + size, + RT_SIZE_DEFAULT, + MATERIAL_RT_DEPTH_SEPARATE, + bit.bor(4, 8, 16, 256), + 0, + IMAGE_FORMAT_BGRA8888 + ) + + function panelContainer:PaintSpray() + if not self.FinalMaterial then return end + + -- If sprays aren't enabled AT ALL + if not CVAR_ENABLE_SPRAYS:GetBool() then + self.FinalMaterial:SetTexture("$basetexture", RT_SPRAY_DISABLEDSPRAY) + return + end + + -- If animated sprays aren't enabled + if self.IsAnimated and not CVAR_ENABLE_ANIMATED_SPRAYS:GetBool() then + self.FinalMaterial:SetTexture("$basetexture", RT_SPRAY_DISABLEDVIDEO) + return + end + + -- If spraymesh_shownames was called, show a black background + if SPRAY_SHOWING_NAMES then + -- This makes the spray invisible/black for animkilled sprays... + self.FinalMaterial:SetTexture("$basetexture", self.RT) + else + -- FPS saver when not showing names + self.FinalMaterial:SetTexture("$basetexture", self.htmlmat:GetName()) + return + end + + render.PushRenderTarget(self.RT) + cam.Start2D() + local sW, sH = ScrW(), ScrH() + + local spraytex = surface.GetTextureID(self.htmlmat:GetName()) + surface.SetDrawColor(255, 255, 255, 255) + surface.SetTexture(spraytex) + surface.DrawTexturedRect(0, 0, sW, sH) + + if SPRAY_SHOWING_NAMES then + local count = 1 + + for id64, data in pairs(spraymesh.SPRAYDATA) do + -- * This is stupid and inefficient, but it only runs when spraymesh_shownames is called, + -- * in which case, good FPS probably isn't important at that very moment + if data.url == urloriginal then + surface.SetDrawColor(0, 255, 0, 255) + surface.DrawOutlinedRect(0, 0, sW, sH, 3) + + local text = ("%s (%s)"):format(data.PlayerName, id64) + draw.WordBox(4, 10, (32 * count) - 22, text, "TargetID", color_black, color_white) + + count = count + 1 + end + end + + draw.WordBox(4, 10, sH - 38, urloriginal, "TargetID", color_black, color_white) + end + cam.End2D() + render.PopRenderTarget() + end + + table.insert(htmlpanels, panelContainer) +end + +local function generateHTMLTexture(url, meshData, callback) + --[[ + how to use: + MyNewImaterial = generateHTMLTexture(url, meshData, function(imat) + -- custom callback code, for when the image is fully loaded and the meshData has been applied + -- imat argument is the loaded imaterial + end) + meshData is a table pointer, and needs to contain an imaterial key + ]] + spraymesh.DebugPrint("Generating HTML material for " .. url) + + -- If the IMaterial doesn't exist yet, initialize it + if imats[url] == nil then + -- Pending table + imats[url] = {} + table.insert(imats[url], {meshData, callback}) + + -- The uniquerequest guff is to stop the game from ever using its internal cache of web resources, because it returns bonkers sizes at random + local newURL = url .. "?uniquerequest=" .. math.floor(SysTime() * 1000) + + generateHTMLPanel(newURL, url, function(imat) + -- Should be + if type(imats[url]) == "table" then + for k, v in pairs(imats[url]) do + local meshDataCurrent = v[1] + local optionalCallback = v[2] + + meshDataCurrent.imaterial = imat + + if optionalCallback then + optionalCallback(imat) + end + + spraymesh.DebugPrint("Finished generating HTML material; replacing dummy texture") + end + + imats[url] = imat + end + end) + + spraymesh.DebugPrint("Generating, giving dummy texture") + + return MAT_SPRAY_PENDING + elseif type(imats[url]) == "table" then + -- Pending table; texture is still generating + spraymesh.DebugPrint("Generated texture is currently pending...") + + table.insert(imats[url], {meshData, callback}) + + return MAT_SPRAY_PENDING + else + spraymesh.DebugPrint("Generated texture already exists") + + return imats[url] + end +end + +local function copyVert(copy, u, v, norm, bnorm, tang) + u = u or 0 + v = v or 0 + norm = norm or 1 + bnorm = bnorm or Vector(0, 0, 0) + tang = tang or 1 + local t = table.Copy(copy) + t.u, t.v, t.normal, t.bitnormal, t.tangent = u, v, norm, bnorm, tang + + return t +end + +-- D C = ix+0,iy+1 ix+1,iy+1 +-- A B = ix+0,iy+0 ix+1,iy+0 +-- Bottom left corner coord +local function addSquareToPoints(x, y, points, coords) + --[[local _a = copyVert(coords[x+0][y+0],0,0) -- Repeating texture per square + local _b = copyVert(coords[x+1][y+0],1,0) -- Probably also needs a y flip + local _c = copyVert(coords[x+1][y+1],1,1) + local _d = copyVert(coords[x+0][y+1],0,1)]] + local rm1 = spraymesh.MESH_RESOLUTION - 1 + local __a = coords[x + 0][y + 0] + local __b = coords[x + 1][y + 0] + local __c = coords[x + 1][y + 1] + local __d = coords[x + 0][y + 1] + + if __a.bad then + __a = coords[x + 0][math.Clamp(y + 1, 0, spraymesh.MESH_RESOLUTION - 1)] + end + + if __b.bad then + __b = coords[x + 1][math.Clamp(y + 1, 0, spraymesh.MESH_RESOLUTION - 1)] + end + + if __c.bad then + __c = coords[x + 1][math.Clamp(y + 0, 0, spraymesh.MESH_RESOLUTION - 1)] + end + + if __d.bad then + __d = coords[x + 0][math.Clamp(y + 0, 0, spraymesh.MESH_RESOLUTION - 1)] + end + + -- Probably could simply replace the other but eh + if __a.bad then + __a = coords[math.Clamp(x + 1, 0, spraymesh.MESH_RESOLUTION - 1)][math.Clamp(y + 1, 0, spraymesh.MESH_RESOLUTION - 1)] + end + + if __b.bad then + __b = coords[math.Clamp(x + 0, 0, spraymesh.MESH_RESOLUTION - 1)][math.Clamp(y + 1, 0, spraymesh.MESH_RESOLUTION - 1)] + end + + if __c.bad then + __c = coords[math.Clamp(x + 0, 0, spraymesh.MESH_RESOLUTION - 1)][math.Clamp(y + 0, 0, spraymesh.MESH_RESOLUTION - 1)] + end + + if __d.bad then + __d = coords[math.Clamp(x + 1, 0, spraymesh.MESH_RESOLUTION - 1)][math.Clamp(y + 0, 0, spraymesh.MESH_RESOLUTION - 1)] + end + + local _a = copyVert(__a, (x + 0) / rm1, 1 - ((y + 0) / rm1)) -- Stretch texture over all squares + local _b = copyVert(__b, (x + 1) / rm1, 1 - ((y + 0) / rm1)) + local _c = copyVert(__c, (x + 1) / rm1, 1 - ((y + 1) / rm1)) + local _d = copyVert(__d, (x + 0) / rm1, 1 - ((y + 1) / rm1)) + table.insert(points, _a) -- Adccba + table.insert(points, _d) + table.insert(points, _c) + table.insert(points, _c) + table.insert(points, _b) + table.insert(points, _a) +end + +function spraymesh.PlaceSpray(sprayData) + local id64 = sprayData.SteamID64 + local nick = sprayData.PlayerName + local hitpos = sprayData.HitPos + local hitnormal = sprayData.HitNormal + local url = sprayData.URL + local playSpraySound = sprayData.PlaySpraySound + local coordDist = sprayData.CoordDistance + local sprayTime = sprayData.SprayTime + + local tracenormal = sprayData.TraceNormal + local anglenormal = tracenormal:Angle() + anglenormal:Normalize() + + local URLToSpray = url + local lpid64 = LocalPlayer():SteamID64() + + -- Give other code a chance to block the spray on the client + local shouldAllowSpray = hook.Run("SprayMesh.ClientShouldAllowSpray", sprayData) ~= false + if not shouldAllowSpray then return end + + -- If the local player is spraying the default spray, show them help instructions in chat + local sprayIsDefault = url == spraymesh.SPRAY_URL_DEFAULT + sprayIsDefault = sprayIsDefault or url == "http://" .. spraymesh.SPRAY_URL_DEFAULT + sprayIsDefault = sprayIsDefault or url == "https://" .. spraymesh.SPRAY_URL_DEFAULT + + if id64 == lpid64 and sprayIsDefault then + spraymesh.Instructions() + end + + -- Play the spray sound + if playSpraySound then sound.Play("SprayCan.Paint", hitpos, 60, 100, .3) end + + -- + -- Create spray mesh + -- + + -- Benchmark how long it takes to create the spray mesh + local timestart = SysTime() + + local pos = hitpos + hitnormal -- One unit out + local points = {} + local coords = {} + + -- + -- Calculate spray angle + -- + local tangang = hitnormal:Angle() + tangang:Normalize() + + -- Note to anyone who reads this: + -- I pretty much just fiddled with random values and equations until I got it right. + -- If you're a math person and can understand it, great. + local angToRotateBy = 0 + if tangang.p < 0 then + angToRotateBy = 180 + (anglenormal - tangang).y + elseif tangang.p > 0 then + angToRotateBy = 180 + (tangang - anglenormal).y + end + + tangang:RotateAroundAxis(tangang:Forward(), angToRotateBy) + + -- + -- Calculate spray's mesh coordinates + -- + coordDist = coordDist or spraymesh.COORD_DIST_DEFAULT + + -- Sizing formula to keep the spray the same size (roughly) when mesh resolution changes + coordDist = coordDist * (1 / spraymesh.MESH_RESOLUTION) * 30 + + for ix = 0, spraymesh.MESH_RESOLUTION - 1 do + coords[ix] = {} + + for iy = 0, spraymesh.MESH_RESOLUTION - 1 do + coords[ix][iy] = {} + + local coord = coords[ix][iy] + + --local yawMultiplier = math.abs(tangang.p) / 180 + --tangang.y = math.Remap(yawMultiplier, 0, 1, anglenormal.y, tangang.p) + + coord.pos = pos + (-(tangang:Right() * ix) + (tangang:Up() * iy)) * coordDist + coord.pos = coord.pos + (tangang:Right() * coordDist * spraymesh.MESH_RESOLUTION / 2) - (tangang:Up() * coordDist * spraymesh.MESH_RESOLUTION / 1.8) + + if not (ix == 0 and iy == 0) then + local testtr = util.TraceLine({ + start = coord.pos + hitnormal * 16, + endpos = coord.pos - hitnormal * 16, + filter = function(ent) + if ent:IsWorld() then return true end + end + }) + + if not testtr.Hit or not testtr.HitWorld then + if ix == 0 then + coord.pos = coords[ix][iy - 1].pos + else + coord.pos = coords[ix - 1][iy].pos + end + + coord.bad = true + else + coord.pos = testtr.HitPos + hitnormal + end + end + + coord.u, coord.v = 0, 0 + coord.bitnormal = 1 + coord.tangent = 1 + coord.normal = hitnormal + + -- + -- Calculate vertex color + -- + local lcol = render.ComputeLighting(coord.pos, hitnormal) + render.GetAmbientLightColor() + lcol = lcol * 255 + + local baseBrightness = 60 + + local finalCol = Color(255, 255, 255) + finalCol.r = math.min(lcol.x + baseBrightness, 255) + finalCol.g = math.min(lcol.y + baseBrightness, 255) + finalCol.b = math.min(lcol.z + baseBrightness, 255) + + coord.color = finalCol + end + end + + for ix = 0, spraymesh.MESH_RESOLUTION - 2 do + for iy = 0, spraymesh.MESH_RESOLUTION - 2 do + addSquareToPoints(ix, iy, points, coords) + end + end + + -- Create the actual mesh for the spray + local meshdata = {} + meshdata.mesh = Mesh() + meshdata.mesh:BuildFromTriangles(points) + meshdata.imaterial = generateHTMLTexture(URLToSpray, meshdata) + + -- Remove the existing spray, if any + spraymesh.RemoveSpray(id64) + + -- Put together new spray info table + local sprayInfo = spraymesh.SPRAYDATA[id64] or {} + sprayInfo.meshdata = meshdata + sprayInfo.meshdata.url = URLToSpray + sprayInfo.hitpos = hitpos + sprayInfo.hitnormal = hitnormal + sprayInfo.TraceNormal = tracenormal + sprayInfo.url = url + sprayInfo.PlayerName = nick + sprayInfo.CoordDistance = coordDist + sprayInfo.Time = sprayTime or CurTime() + + spraymesh.SPRAYDATA[id64] = sprayInfo + + spraymesh.DebugPrint("Spray mesh created in: " .. SysTime() - timestart .. "s") +end + +-- Removes all fully loaded sprays +function spraymesh.RemoveSprays() + for k, v in pairs(htmlpanelsanim) do + imats[v.origurl] = nil + v.panel:Remove() + end + + for k, v in pairs(spraymesh.SPRAYDATA) do + if v.meshdata and v.meshdata.mesh then + v.meshdata.mesh:Destroy() + v.meshdata.mesh = nil + end + end + + htmlpanelsanim = {} +end + +-- +-- HTML handlers +-- +-- This is the HTML that prepares the spray to be displayed +-- + +spraymesh.HTMLHandlers = {} + +function spraymesh.HTMLHandlers.Get(url, size, panelcontainer) + -- Remove uniquerequest garbage + url = string.Explode("?", url, false)[1] + + -- Needs redoing for the extension + local sprayType = spraymesh.GetURLInfo(url) + + if sprayType == SPRAYTYPE_IMAGE then + spraymesh.DebugPrint("Using HTMLHandlers.Image for URL: " .. url) + return spraymesh.HTMLHandlers.Image(url, size, panelcontainer) + elseif sprayType == SPRAYTYPE_VIDEO then + spraymesh.DebugPrint("Using HTMLHandlers.Video for URL: " .. url) + return spraymesh.HTMLHandlers.Video(url, size, panelcontainer) + end + + spraymesh.DebugPrint("Using (FALLBACK) HTMLHandlers.Image for URL: " .. url) + + return spraymesh.HTMLHandlers.Image(url, size, panelcontainer) +end + +local SPRAY_HTML_IMAGE = [=[ + + + + + title + + + +
+ + + +]=] + +function spraymesh.HTMLHandlers.Image(url, size, panelcontainer) + local sprayHTML = SPRAY_HTML_IMAGE + sprayHTML = sprayHTML:Replace("{SPRAY_URL}", string.JavascriptSafe(url)) + sprayHTML = sprayHTML:Replace("{SIZE}", string.JavascriptSafe(size)) + --sprayHTML = sprayHTML:Replace("{SPRAY_URL_ANTIGIF}", string.JavascriptSafe(spraymesh.SPRAY_URL_ANTIGIF)) + + panelcontainer.panel:SetHTML(sprayHTML) +end + +local SPRAY_HTML_VIDEO = [=[ + + + + + + + +