HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

[vJASS]camera help

07-01-2011, 12:21 AM#1
Yrth
background

a friend of mine asked me to help him make a way to rotate a camera randomly around a specific unit for a specific player
i thought it'd be really simple and just be something like using
Collapse JASS:
SetCameraRotateMode
except the problem with this is when you call it more than once it resets the player's camera to the default angle before resuming rotation, creating a huge gap in fluidity
it'd be possible to get around this by simply having every rotation end at the default camera angle before starting a new/different rotation - but this wouldnt be very random and would be fairly predictable... i couldn't find a way to change the default angle for a person so using
Collapse JASS:
SetCameraRotateMode
was no longer a possibility.
why did i code it the way i did? well it was my first attempt at doing it this way in a language im not very familiar with. after ive got a working prototype ill probably rework the whole thing



code


Collapse JASS:
globals
    //CONFIGURABLES
    //should this run in debug mode?
    //only for testing (debug messages leak)
    constant boolean CAMDEBUG = true
    //should the rotation direction alternate or be random?
    constant boolean CAMALTERNATEDIR = true
    //how many people play (at most)
    constant integer NumberPlayers = 10
    //unit array which contains all the different mazers... you should replace this one with your own if you have one
    unit array Mazers[NumberPlayers]
    //how often the periodic trigger is run, higher means less memory intensive but choppier movement
    constant real HowOften = .05
    //bounds for cam rotation speed
    constant real LeastCamSpeed = 20
    constant real MostCamSpeed = 80
    //bounds for rotation duration
    constant integer LeastCamTicks = 50
    constant integer MostCamTicks = 200
    
    //dont touch below here unless you know what you're doing
    //used to access the different cameras
    RotationCamera array RotCameras[NumberPlayers]

endglobals

struct RotationCamera
    //an array that stores previous camera angles
    private real CurCamAngle
    //how many ticks are left going in this direction 
    private integer CamNumTicks
    //true == clockwise, false == counterclockwise
    private boolean CamRotationDirection
    //the speed for the camera
    private real CamRotationSpeed
    //the current camera
    private camerasetup CurCam
    //who does this camera belong to
    private player CamOwner
    //which unit does the camera center on
    private unit CamUnit
    
    private method CamRandGenerate takes nothing returns nothing
        //generates a new random set of paramaters which replace the previous ones
        set CamNumTicks = GetRandomInt(LeastCamTicks, MostCamTicks)
        set CamRotationSpeed = GetRandomReal(LeastCamSpeed, MostCamSpeed)
        static if CAMALTERNATEDIR then
            if CamRotationDirection == true then
                set CamRotationDirection = false
            else
                set CamRotationDirection = true
            endif
        else
            if GetRandomInt(0, 1) == 0 then
                set CamRotationDirection = true
            else
                set CamRotationDirection = false
            endif
        endif
        
        //debug stuff
        static if CAMDEBUG then
            call DisplayTextToForce(bj_FORCE_PLAYER[GetPlayerId(GetOwningPlayer(CamUnit))], "New Parameters")
            call DisplayTextToForce(bj_FORCE_PLAYER[GetPlayerId(GetOwningPlayer(CamUnit))], "Num Ticks: " + I2S(CamNumTicks))
            call DisplayTextToForce(bj_FORCE_PLAYER[GetPlayerId(GetOwningPlayer(CamUnit))], "Rotation Speed: " + R2S(CamRotationSpeed))
            if CamRotationDirection then
                call DisplayTextToForce(bj_FORCE_PLAYER[GetPlayerId(GetOwningPlayer(CamUnit))], "Rotate Clockwise")
            else
                call DisplayTextToForce(bj_FORCE_PLAYER[GetPlayerId(GetOwningPlayer(CamUnit))], "Rotate Counter-Clockwise")
            endif
        endif

    endmethod
    
    public method CamRotate takes nothing returns nothing
        //if the camera has finished its rotation period then its time to generate a new set of parameters
        if CamNumTicks < 1 then
            call CamRandGenerate()
        //otherwise keep rotating the camera with the existing parameters
        else
            //clockwise or counterclockwise? might be easier for CamRotationDirection to just be -1 or 1.
            if CamRotationDirection then
                set CurCamAngle = CurCamAngle - CamRotationSpeed
            else
                set CurCamAngle = CurCamAngle + CamRotationSpeed
            endif
            
            //prevents super big reals
            //for some reason this breaks it
            //if CurCamAngle > 360 then
            //    set CurCamAngle = CurCamAngle - 360
            //elseif CurCamAngle < -360 then
            //    set CurCamAngle = CurCamAngle + 360
            //endif
            
            //set the angle for the camera field
            call CameraSetupSetField(CurCam, CAMERA_FIELD_ROTATION, (CurCamAngle * bj_PI) / 180.0, HowOften)
            call CameraSetupSetDestPosition(CurCam, GetUnitX(CamUnit), GetUnitY(CamUnit), HowOften)
            
            //apply the new angle/camera for the specified person
            if GetLocalPlayer() == CamOwner then
                call CameraSetupApply(CurCam, false, false)
                call PanCameraToTimed(GetUnitX(CamUnit), GetUnitY(CamUnit), HowOften)
            endif
            
            //decrement the number of ticks remaining for these parameters
            set CamNumTicks = CamNumTicks - 1
        endif
    endmethod
    
    //initializes all the data fields for the rotating camera object
    //no rotation is actually done here
    public static method create takes integer WhosCamId returns RotationCamera
        local RotationCamera new = RotationCamera.allocate()
        
        set RotCameras[WhosCamId] = new
        set new.CamOwner = Player(WhosCamId)
        set new.CamUnit = Mazers[WhosCamId]
        set new.CurCamAngle = 0
        set new.CurCam = CreateCameraSetup()
        
        call new.CamRandGenerate()
        
        return new
    endmethod
    
    public method onDestroy takes nothing returns nothing
        set CamOwner = null
        set CurCam = null
    endmethod
    
