• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[GUI-friendly] Circle Object System 1.3.1

[GUI-Friendly] Circle Object System v1.3.1

This system facilitates the creation of circles using in-game coordinates. Since most applications for this will involve units, it also allows the user to move certain units to the points on the circumference in a leakless and efficient manner (which is a big bonus for GUI coding).

Circles can also be made into elipses by setting one radius bigger than the other. Elipses can also be tilted by an angle. A circle can also be spun (the points on its circunference rotate around its center). Each circle is an "object" with a unique number (which is recycled when a circle is destroyed) larger than 1 and up to the maximum array size. Many propeties of the circle are saved in a Hashtable using the unique number (or ID) as the key.

While the system offers a way to spin the circles and update their position, you will need to code the periodic timers yourself, either using JASS timers or systems like [GUI-friendly] Timer System or [GUI] Effect Over Time System 2.3.1.

Circle Object:

Starting Angle (Of First Point)
Center (X/y)
Radius (X/Y)
Resolution (Number of Points)
Tilt Angle (Elipse)
Rotation/Spin Angular Step (Rotate Points Around Center when Circle is Updated)

Current Angle (Of First Point)
Points
Units (optional)

How to Import:

GUI API:
  1. Open World Editor. File -> Preferences -> Tick box for "Automatically create unknown variables (...)"
  2. Copy the Trigger Category "Circle Object System" to your own map
  3. Done!
JASS API:
  1. Open World Editor. File -> Preferences -> Tick box for "Automatically create unknown variables (...)"
  2. Copy the trigger category "Circle Object System JASS" into your map
  3. Copy the contents of the "GCOS Map Header" trigger to your map header
  4. Enable the "GCOS Init JASS" trigger
  5. Done!
Using the System:


Key = Circle Instance
Key = -Circle Instance

0
Starting Angle
0
Cur. Angle

1
Center X
i
X of point i

2
Center Y
-i
Y of point i

3
Radius X

4
Radius Y

5
№ of Points

6
Tilt

7
Rotation

100 + i
Unit i

A basic understanding of Hashtables is recommended in order to properly use this system.

Set Point Variable

Circle with Units

Center on Unit

Changing Values for a Circle

Destroy Circle

Create Circle

Update Circle

JASS API


Setting a point variable in GUI is useful to center regions or create destructibles/special effects/items at the points on the circumference. For units, built-in support examplified in the "Circle with Units" tab is better.

  • Set Point = (Point((Load i of (-1 x GCOS_Instance) from GCOS_Hashtable), (Load (-1 x i) of (-1 x GCOS_Instance) from GCOS_Hashtable)))

GUI:
  • Actions
    • Set GCOS_Instance = 0
    • Set GCOS_Tilt = 0.00
    • Set GCOS_Rotate = 0
    • Set GCOS_Radius_X = 100.00
    • Set GCOS_Radius_Y = 100.00
    • Set GCOS_Center_X = 0
    • Set GCOS_Center_Y = 0
    • Trigger - Run Circle System <gen> (ignoring conditions)
    • For each (Integer A) from 1 to 20, do (Actions)
      • Loop - Actions
        • Unit - Create 1 Footman for Player 1 (Red) at (Center of (Playable map area)) facing Default building facing degrees
          • Hashtable - Save Handle Of(Last created unit) as ((Integer A) + 100) of GCOS_Instance in GCOS_Hashtable
    • Trigger - Run Circle System <gen> (checking conditions)
JASS:
JASS:
function CircleWithUnits takes nothing returns nothing
    local integer i = 1
    local integer circleID = CreateGCOS(0,2500,2500,500,500,10,0,0)
    loop
        exitwhen i > 10
        //call SaveUnitHandle( udg_GCOS_Hashtable, circleID, 100 + i, CreateUnit(Player(0),'hfoo',0,0,0) )
        call SetGCOSUnit(circleID, i, CreateUnit(Player(0),'hfoo',0,0,0))
        set i = i + 1
    endloop
    call UpdateGCOS(circleID)
endfunction

