Elitist Jerks
Register
Blogs
Forums


Go Back   Elitist Jerks » User Interface and AddOns

Reply
 
LinkBack Thread Tools
Old 02/03/09, 9:02 AM   #1
zork
Don Flamenco
 
zork's Avatar
 
Dwarf Warrior
 
Eredar (EU)
[Tutorial] 360° Ring

*update* Made a mod out of it.
TheRingThing : WoWInterface Downloads : Unit Mods

I wrote a tutorial about how to do 360° rings in World of Warcraft based on Iriels findings in his addon StatRings.

Explaining images:

ring_basic.gif http://img3.abload.de/img/ring_basicql5z.gif


Sources:

API Texture SetTexCoord - WoWWiki - Your guide to the World of Warcraft
SetTexCoord Transformations - WoWWiki - Your guide to the World of Warcraft
StatRings - Anchor configuration preview - WoWInterface
StatRings : WoWInterface Downloads : Iriel's Castle

Link to original article:

360° Ring Castbar

What's important?

A ring consists of 4 segments. If you want this ring to be your castbar (for example) it will always have a status. "Nothing is casted", "Cast finished", "Cast finished to 10%" ... stuff like that.

If you want to use 4 segments (less are possible too) each segment is 25% of your castbar (25%*4=100%).

To create that ring you only need the texture of 1 segment of that ring and a slice texture. One texture of that segment is enough since textures can be rotated.

If we assume our castbar is at 40% then segment-one would be completly visible. Thus no special mathematics are needed. Just display the texture and thats it. Only the segment where the castbar currently is, has to be looked at.

Ok, now to keep it simple I assume my castbar is at ~10% and it fills clock-wise. Thus only segment-one (the upper right in my case) is visible and everything else is unvisible.

To get that radial look we have to cheat. This segment will consist of 3 textures. Two of these will be the ring and one will be the slice.

To understand how this is possible you need to know how the alpha-layer of TGA-texture files work. The alpha layer describes what part of a texture will be visible and what will be not visible. Ok, we need to create (in GIMP or Photoshop) two of these textures. A ring textures which is one segment of that ring and will be rotated later and a slice which is a square that consists of two triangles, one visible and one unvisible.

If our segment would be fully visible we would just show the texture (maybe rotation is needed) but thats it.

ring_square.gif http://img3.abload.de/img/ring_squaresjd6v.gif


In this case we are looking at a segment the castbar currently is at. If you check the images (ring_square.gif) you see a red and a blue rectangle and a yellow triangle (the slice).

Ok here comes SetTexCoord() into play. This thing is great. Not only can you rotate textures with it, the more important thing is that you can describe rectangles that lay on the texture. Those rectangles behave like alpha channels. Only parts of that texture that are inside the rectangle will be visible.

This plus the ring texture with the alpha layer makes it possible to create the illusion of a ring.

Now as you can see on the images, when we have the red and the blue rectangle there is a gap. This is where the slice comes into play. It will be spanned between those two rectangles and thanks to the alpha layer on that texture only one part of it will be visible.

Ok, lets do some math.

We assume castbar filled to ~10%. So thats easy, 25% is one segment and one segment is 90°.

So:
25/90 = 10/x --> x = (10*90)/25 --> x = 36°

This angle is important. Its the angle of the line that clips the ring. When the line clips the ring it produces two intersection points (point I and point O).

Ok here are the functions to calculate the coordinates:

outer_radius = texture_width
inner_radius = outer_radius - ring_width
Arad = math.rad(angle)
Ix = inner_radius * math.sin(Arad)
Iy = outer_radius - (inner_radius * math.cos(Arad))
Ox = outer_radius * math.sin(Arad)
Oy = outer_radius - (outer_radius * math.cos(Arad))
The starting point is the upper left corner (point N). This is always (0,0).

The standard definition of SetTexCoord is: SetTexCoord(ULx, ULy, LLx, LLy, URx, URy, LRx, LRy);

UL = upper left
LL = lower left
UR = upper right
LR = lower right

ring_textures.gif http://img3.abload.de/img/ring_texturesvkg0.gif


Ok check the ring_square image again. We are creating two ring segment textures (in LUA) with the following coordinates:

Red square: SetTexCoord(Nx,Ny, Nx,IyCoord, IxCoord,Ny, IxCoord, IyCoord)
Blue square: SetTexCoord(IxCoord,Ny, IxCoord,OyCoord, OxCoord,Ny, OxCoord, OyCoord)

Now we create another texture that contains the slice and move it into position:

SetPoint("TOPLEFT",Ix,-Oy)
SetWidth(Ox-Ix)
SetHeight(Iy-Oy)