endstruct



problems

the functionality of the code is really really inconsistent.
for example if i restart the map 10 times it might:
run perfectly fine, rotating the camera randomly and following the specified unit
follow the unit but refuse to rotate at all
follow the unit and rotate sporadically - working fluidly when it does and then just completely cutting off for a few seconds/minutes
work perfectly but then have the camera completely freak out and spin at ~the speed of light around the unit



test map

also my application for every terraining job ill ever get
-on to turn on rotation, -off to turn it off
debug is set to true
debug leaks
in the final version debug wont be set to true
it wont leak then



last remarks


thanks so much to anyone who takes the time to lend a hand
even though i'm new and i know ive got a lot to learn, please dont be mean - im pretty attached to my code
of course constructive criticism is much appreciated, as i said: ive got a lot to learn

Attached Files
File type: w3xCameraRotateWorkshop.w3x (21.3 KB)
07-01-2011, 08:34 AM#2
Anitarf
I think that CameraSetupSetField, like SetCameraField which I'm familiar with, uses degrees, not radians. I don't immediately see any other errors in your code, so maybe this was all that was wrong.

Here are some other things I would change, they're not necessarily wrong but can be made shorter.

Collapse JASS:
            if CamRotationDirection == true then
                set CamRotationDirection = false
            else
                set CamRotationDirection = true
            endif
// This can be replaced with:
            set CamRotationDirection = not CamRotationDirection
Collapse JASS:
            if GetRandomInt(0, 1) == 0 then
                set CamRotationDirection = true
            else
                set CamRotationDirection = false
            endif
// This can be replaced with:
            set CamRotationDirection = (GetRandomInt(0, 1) == 0)
07-01-2011, 11:35 AM#3
moyack
Oppicam can do that, isn't it??
07-01-2011, 01:46 PM#4
Yrth
Collapse JASS:
set CamRotationDirection = not CamRotationDirection
when i was looking up leaks, i read that using and, or, not leaked
im beginning to doubt the particular guide that i read - so far some other stuff has been wrong
(like why locals need to be nulled)

Quote:
I think that CameraSetupSetField, like SetCameraField which I'm familiar with, uses degrees, not radians. I don't immediately see any other errors in your code, so maybe this was all that was wrong.
mmm i dont think that was it
i tried changing it to degrees and the rotation was completely wacky even when it did work

Quote:
Oppicam can do that, isn't it??
i kinda doubt it
oppicam seems really useful, and this is the type of thing which is super useless except for this one rare purpose
even still thats not really the point
cant learn a language just by looking at other peoples code



i noticed something else
the camera only seems to stop rotating at 0,90,180,270 degrees
which would make sense for the times it didnt ran at all (since it starts at 0... or whatever default is)
is there some default which automatically snaps cameras?
07-01-2011, 02:48 PM#5
Anitarf
Quote:
when i was looking up leaks, i read that using and, or, not leaked
Well, the functions And(), Or() and Not() do "leak", meaning they create new boolexprs which you have to clean up if you use them dynamically. Using the and, or or not operators doesn't leak.

Anyway, I tested the thing in an empty map. To get it to work, I had to remove the conversion from degrees to radians, reactivate the bounds checking and fix it so it didn't limit the value between -360 and 360, but between 0 and 360. Camera setups that have values outside these bounds do not work.

By the way, the code is not very import friendly, I had to add a lot of missing things like the periodic timer which should have been encapsulated in your code already, however you apparently do it from outside somewhere; it is possible there are additional bugs in that code, I can't help you with that because you haven't posted it. Here is the code I ended up using for testing, I used ListModule in it:

Collapse JASS:
library cameratest requires ListModule

globals
    //CONFIGURABLES
    //should this run in debug mode?
    //only for testing (debug messages leak)
    private constant boolean CAMDEBUG = true
    //should the rotation direction alternate or be random?
    private constant boolean CAMALTERNATEDIR = true
    //how many people play (at most)
    private constant integer NumberPlayers = 10
    //how often the periodic trigger is run, higher means less memory intensive but choppier movement
    private constant real HowOften = .05
    //bounds for cam rotation speed
    private constant real LeastCamSpeed = 20
    private constant real MostCamSpeed = 80
    //bounds for rotation duration
    private constant integer LeastCamTicks = 50
    private constant integer MostCamTicks = 200
    
    //dont touch below here unless you know what you're doing
    //used to access the different cameras
    private RotationCamera array RotCameras[NumberPlayers]

endglobals

struct RotationCamera
    //an array that stores previous camera angles
    private real CurCamAngle
    //how many ticks are left going in this direction 
    private integer CamNumTicks
    //true == clockwise, false == counterclockwise
    private boolean CamRotationDirection
    //the speed for the camera
    private real CamRotationSpeed
    //the current camera
    private camerasetup CurCam
    //who does this camera belong to
    private player CamOwner
    //which unit does the camera center on
    private unit CamUnit
    
    private method CamRandGenerate takes nothing returns nothing
        //generates a new random set of paramaters which replace the previous ones
        set CamNumTicks = GetRandomInt(LeastCamTicks, MostCamTicks)
        set CamRotationSpeed = GetRandomReal(LeastCamSpeed, MostCamSpeed)
        static if CAMALTERNATEDIR then
            if CamRotationDirection == true then
                set CamRotationDirection = false
            else
                set CamRotationDirection = true
            endif
        else
            if GetRandomInt(0, 1) == 0 then
                set CamRotationDirection = true
            else
                set CamRotationDirection = false
            endif
        endif
        
        //debug stuff
        static if CAMDEBUG then
            call DisplayTextToForce(bj_FORCE_PLAYER[GetPlayerId(GetOwningPlayer(CamUnit))], "New Parameters")
            call DisplayTextToForce(bj_FORCE_PLAYER[GetPlayerId(GetOwningPlayer(CamUnit))], "Num Ticks: " + I2S(CamNumTicks))
            call DisplayTextToForce(bj_FORCE_PLAYER[GetPlayerId(GetOwningPlayer(CamUnit))], "Rotation Speed: " + R2S(CamRotationSpeed))
            if CamRotationDirection then
                call DisplayTextToForce(bj_FORCE_PLAYER[GetPlayerId(GetOwningPlayer(CamUnit))], "Rotate Clockwise")
            else
                call DisplayTextToForce(bj_FORCE_PLAYER[GetPlayerId(GetOwningPlayer(CamUnit))], "Rotate Counter-Clockwise")
            endif
        endif

    endmethod
    
    private method CamRotate takes nothing returns nothing
        //if the camera has finished its rotation period then its time to generate a new set of parameters
        if CamNumTicks < 1 then
            call CamRandGenerate()
        //otherwise keep rotating the camera with the existing parameters
        else
            //clockwise or counterclockwise? might be easier for CamRotationDirection to just be -1 or 1.
            if CamRotationDirection then
                set CurCamAngle = CurCamAngle - CamRotationSpeed
            else
                set CurCamAngle = CurCamAngle + CamRotationSpeed
            endif
            
            //prevents super big reals
            //for some reason this breaks it   -   actually, this makes it work
            if CurCamAngle > 360 then
                set CurCamAngle = CurCamAngle - 360
            elseif CurCamAngle < 0 then
                set CurCamAngle = CurCamAngle + 360
            endif
            
            //set the angle for the camera field
            call CameraSetupSetField(CurCam, CAMERA_FIELD_ROTATION, CurCamAngle, HowOften) // degrees are correct, not radians
            call CameraSetupSetDestPosition(CurCam, GetUnitX(CamUnit), GetUnitY(CamUnit), HowOften)
            
            //apply the new angle/camera for the specified person
            if GetLocalPlayer() == CamOwner then
                call CameraSetupApply(CurCam, false, false)
                call PanCameraToTimed(GetUnitX(CamUnit), GetUnitY(CamUnit), HowOften)
            endif
            
            //decrement the number of ticks remaining for these parameters
            set CamNumTicks = CamNumTicks - 1
        endif
    endmethod
    
    implement List
    
    private static method periodic takes nothing returns nothing
        local thistype e = .first
            loop
                exitwhen e == 0
                call e.CamRotate()
                set e = e.next
            endloop
    endmethod
    
    //initializes all the data fields for the rotating camera object
    //no rotation is actually done here
    public static method create takes integer WhosCamId returns RotationCamera
        local RotationCamera new = RotationCamera.allocate()
        
        set RotCameras[WhosCamId] = new
        set new.CamOwner = Player(WhosCamId)
        set new.CamUnit = CreateUnit(Player(WhosCamId), 'hfoo', 0,0,0)
        set new.CurCamAngle = 0
        set new.CurCam = CreateCameraSetup()
        
        call new.CamRandGenerate()
        call new.listAdd()
        return new
    endmethod
    
    private static method onInit takes nothing returns nothing
        call TimerStart(CreateTimer(), HowOften, true, function thistype.periodic)
        call thistype.create(0)
    endmethod
    
    private method onDestroy takes nothing returns nothing
        call .listRemove()
        set CamOwner = null
        set CurCam = null
    endmethod
    