This periodic trigger will make it so a circle is centered on a unit, following it as it moves.
  • Periodic Trigger
    • Events
      • Time - Every 0.03 seconds of game time
    • Actions
      • Set GCOS_Instance = 1
      • Custom script: call SaveReal(udg_GCOS_Hashtable, udg_GCOS_Instance, 1, GetUnitX(udg_TestPaladin))
      • Custom script: call SaveReal(udg_GCOS_Hashtable, udg_GCOS_Instance, 2, GetUnitY(udg_TestPaladin))
      • Trigger - Run Circle System <gen> (checking conditions)

  • Actions
    • Hashtable - Save GCOS_Center_X as 1 of GCOS_Instance in GCOS_Hashtable
    • Hashtable - Save GCOS_Center_Y as 2 of GCOS_Instance in GCOS_Hashtable
    • Hashtable - Save GCOS_Radius_X as 3 of GCOS_Instance in GCOS_Hashtable
    • Hashtable - Save GCOS_Radius_Y as 4 of GCOS_Instance in GCOS_Hashtable
    • Hashtable - Save GCOS_N_Points as 5 of GCOS_Instance in GCOS_Hashtable
    • Hashtable - Save GCOS_Tilt as 6 of GCOS_Instance in GCOS_Hashtable
    • Hashtable - Save GCOS_Rotate as 7 of GCOS_Instance in GCOS_Hashtable
    • Trigger - Run Circle System <gen> (checking conditions)

GUI:
  • Actions
    • Set GCOS_Instance = (existing circle number)
    • Trigger - Run GCOS Main <gen> (IGNORING conditions)
Destroying a circle will free up its ID, which will be used for the next circle created. DO NOT use numbers for which there exists no corresponding circle, as this can lead to unintended behaviour.

JASS:
JASS:
call DestroyGCOS(circleID)

GUI:
  • Actions
    • Set GCOS_Instance = 0
    • Trigger - Run GCOS Main <gen> (IGNORING conditions)
After a circle is created, its unique ID will be stored in the variable GCOS_Instance.

JASS:
JASS:
local integer circleID = CreateGCOS(angle, centerX, centerY, radiusX, radiusY, numPoints, tilt, spin)

GUI:
When you change circle values or want to spin it, you must run the following action:

  • Actions
    • Set GCOS_Instance = (circle number)
    • Trigger - Run GCOS Main <gen> (CHECKING conditions)
Each update of a circle will spin it by the angle saved in the 7th position of the Hashtable key. All the points in the circumference will be updated (their new values saved in the Hashtable). All units saved in the Hashtable will also be moved to the new positions.

JASS:
JASS:
call UpdateGCOS(circleID)

If you wish to use the JASS API, you may import the "Circle Object System JASS" category instead of the "Circle Object System" category. This will avoid the copying of many redundant variables for JASS users. After you copy this category into your map, copy the code from the GCOS Map Header trigger to your map's map header and enable the "GCOS Init JASS" trigger.

This is the provided JASS API:

JASS:
//This function updates the coordinates of a circle, also spinning it by its specified spin
//You need to call this function after directly changing values in the Hashtable or with the Set functions
function UpdateGCOS takes integer instance returns nothing

//Creates a GCOS and automatically updates its position using the UpdateGCOS fucntion
function CreateGCOS takes real angle, real centerX, real centerY, real radiusX, real radiusY, integer numPoints, real tilt, real spin returns integer

function DestroyGCOS takes integer instance returns nothing

//=================
//Set/Get functions that are hopefully inlined
//=================

function GetGCOSDefAngle takes integer instance returns real

function GetGCOSCurAngle takes integer instance returns real

function GetGCOSCenterX takes integer instance returns real

function GetGCOSCenterY takes integer instance returns real

function GetGCOSRadiusX takes integer instance returns real

function GetGCOSRadiusY takes integer instance returns real

function GetGCOSNoPoints takes integer instance returns integer

function GetGCOSTilt takes integer instance returns real

function GetGCOSSpin takes integer instance returns real

function SetGCOSDefAngle takes integer instance, real angle returns nothing

function SetGCOSCurAngle takes integer instance, real angle returns nothing

function SetGCOSCenterX takes integer instance, real X returns nothing

function SetGCOSCenterY takes integer instance, real Y returns nothing

function SetGCOSRadiusX takes integer instance, real X returns nothing

function SetGCOSRadiusY takes integer instance, real Y returns nothing

function SetGCOSNoPoints takes integer instance, integer number returns nothing

function SetGCOSTilt takes integer instance, real angle returns nothing

function SetGCOSSpin takes integer instance, real angle returns nothing

function GetGCOSPointX takes integer instance, integer pointNumber returns real

function GetGCOSPointY takes integer instance, integer pointNumber returns real

function SetGCOSUnit takes integer instance, integer pointNumber, unit u returns nothing

function GetGCOSUnit takes integer instance, integer pointNumber returns unit



Test Map:


Command

Action

n or new

Create a new circle on bottom left

