Drawing circular meter?

Hi,

I am trying to create a circular meter that can drain over time, etc. I’ve created a version using lots of small rectangles created with surface.DrawTexturedRectRotated, but it is too grainy.

Someone might suggest stencils as an alternative way to do this, but I know nothing about stencils or if they can be used on 2D ui elements. Some guidance would be appreciated. Anyway, here is the grainy version I’m currently doing using rectangles along with the code (drains as the player holds SHIFT)

``````

local iply = LocalPlayer()

function circlexy(ang, radius, offx, offy, linewidth)

ang = ang - 90
local x = math.cos( ang ) * (radius - linewidth) + offx
local y = math.sin( ang ) * (radius - linewidth) + offy

return x, y;
end

function DrawPartialCircle( x, y, radius, linewidth, startangle, endangle, aa )

-- Thanks for getting me started on how to do this python1320 <3
aa = aa or 1;
startangle = math.Clamp( startangle or 0, -360, 360 );
endangle = math.Clamp( endangle or 360, -90, 360 );

draw.NoTexture()
surface.SetDrawColor( color or color_white )

if endangle < startangle then
local temp = endangle;
endangle = startangle;
startangle = temp;
end

for i=1, endangle, aa do

local x1, y1 = circlexy( i, radius, x, y, linewidth )
surface.DrawTexturedRectRotated( x1, y1, linewidth, aa*20, -i );
end
end

local factor = ( 360 / 100 )
local plystamina = ( (plystamina or 100) * factor )

local sx = ScrW() * ( 9 / 10 )
local sy = ScrH() * ( 9 / 10 )

timer.Create( "StamDrain", 0.01, 0, function()

if ( input.IsKeyDown( KEY_LSHIFT ) ) then
plystamina = ( plystamina - 1 )
if plystamina == 0 then
plystamina = 100 * factor
end

end

end )

DrawPartialCircle( sx, sy, 50, 1, 0, plystamina, 0.5 )

end )

``````

Well, I never saw that someone was using a rotated rectangles for this, this is an overkill to performance, people usually use surface.DrawPoly to draw each segment of circle bar. Some time long ago I wrote an optimized rectangular version that was meant to be used with textures and draws up to 5 triangles only, that’s what you can see in many games. I think I can find this code a bit later.

UPDATE: So, the code is pretty complex, but it should work with textures

``````local function DrawRadialRect(x, y, width, height, fraction, fractionOffset, counterClockWise)
fraction = math.Clamp(fraction, 0, 1)
fractionOffset = math.Clamp(fractionOffset or 0, 0, 1) - 0.25
counterClockWise = counterClockWise or false