endstruct

endlibrary
07-02-2011, 04:28 AM#6
Yrth
Quote:
Well, the functions And(), Or() and Not() do "leak", meaning they create new boolexprs which you have to clean up if you use them dynamically. Using the and, or or not operators doesn't leak.
raaaaawr
this would have saved so much time on the last thing i made
i had 30 nested if statements because i thought or leaked (and the function was called periodically)
not only that but i didnt close one of the ifs so i had to spend like 15 minutes counting spaces, since i didn't notice the indenting was off till the very end

Quote:
Anyway, I tested the thing in an empty map. To get it to work, I had to remove the conversion from degrees to radians, reactivate the bounds checking and fix it so it didn't limit the value between -360 and 360, but between 0 and 360. Camera setups that have values outside these bounds do not work.
ohhh ok
when i did it, it had no limit for bounds so that must have been why it seemed even glitchier when i tried it as degrees
that was a really good catch though, i'd have never thought of changing the lower limit from -360 to 0 (cuz they're equivalent and all that)
thank you
(maybe i should just make "thank you" my signature)

Quote:
By the way, the code is not very import friendly, I had to add a lot of missing things like the periodic timer which should have been encapsulated in your code already, however you apparently do it from outside somewhere
erm
i had posted the test map (in my first post) i'd been using so you wouldn't have to do stuff like this
even still i really appreciate that you actually did all that extra stuff, tyvm for the help

Quote:
like the periodic timer which should have been encapsulated in your code already
yeah ive been having a lot of trouble trying to do exactly this
every way so far has run into a dead end
i dont completely understand why exactly what you did works as it does so if you don't mind i'll ask one or two questions about it (and see if i understand it correctly)

1. list module is a (well done) doubly linked list
1b. thistype is the JASS version of generics
1c. each type gets its own list devoted to that type (although i guess you could give a type more than 1 list if necessary)

2.
Collapse JASS:
private static method onInit takes nothing returns nothing
any function with this signature is called on map init (or compile time?)

3.
Collapse JASS:
call TimerStart(CreateTimer(), HowOften, true, function thistype.periodic)
i dont understand "function thistype.periodic"
does that mean the .periodic that is defined in this library as opposed to .periodic in other structs
or is it a short hand way to say function RotationCamera.periodic
and why isn't it written as .periodic()
i guess my question is can you explain thistype.periodic a bit, please?

4. despite not really knowing how #3 works,
a timer is attached to the .periodic function, which in turn iterates through the list of RotationCameras calling the .rotate function
07-02-2011, 09:22 AM#7
Anitarf
Quote:
Originally Posted by Yrth
erm
i had posted the test map (in my first post) i'd been using so you wouldn't have to do stuff like this
Ah, I apologize, since the code was posted in the post I completely disregarded the attached map. Note that if the code is properly self sufficient, it is actually easier for me to copy it to test map I have already opened in the editor than to download and open a new testmap. Also, I prefer not to download and open new test maps because then they clutter up my recently opened maps list in the world editor file menu and my active projects get pushed off that list so that when I want to work on them again I have to browse through my folders to find them. Really, copying code is a lot more convenient unless it has a lot of library/object requirements.

Quote:
1c. each type gets its own list devoted to that type (although i guess you could give a type more than 1 list if necessary)
You can only implement a module once per struct, so ListModule can only give you one list. You'd need a different module if you wanted more lists (for example, the AutoData module gives you a list per unit).

Quote:
2.
Collapse JASS:
private static method onInit takes nothing returns nothing
any function with this signature is called on map init (or compile time?)
In vJass, only static methods with that name get automatically called on map initialization; they're essentially struct/module initializers. In Zinc, library initializers are done this way as well instead of using the initializer keyword in the library declaration.