-d (number>27)

Destroy a circle on bottom left with that number

-rx

Change bottom right circle radius x value

-ry

Change bottom right circle radius y value

-np

Change bottom right circle № points

-s

Change bottom right circle rotation step> 2pi/(input)

-t

Change bottom right circle (elipse) tilt

The Test Map has 20 circles of 20 points spinning at a 0.03 second periodic timer. This is done to demonstrate performance. The two circle objects at the top right corner are made using the JASS API (1 with JASS-only triggers and the other using custom script in GUI)

Videos: 1 2

Updates and Version History:
v1:
1.0.0 > Initial Release
1.0.1 >
- Fully conforming to GPAG and JPAG (except my preference with "exitwhen" identation)
- Added import instructions to world editor
- Fixed negative instance key of Hashtable not being flushed​
1.1.0 >
- Made a better API for JASS users
- Added new circles to test map using JASS API​
1.2.0 >
- System now requires World Bounds by Almia
- Code now uses SetUnitX/Y instead of SetUnitPosition
- Code now uses parametric equation for circumferences (instead of elipses) when possible
- CircleCreate and CreateGCOS now call CircleUpdate and UpdateGCOS to set circle points​
1.3.0 >
- Unified JASS and GUI code for better maintenance and to match style of my recent systems
- Due to the unification, import instructions for JASS API have been altered
- Better documentation of the JASS API offered in the submission's description
- Tried making a function to only spin the units around (thus using only memory instead of constant calculations, but it did not work as well as I wanted. May look into this further
- Removed code from submission description, since it is no longer a requirement and its a bother to change it for every update​
1.3.1 >
- Added documentation to the JASS trigger
- Made it so angles cannot be outisde the range of 0 to 2*PI
Upcoming:
-Maybe support all widgets instead of only units?
Credits:
Contents

Guhun's Circle Object System (Map)

Reviews
MyPad
Hmm, this looks like a handy plug-in system for GUI users. Although it does not come with the automated updating of the circle objects through a timer, this does the job quite handily. Approved
Level 7
Joined
Jan 23, 2011
Messages
350
Nice system

Although it require understanding of Hashtable and how variables and ids works, it is a really strong system, specially the possibility to make elipses

Plus: Making it all work from a single trigger is awesome
 
Level 7
Joined
Jan 23, 2011
Messages
350
My suggestion is: Having a "Load" function and a "Save" function

So you can have something like
Set GCOS_Instace
Load
And all the important values are stored on variables like
GCOS_Instance_Center
GCOS_Instance_Unit

And then having a Save function that stores the values from the variables to the Hashtable

So the editor only have to worry for the Instance variable as the Id
 
This flying curve of the Elipses are looking nice.

The demo is a little bit heavy my craptop can't handle it, there are a lot of moving units.

You could speed up this System by using the lighter SetUnitX()/SetUnitY() over SetUnitPosition().
SetUnitPosition does alot:
  • pathable?
  • collision?
  • out of Map bounds?
  • repaint!
  • orders "stop"!
  • updates position for missles shot onto this unit!
  • possible other things (not sure currently but it should do more)
SetUnitX/Y Does nothing from the list.

The default circling for dummies/Missles only needs:
  • repaint! (which they do by themself, if they are setuped correctly in the Object Editor (having speed and movetype))
  • out of Map bounds?
  • Update Z if changing Cliff height.
Eidt: If you circle normal units and want them disabled the normal setunitposition is quite fine.
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,258
Required for approval:
  • The description does not include the submissions triggers/code and as such cannot be approved.
  • The performance of the system is currently too low to be approvable. Even the test map runs at 30 FPS on my computer which is not a good advertisement of system performance. As some of the comments suggest, I recommend using SetUnitX/Y over SetUnitPosition as those natives perform much better when just movement is important.
Feedback:
  • It is a good idea to use named variable constants for hash table keys instead of hard coded magic numbers. This makes the code easier to understand and more maintainable. If the code is performance critical then maybe a readable and an optimized version should be provided.
  • Rather than duplicating like code, it is a good idea to bundle a single copy of the code into a function and call it from multiple places. This would be applicable to the ellipse position calculating procedures which are currently duplicated for both creation and update. Again if the code is performance critical then it is a good idea to provide both readable and optimized versions.
 
Required for approval:
  • The description does not include the submissions triggers/code and as such cannot be approved.

My mistake, I wrongly assumed that posting triggers was no longer a requirement, as the they can now be previewed from the site, thanks to Ralle's awesome efforts. I have added the code to the description.

  • The performance of the system is currently too low to be approvable. Even the test map runs at 30 FPS on my computer which is not a good advertisement of system performance. As some of the comments suggest, I recommend using SetUnitX/Y over SetUnitPosition as those natives perform much better when just movement is important.

I have changed the code to now use SetUnitX/Y, but I'm not very confident that it will improve the performance so much. In my current PC, the test map runs at 60 fps, so I'll wait for feedback to see how much it has improved.

Even if it hasn't, I think it may be unfair to say the system performance is too low, as in the test map, 400 units are moved and 400 elipse points are calculated every 0.03 seconds, which I believe is a pretty extreme case that should not happen in most maps.

  • It is a good idea to use named variable constants for hash table keys instead of hard coded magic numbers. This makes the code easier to understand and more maintainable. If the code is performance critical then maybe a readable and an optimized version should be provided.

The thing about having a readable version and an optimized version is that you need to change code twice when editing, and that can sometimes lead to errors when making changes. The best appraoch to me would be to use constant functions which are optimized by the vJASS compiler, but since I make GUI systems with the objective of them being usable with the vanilla editor, this would kind of defeat the point.

  • Rather than duplicating like code, it is a good idea to bundle a single copy of the code into a function and call it from multiple places. This would be applicable to the ellipse position calculating procedures which are currently duplicated for both creation and update. Again if the code is performance critical then it is a good idea to provide both readable and optimized versions.

To get around this, I have made the creation functions call the update functions to set the points. It's a lot less efficient, but the creation of circles itself is not so perfomance critical.

Thanks for the review and feedback!

Changelog:
1.2.0 >
-System now requires World Bounds by Almia
-Code now uses SetUnitX/Y instead of SetUnitPosition
-Code now uses parametric equation for circumferences (instead of elipses) when possible
-CircleCreate and CreateGCOS now call CircleUpdate and UpdateGCOS to set circle points
 
Last edited:
My mistake, I wrongly assumed that posting triggers was no longer a requirement, as the they can now be previewed from the site, thanks to Ralle's awesome efforts. I have added the code to the description.
We talked about updating the rule, but never actually came across to updating it. Thanks to both of you for bringing this to my attention! The rule has now been properly updated:
Submissions must have an adequate resource description. A changelog must be present in either the resource description or the trigger viewer.
 
New version update. No new features, as I tried implementing one, but it didn't turn out too well. Still, made some QoL changes that are worth getting online.

1.3.0 >
-Unified JASS and GUI code for better maintenance and to match style of my recent systems
-Due to the unification, import instructions for JASS API have been altered
-Better documentation of the JASS API offered in the submission's description
-Tried making a function to only spin the units around (thus using only memory instead of constant calculations, but it did not work as well as I wanted. May look into this further
-Removed code from submission description, since it is no longer a requirement and it's a bother to change it for every update
 
Last edited:

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
You should take advantage of the fact that your allocator returns only allowed integers for an array index. Instead of using hashtables, it would be better to just use arrays. This will both improve the API for users and also a bit of performance in cases where a circle is updated periodically. So there are no downside in using arrays over hashtable here.
 
You should take advantage of the fact that your allocator returns only allowed integers for an array index. Instead of using hashtables, it would be better to just use arrays. This will both improve the API for users and also a bit of performance in cases where a circle is updated periodically. So there are no downside in using arrays over hashtable here.


You'll be really limiting the total amount of units that a circle can have if you use arrays instead of Hashtables (though I guess it's not as big an issue with the new 32k array size), since you need to save multiple units per circle :S

Since I have to use Hashtables for the units, I might as well not pollute the global namespace (which also slows down global variable performance across the board). I also prefer a function API over changing the values of array variables (this way, the implementation of a function can be changed without the user having to alter their code), so the API would likely stay the same either way.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
Since I have to use Hashtables for the units, I might as well not pollute the global namespace (which also slows down global variable performance across the board).
Theoretically, any system could use a hashtable in place of all his array variables, but it would not be so great when it comes to readability (big pro especially for a public API) and performance (in places where performance matters). You provided a reason why you don't want to pollute the global namespace but even with thousands of global arrays, hashtables would still be many times slower compared to your variables (I didn't even heard of it before that polluting the global space would affect the speed at which you read and write to your global variables, but maybe I just really haven't been informed confirmed, I just recently read about it at wc3c.net), not to mention that hashtables can slow down the more data you to store to them because of possible bucket (data structure behind hashtables) collisions. Not saying that we should avoid using a hashtable for many things though. In fact, we dont mind the 'slowing down due to collision' because it is negligible. However, the more reason that we should not mind the more negligible 'slowing due to the pollution of global namespace' (if that is even the case it is).

You'll be really limiting the total amount of units that a circle can have if you use arrays instead of Hashtables (though I guess it's not as big an issue with the new 32k array size), since you need to save multiple units per circle :S
In the case of units, I would propose that you just stay with hashtable since it makes more sense because you are using two keys (circle instance, and the index/position of unit on the storage), but with the circle instance's data, it would make sense to just use arrays imo. One's preference also plays a role here though.