This is the basic stuff. Now lets move on to rotations which are not that complicated if you make yourself a little helper. Get yourself a sheet of paper (in real life!) and cut out a square. Now mark the edges of that square. Upper left = UL, lower left = LL, upper right = UR and lower right = LR.

Ok now lets assume we want to rotate that thing 90° to the right. Now get your square and rotate it 90° and see what happened. All points have moved clockwise to the right...right?!

UL => UR
LL => UL
UR => LR
LR => LL

So:
SetTexCoord(ULx, ULy, LLx, LLy, URx, URy, LRx, LRy) =>
SetTexCoord(LLx, LLy, LRx, LRy, ULx, ULy, URx, URy)

This is how you rotate the texture 90°. You can use the same technique for 180° and 270°.

Thats the basic explanation on how to create a ring that moves clock-wise. If you want to get a ring that moves counter-clock-wise that's possible too. Check the ring reverse graphic. Your starting point changed to M (1,1). What you need is a new slice texture or you create a 2in1 texture that contains both sides of the slice and must be adjusted with SetTexCoord aswell.

ring_reverse.gif http://img3.abload.de/img/ring_reverseclfl.gif


Still reading?

Here is a small example addon, don't forget to create your ring and slice textures and replace them accordingly:
  
  local addon = CreateFrame("Frame", nil, UIParent)
 
  addon:RegisterEvent("PLAYER_LOGIN")
  
  addon:SetScript("OnEvent", function ()
    if(event=="PLAYER_LOGIN") then
      addon:initme(1,"BOTTOMLEFT")
      addon:initme(2,"TOPLEFT")
      addon:initme(3,"TOPRIGHT")
      addon:initme(4,"BOTTOMRIGHT")
    end 
  end)

  function addon:initme(id,anchor)
  
    --outer ring
    local outer_radius = 128
    
    --inner ring
    local ring_factor = 1.12280702
    local inner_radius = outer_radius/ring_factor
  
    local f = CreateFrame("Frame", nil,UIParent)
    f:SetFrameStrata("BACKGROUND")
    f:SetWidth(outer_radius)
    f:SetHeight(outer_radius)
    f:SetPoint(anchor,UIParent,"CENTER",0,0)
    f:Show()
    
    local statusbarvalue = 50
    
    --angle
    local angle = statusbarvalue * 90 / 100
    local Arad = math.rad(angle)
    
    local ULx,ULy, LLx,LLy, URx,URy, LRx,LRy

    local Nx = 0
    local Ny = 0
    local Ix = inner_radius * math.sin(Arad)
    local Iy = outer_radius - (inner_radius * math.cos(Arad))
    local Ox = outer_radius * math.sin(Arad)
    local Oy = outer_radius - (outer_radius * math.cos(Arad))
    
    local IxCoord = Ix / outer_radius 
    local IyCoord = Iy / outer_radius
    local OxCoord = Ox / outer_radius
    local OyCoord = Oy / outer_radius    

    if id == 1 then
      
      local t0 = f:CreateTexture(nil, "BACKGROUND")
      t0:SetTexture("Interface\\AddOns\\StatRingsRevived\\ring1")
      t0:SetPoint("TOPLEFT",Nx,Ny)
      t0:SetWidth(Ix)
      t0:SetHeight(Iy)
      t0:SetTexCoord(Nx,Ny, Nx,IyCoord, IxCoord,Ny, IxCoord, IyCoord)
      
      local t1 = f:CreateTexture(nil, "BACKGROUND")
      t1:SetTexture("Interface\\AddOns\\StatRingsRevived\\ring1")
      t1:SetPoint("TOPLEFT",Ix,Ny)
      t1:SetWidth(Ox-Ix)
      t1:SetHeight(Oy)
      t1:SetTexCoord(IxCoord,Ny, IxCoord,OyCoord, OxCoord,Ny, OxCoord, OyCoord)
      
      local t2 = f:CreateTexture(nil, "BACKGROUND")
      t2:SetTexture("Interface\\AddOns\\StatRingsRevived\\slice1")
      t2:SetPoint("TOPLEFT",Ix,-Oy)
      t2:SetWidth(Ox-Ix)
      t2:SetHeight(Iy-Oy)

      local t9 = f:CreateTexture(nil, "BACKGROUND")
      t9:SetTexture("Interface\\AddOns\\StatRingsRevived\\ring1")
      t9:SetPoint("CENTER",0,0)
      t9:SetWidth(outer_radius)
      t9:SetHeight(outer_radius)
      t9:SetAlpha(0.2)

      DEFAULT_CHAT_FRAME:AddMessage("angle "..angle)
      DEFAULT_CHAT_FRAME:AddMessage("Arad "..Arad)
      DEFAULT_CHAT_FRAME:AddMessage("Ix "..Ix)
      DEFAULT_CHAT_FRAME:AddMessage("Iy "..Iy)
      DEFAULT_CHAT_FRAME:AddMessage("Ox "..Ox)
      DEFAULT_CHAT_FRAME:AddMessage("Oy "..Oy)
      DEFAULT_CHAT_FRAME:AddMessage("IxCoord "..IxCoord)
      DEFAULT_CHAT_FRAME:AddMessage("IyCoord "..IyCoord)
      DEFAULT_CHAT_FRAME:AddMessage("OxCoord "..OxCoord)
      DEFAULT_CHAT_FRAME:AddMessage("OyCoord "..OyCoord)
      DEFAULT_CHAT_FRAME:AddMessage("BOTTOMRIGHTX "..(outer_radius-Ix))
      DEFAULT_CHAT_FRAME:AddMessage("BOTTOMRIGHTY "..(outer_radius-Iy))

    end

    if id == 2 then
      local t0 = f:CreateTexture(nil, "BACKGROUND")
      t0:SetTexture("Interface\\AddOns\\StatRingsRevived\\ring1")
      t0:SetPoint("TOPRIGHT",0,0)
      t0:SetWidth(Iy)
      t0:SetHeight(Ix)
      --t0:SetTexCoord(0,1, 1,1, 0,0, 1,0)
      t0:SetTexCoord(Nx,IyCoord, IxCoord,IyCoord, Nx,Ny, IxCoord,Ny)
      
      local t1 = f:CreateTexture(nil, "BACKGROUND")
      t1:SetTexture("Interface\\AddOns\\StatRingsRevived\\ring1")
      t1:SetPoint("TOPRIGHT",Ny,-Ix)
      t1:SetWidth(Oy)
      t1:SetHeight(Ox-Ix)
      --t1:SetTexCoord(IxCoord,Ny, IxCoord,OyCoord, OxCoord,Ny, OxCoord, OyCoord)
      t1:SetTexCoord(IxCoord,OyCoord,  OxCoord, OyCoord,  IxCoord,Ny,  OxCoord,Ny)
      
      local t2 = f:CreateTexture(nil, "BACKGROUND")
      t2:SetTexture("Interface\\AddOns\\StatRingsRevived\\slice1")
      t2:SetPoint("TOPRIGHT",-Oy,-Ix)
      t2:SetWidth(Iy-Oy)
      t2:SetHeight(Ox-Ix)
      t2:SetTexCoord(0,1, 1,1, 0,0, 1,0)

      local t9 = f:CreateTexture(nil, "BACKGROUND")
      t9:SetTexture("Interface\\AddOns\\StatRingsRevived\\ring1")
      t9:SetPoint("CENTER",0,0)
      t9:SetWidth(outer_radius)
      t9:SetHeight(outer_radius)
      t9:SetTexCoord(0,1, 1,1, 0,0, 1,0)
      t9:SetAlpha(0.2)

    end

    if id == 3 then
      
      local t0 = f:CreateTexture(nil, "BACKGROUND")
      t0:SetTexture("Interface\\AddOns\\StatRingsRevived\\ring1")
      t0:SetPoint("BOTTOMRIGHT",Nx,Ny)
      t0:SetWidth(Ix)
      t0:SetHeight(Iy)
      --t0:SetTexCoord(Nx,Ny, Nx,IyCoord, IxCoord,Ny, IxCoord, IyCoord)
      t0:SetTexCoord(IxCoord, IyCoord, IxCoord,Ny, Nx,IyCoord, Nx,Ny)
      
      local t1 = f:CreateTexture(nil, "BACKGROUND")
      t1:SetTexture("Interface\\AddOns\\StatRingsRevived\\ring1")
      t1:SetPoint("BOTTOMRIGHT",-Ix,Ny)
      t1:SetWidth(Ox-Ix)
      t1:SetHeight(Oy)
      t1:SetTexCoord(OxCoord, OyCoord, OxCoord,Ny, IxCoord,OyCoord, IxCoord,Ny)
      
      local t2 = f:CreateTexture(nil, "BACKGROUND")
      t2:SetTexture("Interface\\AddOns\\StatRingsRevived\\slice1")
      t2:SetPoint("BOTTOMRIGHT",-Ix,Oy)
      t2:SetWidth(Ox-Ix)
      t2:SetHeight(Iy-Oy)
      t2:SetTexCoord(1,1, 1,0, 0,1, 0,0)
      
      local t9 = f:CreateTexture(nil, "BACKGROUND")
      t9:SetTexture("Interface\\AddOns\\StatRingsRevived\\ring1")
      t9:SetPoint("CENTER",0,0)
      t9:SetWidth(outer_radius)
      t9:SetHeight(outer_radius)
      t9:SetTexCoord(1,1, 1,0, 0,1, 0,0)
      t9:SetAlpha(0.2)

    end


    if id == 4 then
      local t0 = f:CreateTexture(nil, "BACKGROUND")
      t0:SetTexture("Interface\\AddOns\\StatRingsRevived\\ring1")
      t0:SetPoint("BOTTOMLEFT",0,0)
      t0:SetWidth(Iy)
      t0:SetHeight(Ix)
      t0:SetTexCoord(IxCoord,Ny, Nx,Ny, IxCoord, IyCoord, Nx,IyCoord)
      
      local t1 = f:CreateTexture(nil, "BACKGROUND")
      t1:SetTexture("Interface\\AddOns\\StatRingsRevived\\ring1")
      t1:SetPoint("BOTTOMLEFT",Ny,Ix)
      t1:SetWidth(Oy)
      t1:SetHeight(Ox-Ix)
      t1:SetTexCoord(OxCoord,Ny, IxCoord,Ny, OxCoord, OyCoord, IxCoord,OyCoord)
      
      local t2 = f:CreateTexture(nil, "BACKGROUND")
      t2:SetTexture("Interface\\AddOns\\StatRingsRevived\\slice1")
      t2:SetPoint("BOTTOMLEFT",Oy,Ix)
      t2:SetWidth(Iy-Oy)
      t2:SetHeight(Ox-Ix)
      t2:SetTexCoord(1,0, 0,0, 1,1, 0,1)
                 
      local t9 = f:CreateTexture(nil, "BACKGROUND")
      t9:SetTexture("Interface\\AddOns\\StatRingsRevived\\ring1")
      t9:SetPoint("CENTER",0,0)
      t9:SetWidth(outer_radius)
      t9:SetHeight(outer_radius)
      t9:SetTexCoord(1,0, 0,0, 1,1, 0,1)
      t9:SetAlpha(0.2)

    end    


  end