Quote:
i dont understand "function thistype.periodic"
does that mean the .periodic that is defined in this library as opposed to .periodic in other structs
or is it a short hand way to say function RotationCamera.periodic
and why isn't it written as .periodic()
i guess my question is can you explain thistype.periodic a bit, please?
The thistype keyword is meant to be used as a wildcard in modules since you don't know what the name of the struct that implements the module is going to be. Once JassHelper finishes copying the modules everywhere where they are implemented, all thistype keywords get replaced with the name of the struct they're in. Although this was not intended, you can also use thistype outside modules and it will work the same way. So yes, it is just a replacement for RotationCamera.periodic, when I was writing it I forgot what the name of the struct was and was too lazy to scroll up and check, so I just used thistype, although that is considered poor form.

Quote:
4. despite not really knowing how #3 works,
a timer is attached to the .periodic function, which in turn iterates through the list of RotationCameras calling the .rotate function
Yes, that's what happens. I could have just made a static array of active RotationCameras and looped through that, but I already happened to have the ListModule in my test map so I used it.
07-03-2011, 03:19 AM#8
Yrth
Quote:
Note that if the code is properly self sufficient, it is actually easier for me to copy it to test map I have already opened in the editor than to download and open a new testmap. Also, I prefer not to download and open new test maps because then they clutter up my recently opened maps list in the world editor file menu and my active projects get pushed off that list so that when I want to work on them again I have to browse through my folders to find them. Really, copying code is a lot more convenient unless it has a lot of library/object requirements.

good to know, ill keep that in mind next time

Quote:
Yes, that's what happens. I could have just made a static array of active RotationCameras and looped through that, but I already happened to have the ListModule in my test map so I used it.
I like using the list much, much more
especially since most of my coding is done in LISP
all i need now is car and cdr haha


ok so ive taken your suggestions and then reevaluated a few other things that might be useful and have a newer version if you have time to take a quick look (mostly for glaring leaks or baaad syntax)
i've got 3 specific questions on top of that

Collapse JASS:
library CameraLibrary requires ListModule
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                        Rotation Camera
//              credit to Grim001 for ListModule
//                          
// Purpose: 
// Creates a structure which rotates "randomly" around a given unit for a given player
//====================================================================================
// Configuration:
// Change the values of the global variables to be as you want, recommended values are given
// descriptions of what each variable does is given by the comment directly above the variable
//============================================================================================
// Importing: 
// This library requires ListModule. Either import ListModule manually, or copy and paste the Libraries folder in the test map
// located in the test map.
// All global variables must have a value. Some must be set in a separate trigger at map init (like CamUnit)
// If being used in a maze, you most likely have an array which contains all the mazers - if this is the case
// you should replace the CamUnit array with your own.
// Because this uses libraries and structs, this requires JASS newGen editor to compile.
//======================================================================================
// Expanded API: (functions available to this struct) + explanations
//
// .create(integer i, unit u) -> RotationCamera
// creates a camera for Player(i) centering around unit u
// if ROTATEONCREATE in the configurables section is set to true, then the camera will begin rotation immediately
//
// .destroy() -> nothing
// deallocates the RotationCamera object as well as:
// nulling member variables, resetting default camera for owning player, stopping camera rotation.
//
// .CamStart() -> nothing
// resumes rotation for a previously created camera. camera MUST exist already.
//
// .CamStop() -> nothing
// stops rotation for an existing camera. however this does not destroy the camera.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
globals
    //CONFIGURABLES
    //should this run in debug mode?
    //only for testing (debug messages leak)
    private constant boolean CAMDEBUG = true
    //the timer used for the periodic function
    private timer CamTimer = CreateTimer()
    //should the rotation direction alternate or be random?
    private constant boolean CAMALTERNATEDIR = true
    //should cameras start rotating as soon as they are created?
    private constant boolean ROTATEONCREATE = true
    //how often the periodic trigger is run, higher means less memory intensive but choppier movement
    private constant real HowOften = .05
    //bounds for cam rotation speed
    private constant real LeastCamSpeed = .5
    private constant real MostCamSpeed = 5
    //bounds for rotation duration
    private constant integer LeastCamTicks = 50
    private constant integer MostCamTicks = 200
endglobals