I also prefer a function API over changing the values of array variables (this way, the implementation of a function can be changed without the user having to alter their code), so the API would likely stay the same either way.
In the GUI api, they are provided the direct hashtable access (not function) so providing the directly the array access would not bring a new problem but I guess you're talking about the jass api here? (Because you said functions) So here, you could also just change the function implementation by making them (the functions) use the array instead of the hashtable, so no need to let the user access the arrays directly and thus they dont have to alter their codes.
 
Last edited:

System Review:

Notes:

  • Most of the underlying issues in the previous review were resolved with the recent update on this resource. Furthermore, in my tests, the core system ran rather smoothly (with a slight logic flaw, would be elaborated below), with most of the overhead being the result of timeout configurations and the number of units used.

  • At the moment, the system does not come with it an in-map API, which may be helpful to some who do not have a secure internet connection. It would be wise to include it in a possible update.

  • To expand on the first point, the system currently does not take into account any missing unit elements in a Circle Object when invoking the function UpdateGCOS, as it considers the case that they exist. This might end up becoming quite costly (and wasteful) in performance. (A hint: You can use indices 32769 (> JASS_MAX_ARRAY_SIZE) and above as a parent table.)

  • An added cost to performance, but one that may be of help is to ensure that the angle value would be within the range of 0 and 2*pi, in order to simplify angle calculations.