if (fraction == 1) then
surface.DrawTexturedRect(x, y, width, height)
return
end
if (fraction == 0) then
return
end
local startAngle = 2 * math.pi * ((fractionOffset + 0.5) % 1 - 0.5)
local endAngle = counterClockWise
and 2 * math.pi * ((fractionOffset - fraction + 0.5) % 1 - 0.5)
or 2 * math.pi * ((fractionOffset + fraction + 0.5) % 1 - 0.5)
local cornerAngleOffset = 0.5 * math.pi - math.atan(width / height)
local cornerAngles = {
cornerAngleOffset,
math.pi - cornerAngleOffset,
math.pi + cornerAngleOffset,
2 * math.pi - cornerAngleOffset
}
local vertices = {}
local deltaAngle = counterClockWise
and (startAngle - endAngle) % (2 * math.pi)
or (endAngle - startAngle) % (2 * math.pi)
local startVertexIndex = 0
local vertexIndex = 0
local maxDeltaCornerAngle = 0
for i = 1, 4 do
local deltaCornerAngle  = counterClockWise
and deltaAngle - (cornerAngles[i] + startAngle) % (2 * math.pi)
or deltaAngle - (cornerAngles[i] - startAngle) % (2 * math.pi)
if (deltaCornerAngle > 0) then
vertexIndex = vertexIndex + 1
local coordinateIndex = counterClockWise and 5 - i or i
local coordinateU = math.floor((2 + coordinateIndex) / 2 % 2)
local coordinateV = math.floor((1 + coordinateIndex) / 2 % 2)
vertices[vertexIndex] = {
x = x + width * coordinateU,
y = y + height * coordinateV,
u = coordinateU,
v = coordinateV
}
if (deltaCornerAngle > maxDeltaCornerAngle) then
maxDeltaCornerAngle = deltaCornerAngle
startVertexIndex = vertexIndex
end
end
end
local indices = { 1, 2, 3, 4, 5 }
for i = 1, vertexIndex do
indices[i] = (i + startVertexIndex - 2) % vertexIndex + 1
end
local centerX = x + 0.5 * width
local centerY = y + 0.5 * height
local startAngleRectFactor = math.max(
math.abs(math.cos(startAngle)) * 2 / width,
math.abs(math.sin(startAngle)) * 2 / height
)
local startAngleOffsetX = math.cos(startAngle) / startAngleRectFactor
local startAngleOffsetY = math.sin(startAngle) / startAngleRectFactor
local endAngleRectFactor = math.max(
math.abs(math.cos(endAngle)) * 2 / width,
math.abs(math.sin(endAngle)) * 2 / height
)
local endAngleOffsetX  = math.cos(endAngle) / endAngleRectFactor
local endAngleOffsetY  = math.sin(endAngle) / endAngleRectFactor
vertices[vertexIndex + 1] = {
x = centerX + endAngleOffsetX,
y = centerY + endAngleOffsetY,
u = 0.5 + 0.5 * endAngleOffsetX * 2 / width,
v = 0.5 + 0.5 * endAngleOffsetY * 2 / height
}
local centerVertex = {
x = centerX,
y = centerY,
u = 0.5,
v = 0.5
}
local edgeVertex = {
x = centerX + startAngleOffsetX,
y = centerY + startAngleOffsetY,
u = 0.5 + 0.5 * startAngleOffsetX * 2 / width,
v = 0.5 + 0.5 * startAngleOffsetY * 2 / height
}
for i = 1, vertexIndex + 1 do
local cornerVertex = vertices[indices[i]]
if (counterClockWise) then
surface.DrawPoly({ cornerVertex, edgeVertex, centerVertex })
else
surface.DrawPoly({ centerVertex, edgeVertex, cornerVertex })
end
edgeVertex = cornerVertex
end
end

-- example
local textureId = surface.GetTextureID("effects/wheel_ring")
surface.SetDrawColor(color_white)
surface.SetTexture(textureId)
DrawRadialRect(64, 64, 128, 128, (CurTime() * 0.25) % 1)
end)
``````

Thank you VERY much for this.

I will post a quick update here with some issues I’m having- maybe you or someone else will know what to do.

1.) I’m trying this on a custom vtf/vmt I made in VTFEdit, but it appears to be glitching with the render order of the world or something unless I’m looking at certain parts of the map (usually up or towards the skybox)
The intended look is for the circle to be white. Here is non-working vs working (when looking up):

Glitch occurring:

Glitch not occurring:

2.) My second problem is that drawing the rect in dimensions other than the dimensions of the original vtf causes some pixelation (ex: drawing a vtf that is 128x128 in a rectangle that is 64x64). Is there any file format that will allow VTFs to be resized without pixelation?

Thank you all

I can’t tell much without code, show me the .vmt file and also how you draw and apply this material. Generally UI materials are meant to derive from UnlitGeneric shader or something other that doesn’t account for lighting.

I didn’t quite get what are you trying to do, doesn’t making texture the same size as rectangle solving your problem? Then you might try enabling mipmap generation for .vtf.

Changing the VMT to UnlitGeneric fixed it for me. Thank you for that.

As for rescaling:
I am basically using your provided code to draw the rectangle.
Creating it same size would solve the problem, but I want my gamemode UI to scale up/down for different screen sizes and thus I’d prefer to only have to make one vtf and have it resizable.

Update:
However I’ll note that it’s actually not so bad right now- seems like it rescales decently. If there’s some way to make it even better that’d be cool but this is not as urgent of an issue anymore.