struct RotationCamera
    //an array that stores previous camera angles
    private real CurCamAngle
    //how many ticks are left going in this direction 
    private integer CamNumTicks
    //true == clockwise, false == counterclockwise
    private boolean CamRotationDirection
    //the speed for the camera
    private real CamRotationSpeed
    //the current camera
    private camerasetup CurCam
    //who does this camera belong to
    private player CamOwner
    //which unit does the camera center on
    private unit CamUnit
    
    private method CamRandGenerate takes nothing returns nothing
        //generates a new random set of paramaters which replace the previous ones
        set CamNumTicks = GetRandomInt(LeastCamTicks, MostCamTicks)
        set CamRotationSpeed = GetRandomReal(LeastCamSpeed, MostCamSpeed)
        static if CAMALTERNATEDIR then
            set CamRotationDirection = not CamRotationDirection
        else
            set CamRotationDirection = (GetRandomInt(0, 1) == 0)
        endif
        
        //debug stuff
        static if CAMDEBUG then
            call DisplayTextToForce(bj_FORCE_PLAYER[GetPlayerId(GetOwningPlayer(CamUnit))], "New Parameters")
            call DisplayTextToForce(bj_FORCE_PLAYER[GetPlayerId(GetOwningPlayer(CamUnit))], "Num Ticks: " + I2S(CamNumTicks))
            call DisplayTextToForce(bj_FORCE_PLAYER[GetPlayerId(GetOwningPlayer(CamUnit))], "Rotation Speed: " + R2S(CamRotationSpeed))
            if CamRotationDirection then
                call DisplayTextToForce(bj_FORCE_PLAYER[GetPlayerId(GetOwningPlayer(CamUnit))], "Rotate Clockwise")
            else
                call DisplayTextToForce(bj_FORCE_PLAYER[GetPlayerId(GetOwningPlayer(CamUnit))], "Rotate Counter-Clockwise")
            endif
        endif

    endmethod
    
    private method CamRotate takes nothing returns nothing
        //if the camera has finished its rotation period then its time to generate a new set of parameters
        if CamNumTicks < 1 then
            call CamRandGenerate()
        //otherwise keep rotating the camera with the existing parameters
        else
            //clockwise or counterclockwise? might be easier for CamRotationDirection to just be -1 or 1.
            if CamRotationDirection then
                set CurCamAngle = CurCamAngle - CamRotationSpeed
            else
                set CurCamAngle = CurCamAngle + CamRotationSpeed
            endif
            
            //prevents super big reals
            if CurCamAngle > 360 then
                set CurCamAngle = CurCamAngle - 360
            elseif CurCamAngle < 0 then
                set CurCamAngle = CurCamAngle + 360
            endif
            
            //set the angle for the camera field
            call CameraSetupSetField(CurCam, CAMERA_FIELD_ROTATION, CurCamAngle, HowOften)
            call CameraSetupSetDestPosition(CurCam, GetUnitX(CamUnit), GetUnitY(CamUnit), HowOften)
            
            //apply the new angle/camera for the specified person
            if GetLocalPlayer() == CamOwner then
                call CameraSetupApply(CurCam, false, false)
                call PanCameraToTimed(GetUnitX(CamUnit), GetUnitY(CamUnit), HowOften)
            endif
            
            //decrement the number of ticks remaining for these parameters
            set CamNumTicks = CamNumTicks - 1
        endif
    endmethod
    
    implement List
    
    //iterates through the list of cameras calling the rotation function
    private static method periodic takes nothing returns nothing
        local RotationCamera e = .first
        
        loop
        exitwhen e == 0
            call e.CamRotate()
            set e = e.next
        endloop
    endmethod
        
    //start a camera given a previously stopped camera. 
    //Cannot start a destroyed camera, if you destroy a camera you need to .create a new one to replace it
    public method CamStart takes nothing returns nothing
        call .listAdd()
    endmethod
    
    //stops a camera while leaving the RotationCamera object intact
    public method CamStop takes nothing returns nothing
        call .listRemove()
    endmethod
    
    //start the timer and pause it, as its not needed until a camera object is made
    /*
    private static method onInit takes nothing returns nothing
        call TimerStart(CamTimer, HowOften, true, function RotationCamera.periodic)
        call PauseTimer(CamTimer)
    endmethod
    */
    
    //initializes all the data fields for the rotating camera object
    //no rotation is actually done here
    public static method create takes integer WhosCamId, unit whichCamUnit returns RotationCamera
        local RotationCamera new = RotationCamera.allocate()
        
        set new.CamOwner = Player(WhosCamId)
        set new.CamUnit = whichCamUnit
        set new.CurCamAngle = 0
        set new.CurCam = CreateCameraSetup()
        
        //generates and sets the parameters for the camera
        call new.CamRandGenerate()
        
        //if true, the camera will begin rotating immediately for the given player
        static if ROTATEONCREATE then
            call new.listAdd()
        endif
        
        //always assume the timer is paused, and needs to be started
        //my attempts to save memory on timer runs:
        //
        //call ResumeTimer(CamTimer)
        //
        //if .count == 0 then
        //  call TimerStart(CamTimer, HowOften, true, function RotationCamera.periodic)
        //endif
        
        call TimerStart(CamTimer, HowOften, true, function RotationCamera.periodic)
        
        return new
    endmethod
        
    private method onDestroy takes nothing returns nothing
        //remove this camera from the list
        call .listRemove()
        //if no camera is being rotated, then pause the timer
        if .count == 0 then
            call PauseTimer(CamTimer)
        endif
        //resets the game camera to default for given player
        if GetLocalPlayer() == CamOwner then
            call ResetToGameCamera(3.00)
        endif
        //null handle member variables
        set CamOwner = null
        set CurCam = null
    endmethod
    