Remarks:

  • At its' current state, the system is already good enough for approval, but it does have its' flaws, specified above. It is quite fascinating to see what one can do with this, with an alluring display of concentric circles resembling an aura.

Status:

  • Awaiting Update
 
Firstly, I would like to thank you for reviewing my system, it had been quite a while :wgrin:

I imagine that, when you speak of the angle value being in a limited range, you mean how I only add a value to the angle in this line: set angle = angle + LoadReal(udg_GCOS_Hashtable,instance,7)? I can certainly see it being useful for a programmer to get a nice value for the current angle, so I'll get it done. I'll also look into putting in some nice documentation in the map itself.

About the units thing, I also thought of doing something similar. However, that would require me using two loops: one to set each point (which needs to iterate over all points) and one to position each (valid) unit. I think the cost of a SetUnitX call and a SetUnitY call on a null unit handle is a lot cheaper than making a separate loop for valid units only. Unless you are thinking of some other implementation? :S

I had actually been working a bit on my old systems (converting them to vJASS), but right now it seems like I have so many unrelated projects going on that I can't keep up with the wc3 stuff. But as soon as you get back to me on the double loop thing, I'll start working on getting an approvable version out.

Thanks again!
 
New update addressing some of the points raised by @MyPad. Did not change anything regarding his third point, because, since ALL the points need to be updated anyway, making a second loop to only update the position of occupied unit slots would possibly make the system slower in most situations.

1.3.1 >
- Added documentation to the JASS trigger
- Made it so angles cannot be outisde the range of 0 to 2*PI

 
Level 6
Joined
Dec 6, 2009
Messages
168
How do I make the ring start from the outer point with more than 20 units? I managed to make it a single ring but if I add more units they either line upp from the bottom to the top och the circle or just stay in the middle untill the circle goes back in again and removes them. I want 1 circle that starts from the outer and goes in like in PUBG/APEX/Fortnite. Which line checks if the circle is at its most center point and which line can I chane to make it only go in towards the center?
 
How do I make the ring start from the outer point with more than 20 units? I managed to make it a single ring but if I add more units they either line upp from the bottom to the top och the circle or just stay in the middle untill the circle goes back in again and removes them. I want 1 circle that starts from the outer and goes in like in PUBG/APEX/Fortnite. Which line checks if the circle is at its most center point and which line can I chane to make it only go in towards the center?

I really don't get what you mean. What do you mean, check if the circle is at its most center point? You should be able to add a lot more than 20 units to the circle. Are you making the circle larger than the map area? That can cause units to line up at the edges of the map.
 
Top