There is another more graphical way to approach the 360° ring. Since Wrath of the Lich King 2048x2048 px textures are allowed. This is huge! Remember what SetTexCoord() can do for use? It can behave like an alpha-channel only making stuff visible we want to look at. Now think of a 2048x2048px texture that is split in 8 colums and 8 rows each 256x256px in size. Thats a total number of 64 possible textures or 16 texturer per segment! Dugu does this with his CursorCastbar and SpartanUI does this for the bar animation aswell. A neat way to create some sort of animation.

Last edited by zork : 07/24/09 at 6:37 AM.


Offline
Reply With Quote
Old 02/03/09, 2:22 PM   #2
dr_AllCOM3
Great Tiger
 
dr_AllCOM3's Avatar
 
Orc Death Knight
 
Blutkessel (EU)
Massive wall of math!

I could imagine a nice rune addon using rings. In fact, I have a very concrete idea. Maybe I give it a try. Thanks for the tutorial.


Offline
Reply With Quote
Old 07/24/09, 6:28 AM   #3
zork
Don Flamenco
 
zork's Avatar
 
Dwarf Warrior
 
Eredar (EU)
To bring this back to life. I finally found the time to finish my thoughts on this topic and to create a first mod out of it.

Mod:
TheRingThing : WoWInterface Downloads : Unit Mods