endstruct

endlibrary

the test function looked like this

Collapse JASS:
globals
    //how many people play (at most)
    constant integer NumberPlayers = 10
    //used to access the different cameras
    RotationCamera array RotCameras[NumberPlayers]
    //array for all the units which are "attached" to the camera
    unit array CamUnit[NumberPlayers]
endglobals


//===========================================================================
function InitTrig_CamTest takes nothing returns nothing
    local integer i = 0
    
    set CamUnit[0] = gg_unit_Hblm_0000
    set CamUnit[1] = gg_unit_Hblm_0003
    set gg_trg_CamTest = CreateTrigger(  )
    
    loop
    exitwhen i > NumberPlayers - 1
        if GetPlayerSlotState(Player(i)) == PLAYER_SLOT_STATE_PLAYING then
            set RotCameras[i] = RotationCamera.create(i, CamUnit[i])
        endif
        set i = i + 1
    endloop
endfunction

question 1:
theres no error protection whatsoever
if someone were to create a camera twice without destroying the old camera, everything goes to hell
turning a camera that doesnt exist on then creating a new camera also causes baaaad stuff
basically the problem is i need to be able to check if a camera is already in a list for a person
this should be easy but i'm not totally sure if the way i want to do it is correct


Collapse JASS:
    private static method containsPlayer takes integer i returns RotationCamera
        local RotationCamera e = .first
        
        loop
        exitwhen e == 0
            if GetPlayerId(e.CamOwner) == i then
                return e
            endif
            set e = e.next
        endloop
        
        return -1
    endmethod

