Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions gamemodes/terrortown/gamemode/shared/sh_init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
3 changes: 3 additions & 0 deletions lua/ttt2/extensions/draw.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
12 changes: 12 additions & 0 deletions lua/ttt2/extensions/util.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
6 changes: 5 additions & 1 deletion lua/ttt2/libraries/roles.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
193 changes: 193 additions & 0 deletions lua/ttt2/libraries/svg.lua
Original file line number Diff line number Diff line change
@@ -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 = [[
<html>
<head>
<style>
body {
margin: 0;
padding: %dpx;
overflow: hidden;
background-color: rgba(255,255,255,0);
}
</style>
</head>
<body>
%s
</body>
</html>
]]

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, "<svg%s(.-)>")
local _, close = stringFind(strSVG, "</svg>%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
7 changes: 7 additions & 0 deletions materials/test.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions materials/test2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions materials/test3.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions materials/vgui/ttt/dynamic/roles/icon_inno.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.