Jump to content

[HELP] Circle progress bar

Recommended Posts

Hey everyone,

I require some help with my project: I want to make a circle shaped progress bar, so 100% is a full circle, 50% is half circle... My first attempt was dxDrawCircle, but it's very ugly and requires a lot of resources if I'm not wrong. The second attempt was using an image of a circle + dxDrawImageSection. The only problem is that the section is rectangular and this way it can make it ugly at certain values because it just cuts the circle at values like 21%, 41%, etc. It looks good at 25%, 50%, etc. Is there a way to determine the section to draw in a circular (circle sector) shape? So it wouldn't cut the image horizontally and/or vertically. I made a fast representation about this. https://imgur.com/a/e0Cdwjo

Or is there a way to draw a non-pixelated circle? This whole thing would be easy with dxDrawCircle, but the quality ruins it.

Thank you for your time!

Link to comment

A solution is to use render targets and rotating half circles. It might not be less resource intensive than dxDrawCircle though.

I hope the illustration makes sense 


You need 3 rendertargets (RT1, RT2, RT3) which are textures you can draw onto, instead of the screen. Create these once:

local W, H = 100, 100
local RT1 = dxCreateRenderTarget(W, H, true)
local RT2 = dxCreateRenderTarget(W/2, H, true)
local RT3 = dxCreateRenderTarget(W/2, H, true)

The main texture which you will eventually draw to the screen, is RT1. RT2 and RT3 will be drawn onto RT1. Split your circle image into two images of a semicircle. Both images need to be full size so they rotate around the center.

Do the following "onClientRender"

-- Set and clear RT2
dxSetRenderTarget(RT2, true)
local RT2_rot = 0
dxDrawImage(0, 0, W, H, "semicircle_left.png", RT2_rot)

-- Set and clear RT3
dxSetRenderTarget(RT3, true)
local RT3_rot = 0
dxDrawImage(-W/2, 0, W, H, "semicircle_right.png", RT3_rot)

-- Set and clear RT1
dxSetRenderTarget(RT1, true)
-- Draw rotated circles onto RT1
dxDrawImage(0, 0, W/2, H, RT2)
dxDrawImage(W/2, 0, W/2, H, RT3)
-- Draw to screen
dxDrawImage(100, 100, W, H, RT1)

Play around with RT2_rot and RT3_rot to make the loading bar work as you want.

Edited by Zango
  • Thanks 1
Link to comment

There is another solution that doesn't require render targets, and it's closer to dxDrawImageSection in the way it works: dxDrawMaterialPrimitive. dxDrawImageSection only operates on rectangular sections. dxDrawMaterialPrimitive allows you to draw triangles, specifying the texture coordinates for each vertex, and since triangles can be put together to form other shapes, you can do what dxDrawImageSection does but not limited to rectangular sections.

There isn't an example in the wiki page on how to use it, but dxDrawPrimitive has one, and dxDrawMaterialPrimitive works in a similar way, only it takes image as second argument, and each vertex has 5 parameters instead of 3 (2 extra parameters are for image coordinates).

I came up with some function, for drawing a radially cut out section of an image. I only tested it as much as I could test it in standalone Lua interpreter so I don't know if it works in MTA, but if it does, someone may put it on useful functions page in wiki 😁 It uses trianglefan primitive type, puts the first vertex in the center and other vertices around it.

local white = tocolor(255, 255, 255, 255)
local degToRad = math.pi/180

local function makeVertexAtAngle(centerX, centerY, halfWidth, halfHeight, angle, color)
    local angleRad = angle*degToRad
    local xAdd, yAdd = math.sin(angleRad), -math.cos(angleRad)
    local maxAdd = math.max(math.abs(xAdd), math.abs(yAdd))
    xAdd, yAdd = xAdd/maxAdd, yAdd/maxAdd

    return {
        centerX+xAdd*halfWidth, centerY+yAdd*halfHeight,
        0.5+xAdd*0.5, 0.5+yAdd*0.5

function dxDrawRadialImageSection(posX, posY, width, height, image, startAngle, stopAngle, color, postGUI)
    if color == nil then
        color = white
    if postGUI == nil then
        postGUI = false

    local halfWidth, halfHeight = width*0.5, height*0.5
    local centerX, centerY = posX+halfWidth, posY+halfHeight

    local roundedStartAngle = math.floor((startAngle-45)/90+1)*90+45
    local roundedStopAngle = math.ceil((stopAngle-45)/90-1)*90+45

    local vertices = {{centerX, centerY, color, 0.5, 0.5}}

    table.insert(vertices, makeVertexAtAngle(centerX, centerY, halfWidth, halfHeight, startAngle, color))
    for angle = roundedStartAngle, roundedStopAngle, 90 do
        table.insert(vertices, makeVertexAtAngle(centerX, centerY, halfWidth, halfHeight, angle, color))
    table.insert(vertices, makeVertexAtAngle(centerX, centerY, halfWidth, halfHeight, stopAngle, color))

    dxDrawMaterialPrimitive("trianglefan", image, postGUI, unpack(vertices))

This example should display a looping 5-second animation of image going from 0 to 360 (if I didn't screw anything up):

function drawAnimatedRadialSection()
    local angle = (getTickCount() % 5000) / 5000 * 360
    dxDrawRadialImageSection(100, 100, 200, 200, "your_image.png", 0, angle)
addEventHandler("onClientRender", root, drawAnimatedRadialSection)


Edited by CrystalMV
  • Like 1
  • Thanks 1
Link to comment
7 hours ago, Geralt said:

I used dxSetBlendMode to fix the quality, and it worked, but if it's rotated... right side is not rotated, left side is and it became ugly again. How can I fix this?

For reference, following the wiki explanation on dxSetBlendMode should fix the quality ("modulate_add" when drawing render target, "add" when drawing to screen)

Between these two, I would pick CrystalMV's solution, using a trianglefan for this is neat.

  • Thanks 1
Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    No registered users viewing this page.

  • Create New...