then I check if the value is -1 (i wanted to use null but JASS won't let you do that for atoms) otherwise I call .destroy() on the returned RotationCamera.
if the value is -1, than the given player doesn't already have a rotating camera.

question 2:
so now that the timer is built into the structure, there is the small problem that it is always running
my first attempt at fixing this used PauseTimer and ResumeTimer for a global timer var I made, but for whatever reason this didn't work
I left what I tried in the code, but commented it out, there are 2 parts to it the onInit code and the .create() code (end of .create)

in the end, pausing the timer when the struct is destroyed and the .count of the list is 0 and then starting the timer every time .create() is called worked
see the commented out code for more information on what I tried that didn't work

question 3:
is there an easy way to recycle things?
the last project I did, I used a hashtable within each data struct
but because I couldn't figure out how to destroy hashtables, there was no support for destroying the struct
it wasn't such a big deal as the struct wasn't ever meant to be destroyed, but adding such support to it (last project) would certainly help

question 4 (yeah i lied):
I read in the ListModule description to implement list at the very top of the struct to avoid unnecessary trigger evaluate calls. does this make a noticeable difference/is this no longer the case?
07-03-2011, 06:52 AM#9
Anitarf
Quote:
Originally Posted by Yrth
question 1:
theres no error protection whatsoever
if someone were to create a camera twice without destroying the old camera, everything goes to hell
turning a camera that doesnt exist on then creating a new camera also causes baaaad stuff
basically the problem is i need to be able to check if a camera is already in a list for a person
this should be easy but i'm not totally sure if the way i want to do it is correct
This depends entirely on the way you need this to work in your map, this is quite a specific library so you should streamline it for your map rather than trying to make it a general system. You should explain what causes the rotating camera and how long do you want it to work.

BTW, structs start at 1 so we usually use 0 as a null return, not -1.

Quote:
question 2:
so now that the timer is built into the structure, there is the small problem that it is always running
my first attempt at fixing this used PauseTimer and ResumeTimer for a global timer var I made, but for whatever reason this didn't work
I left what I tried in the code, but commented it out, there are 2 parts to it the onInit code and the .create() code (end of .create)

in the end, pausing the timer when the struct is destroyed and the .count of the list is 0 and then starting the timer every time .create() is called worked
see the commented out code for more information on what I tried that didn't work
That's okay, but what's the question?

Quote:
question 3:
is there an easy way to recycle things?
the last project I did, I used a hashtable within each data struct
but because I couldn't figure out how to destroy hashtables, there was no support for destroying the struct
it wasn't such a big deal as the struct wasn't ever meant to be destroyed, but adding such support to it (last project) would certainly help
It depends on what you need to recycle. For timers and groups, there's TimerUtils and GroupUtils. You can also just hardcode it into your struct, that way the handle will get recycled along with your struct index:
Collapse JASS:
    group g
    static method create takes nothing returns thistype
        local thistype this=thistype.allocate()
        if .g==null then
            set .g=CreateGroup()
        endif
        return this
    endmethod

    method onDestroy takes nothing returns nothing
        call GroupClear(.g)
    endmethod

Quote:
question 4 (yeah i lied):
I read in the ListModule description to implement list at the very top of the struct to avoid unnecessary trigger evaluate calls. does this make a noticeable difference/is this no longer the case?
You know, I thought a feature was added to JassHelper where it would reorder struct methods so that evaluates wouldn't be used if possible, but looking at the JassHelper changelog I found no such update. I did a quick test and no such feature seems to exist, so yes, you should order your methods and modules in such a way as to avoid evaluates because they are slower.
07-03-2011, 07:06 PM#10
Yrth
Quote:
This depends entirely on the way you need this to work in your map, this is quite a specific library so you should streamline it for your map rather than trying to make it a general system. You should explain what causes the rotating camera and how long do you want it to work.

I'm going to release several libraries for mazes after I finish my map, and this rotation theme is surprisingly common in mazes
But I'm also going to make a maze maker map after I finish the current maze (where you build the maze in game using super-gui) after I finish my own. For that, i'll need to be able to create things in the most general manner possible.

Quote:
That's okay, but what's the question?
well
why doesn't it work to do what I did
I start the timer at map init and immediately paused it
then every time .create() was called, I also resumed the timer (regardless of knowing whether it was paused or not)
and then when the list was empty I paused the timer once again
But for some reason, unpause never worked, so I had to call TimerStart instead of unpause
I don't fully understand how to use timers yet, but this seems bad.

Quote:
It depends on what you need to recycle. For timers and groups, there's TimerUtils and GroupUtils. You can also just hardcode it into your struct, that way the handle will get recycled along with your struct index:
in your example, will g be shared among all instances of the struct?

I'll ask a bit more about group utils in a few days when I've got something to show for unit collision
but is there some sort of hashtable utils? or should I just use stack to hold and pop unused hashes, releasing them back into the stack onDestroy()

Quote:
You know, I thought a feature was added to JassHelper where it would reorder struct methods so that evaluates wouldn't be used if possible, but looking at the JassHelper changelog I found no such update. I did a quick test and no such feature seems to exist, so yes, you should order your methods and modules in such a way as to avoid evaluates because they are slower.
will do
would you mind giving me a crash course lesson on correct ordering?


once again thank you very much for all the help
07-03-2011, 09:01 PM#11
Anitarf
Quote:
Originally Posted by Yrth
well
why doesn't it work to do what I did
I start the timer at map init and immediately paused it
then every time .create() was called, I also resumed the timer (regardless of knowing whether it was paused or not)
and then when the list was empty I paused the timer once again
But for some reason, unpause never worked, so I had to call TimerStart instead of unpause
I don't fully understand how to use timers yet, but this seems bad.
StartTimer was everything I've ever seen anyone use in these cases, actually. Look at TimedLoop for an example.

Quote:
in your example, will g be shared among all instances of the struct?
Notice that g is not a static group, so each struct instance creates its own, until a struct instance gets recycled in which case the group would still be there from the previous instance since I don't destroy it in the onDestroy method.

Quote:
I'll ask a bit more about group utils in a few days when I've got something to show for unit collision
but is there some sort of hashtable utils? or should I just use stack to hold and pop unused hashes, releasing them back into the stack onDestroy()
Well, for hashtables, there's Table although what it does is a bit different from GroupUtils and TimerUtils: it doesn't give you whole hashtables, it just reserves address space in a single one, but that's enough for most hashtable uses. Needing an entire hashtable for each instance of a dynamically allocated struct seems like a bit of an overkill, what would you need that for?
07-04-2011, 04:15 PM#12
Yrth
Quote:
StartTimer was everything I've ever seen anyone use in these cases, actually. Look at TimedLoop for an example.

ok
I tried a few other things to see if ResumeTimer had any effect, but it didn't do anything at all
just kinda strange

Quote:
Notice that g is not a static group, so each struct instance creates its own, until a struct instance gets recycled in which case the group would still be there from the previous instance since I don't destroy it in the onDestroy method.

ohhhh
I never realized objects got recycled like that
that's really cool
I'm guessing this applies to hashtables as well

Quote:
Needing an entire hashtable for each instance of a dynamically allocated struct seems like a bit of an overkill, what would you need that for?
a wisp wheel library
its way off topic to just post here so i'll make a new one here
i had a few questions about it anyways