Documentation:
http://rothui.googlecode.com/svn/tru...umentation.txt

Maybe the mod finds some who are dedicated enough and who are willing to create some mods out of the functionality it provides.

Some teaser shots:
Orb-like: http://img2.abload.de/img/orblike_half0tit.jpg
Two half-rings: http://img2.abload.de/img/halfringsat89.jpg
Full health and manaring: http://img2.abload.de/img/hp_mp_ringepxa.jpg

Some other ideas:
Wing units: http://img3.abload.de/img/wingunit5g7h.gif
Rings around an orb: http://s.wowinterface.com/preview/pvw18480.jpg


Offline
Reply With Quote
Old 08/19/09, 7:35 AM   #4
Ammo
Glass Joe
 
Gnome Warlock
 
Kazzak (EU)
If you need more ideas of how to put circles to use.

I used Iriel's code and textures (with permission) to create a circular castbar and gcd ring around your cursor, handy for those Clique users.

Cast Donut : WoWInterface Downloads : Casting Bars and Cooldowns

Good explanation of how it works btw.

Author of Ace3, BigWigs, oRA2, Violation, WitchHunt, Necronomicon2, CrowBar, LockSmith, CandyBar and other stuff

Offline
Reply With Quote
Reply

Go Back   Elitist Jerks » User Interface and AddOns

Thread Tools

Similar Threads
Thread Thread Starter Forum Replies Last Post
Ring enchants-Self only? Kinv Public Discussion 21 10/05/06 10:50 AM
zzzzz....wah....huh....1:45 am????? Kaubel News 7 04/19/05 11:39 PM