diff --git a/gamemodes/terrortown/gamemode/shared/sh_init.lua b/gamemodes/terrortown/gamemode/shared/sh_init.lua index b5fa6c803e..bef1b0e08f 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_init.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_init.lua @@ -550,6 +550,7 @@ include("ttt2/extensions/input.lua") include("ttt2/extensions/cvars.lua") -- include libraries +include("ttt2/libraries/svg.lua") include("ttt2/libraries/huds.lua") include("ttt2/libraries/hudelements.lua") include("ttt2/libraries/items.lua") diff --git a/lua/ttt2/extensions/draw.lua b/lua/ttt2/extensions/draw.lua index 1b0437abc0..261507d88d 100644 --- a/lua/ttt2/extensions/draw.lua +++ b/lua/ttt2/extensions/draw.lua @@ -248,6 +248,9 @@ function draw.Texture(x, y, w, h, material, alpha, color) alpha = alpha or 255 color = color or COLOR_WHITE + -- handle custom mipmaps, returns material if not set + material = svg.GetCustomMipmap(material, h) + surface.SetDrawColor(color.r, color.g, color.b, alpha) surface.SetMaterial(material) diff --git a/lua/ttt2/extensions/util.lua b/lua/ttt2/extensions/util.lua index d6d8b9fa8e..44b224af89 100644 --- a/lua/ttt2/extensions/util.lua +++ b/lua/ttt2/extensions/util.lua @@ -483,6 +483,18 @@ function util.VectorInBounds(vec, lowerBound, upperBound) and vec.z > lowerBound.z and vec.z < upperBound.z end +function util.NextPowerOfTwo(value) + value = value - 1 + + value = bit.bor(value, bit.rshift(value, 1)) + value = bit.bor(value, bit.rshift(value, 2)) + value = bit.bor(value, bit.rshift(value, 4)) + value = bit.bor(value, bit.rshift(value, 8)) + value = bit.bor(value, bit.rshift(value, 16)) + + return value + 1 +end + if CLIENT then local colorsHealth = { healthy = Color(0, 255, 0, 255), diff --git a/lua/ttt2/libraries/roles.lua b/lua/ttt2/libraries/roles.lua index 2d12756b07..80895f8144 100644 --- a/lua/ttt2/libraries/roles.lua +++ b/lua/ttt2/libraries/roles.lua @@ -161,7 +161,11 @@ local function SetupData(roleData) roleData.icon = roleData.icon or ("vgui/ttt/dynamic/roles/icon_" .. roleData.abbr) -- set a roledata icon material to prevent creating new materials each frame - roleData.iconMaterial = Material(roleData.icon) + if svg.FileExists(roleData.icon) then + roleData.iconMaterial = svg.CreateSVGMaterial(roleData.icon, 512, 512, 32) + else + roleData.iconMaterial = Material(roleData.icon) + end -- set default colors roleData.dkcolor = util.ColorDarken(roleData.color, 30) diff --git a/lua/ttt2/libraries/svg.lua b/lua/ttt2/libraries/svg.lua new file mode 100644 index 0000000000..da77ddbc3e --- /dev/null +++ b/lua/ttt2/libraries/svg.lua @@ -0,0 +1,193 @@ +--- +-- svg library functions +-- Adds the possibility to render svg files as normal materials +-- @author Mineotopia +-- @author noaccessl +-- @module svg + +if SERVER then + AddCSLuaFile() + + return +end + +local stringFormat = string.format +local stringSub = string.sub +local stringGSub = string.gsub +local stringLen = string.len +local stringFind = string.find +local fileRead = file.Read + +local svgTemplate = [[ + + + + + + %s + + +]] + +local materialAttributes = { + ["$nodecal"] = 1, + ["$nolod"] = 1, + ["$smooth"] = 1, + ["$translucent"] = 1, + ["$vertexalpha"] = 1, + ["$vertexcolor"] = 1 +} + +local mipmapSizes = { + [8] = true, + [16] = true, + [32] = true, + [64] = true, + [128] = true, + [256] = true, + [512] = true, + [1024] = true +} + +local function SetIfEmpty(haystack, needle, pos, needed) + if not stringFind(haystack, needle) then + return stringSub(haystack, 1, pos) .. needed .. stringSub(haystack, pos + stringLen(needed)) + end + + return haystack +end + +local function SetupMaterial(name, width, height) + -- set individual material attributes + materialAttributes["$basetexture"] = name + + return CreateMaterial(name, "UnlitGeneric", materialAttributes) +end + +local function GenerateHTMLElement(width, height, padding, strSVG) + -- make sure svg file has opening and closing tag + local open = stringFind(strSVG, "") + local _, close = stringFind(strSVG, "%s*$") + + if not open or not close then return end + + strSVG = stringSub(strSVG, open, close) + + -- todo make sure that the svg size in combination witht the padding works here + strSVG = SetIfEmpty(strSVG, "width='(.-)'", 5, "width='' ") + strSVG = SetIfEmpty(strSVG, "height='(.-)'", 5, "height='' ") + + strSVG = stringGSub(strSVG, "width='(.-)'", "width='" .. width - 2 * padding .. "'") + strSVG = stringGSub(strSVG, "height='(.-)'", "height='" .. height - 2 * padding .. "'") + + local htmlElement = vgui.Create("DHTML") + htmlElement:SetVisible(false) + htmlElement:SetSize(width, height) + htmlElement:SetHTML(stringFormat(svgTemplate, padding, strSVG)) + + return htmlElement +end + +local function GenerateHTMLMaterial(width, height, padding, strSVG) + width = math.floor(width) + height = math.floor(height) + + local htmlElement = GenerateHTMLElement(width, height, padding, strSVG) + + if not htmlElement then return end + + -- the HTML element texture has to be updated once to generate a material + htmlElement:UpdateHTMLTexture() + + -- then the material can be extracted from the HTML element + local materialInternal = htmlElement:GetHTMLMaterial() + local material = SetupMaterial(materialInternal:GetName(), width, height) + + --htmlElement:Remove() + + return material +end + +svg = svg or {} +svg.customMipmaps = svg.customMipmaps or {} + +--- +-- Creates a material from an SVG file that can be used as any other material in GMod. Since normal +-- materials are created from pixelated sources instead of vectorized sources, a basewidth has to be +-- provided. +-- @param string path The filepath to the svg file +-- @param[default=64] number width The base width of the generated material +-- @param[default=64] number height The base height of the generated material +-- @param[default=0] number padding The padding around the material, is included in the set width and height +-- @param[default=true] boolean mipmapping Set to false to disable mipmapping for this material +-- @return nil|Material Returns the created material, nil if failed +-- @note This function is rather compute heavy and it should be therefore avoided to call +-- it from within a rendering hook. Caching of the returned matial is recommended. +-- @realm client +function svg.CreateSVGMaterial(path, width, height, padding, mipmapping) + width = width or 64 + height = height or 64 + padding = padding or 0 + + local strSVG = fileRead("materials/" .. path .. ".svg", "GAME") + + if not strSVG then return end + + -- generate base material + local material = GenerateHTMLMaterial(width, height, padding, strSVG) + local name = material:GetName() + + svg.InitializeTable(name) + + -- if not explicitly disabled, mipmaps should be generated as well + if mipmapping ~= false then + for size in pairs(mipmapSizes) do + if size >= height then continue end + + local mult = size / height + + svg.AddCustomMipmap(name, size, GenerateHTMLMaterial(width * mult, size, padding * mult, strSVG)) + end + end + + return material +end + +function svg.InitializeTable(name) + svg.customMipmaps[name] = svg.customMipmaps[name] or {} +end + +function svg.AddCustomMipmap(name, height, material) + svg.customMipmaps[name][height] = material +end + +function svg.GetCustomMipmap(material, height) + local name = material:GetName() + + if not svg.IsSVGMaterial(name) then + return material + end + + local nextSize = util.NextPowerOfTwo(height) + + if not svg.customMipmaps[name][nextSize] then + return material + end + + return svg.customMipmaps[name][nextSize] +end + +function svg.FileExists(path) + return file.Exists("materials/" .. path .. ".svg", "GAME") +end + +function svg.IsSVGMaterial(name) + return svg.customMipmaps[name] ~= nil +end diff --git a/materials/test.svg b/materials/test.svg new file mode 100644 index 0000000000..6a12333e39 --- /dev/null +++ b/materials/test.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/materials/test2.svg b/materials/test2.svg new file mode 100644 index 0000000000..fd921ff63b --- /dev/null +++ b/materials/test2.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/materials/test3.svg b/materials/test3.svg new file mode 100644 index 0000000000..40671df7eb --- /dev/null +++ b/materials/test3.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/materials/vgui/ttt/dynamic/roles/icon_inno.svg b/materials/vgui/ttt/dynamic/roles/icon_inno.svg new file mode 100644 index 0000000000..b6073a001a --- /dev/null +++ b/materials/vgui/ttt/dynamic/roles/icon_inno.svg @@ -0,0 +1,10 @@ + + + + + + + + + +