HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Angle between points error... or just my stupidity

02-16-2007, 05:52 AM#1
Pyrogasm
I'm working on a spell called 'Wall of Souls'. I'm not sure what I want it to do yet, but I'm getting the mechanics down first so I can save myself effort if it's not feasable. At this point, I'm attempting to make a function that will allow certain units to pass through these "wall of souls" units, while forcing others away (the Wall of Souls units have locust).

Right now, the code runs as it should, except for the fact that every other iteration, the angle gets screwed up. I've drawn some helpful pictures to explain what happens:
  • Blue = The unit
  • Green = Wall of Souls
  • Light colored circle = area near the Wall of Souls that units should be moved out of
  • Unit Moves into range
    Zoom (requires log in)
  • Unit gets moved in opposite direction it shouls
    Zoom (requires log in)
  • Unit Gets moved back to where it started
    Zoom (requires log in)
  • Wash, rinse, repeat.

Here's my code (it's not finished):
Collapse JASS:
function Distance takes real X1, real Y1, real X2, real Y2 returns real
    return SquareRoot( (X1-X2)*(X1-X2)+(Y1-Y2)*(Y1-Y2) )
endfunction

function Get_Reverse_Angle takes real A returns real
    if A+180 > 360 then
        return A-180
    endif
    return A+180
endfunction

function Souls_Filter takes nothing returns boolean
    return GetUnitTypeId(GetFilterUnit()) == 'h000'
endfunction

function Souls_Filter_Opposite takes nothing returns boolean
    return (GetUnitTypeId(GetFilterUnit()) == 'h000') == false
endfunction

function Trig_Periodic_Move_Away_Actions takes nothing returns nothing
    local boolexpr B = Condition(function Souls_Filter)
    local boolexpr B2 = Condition(function Souls_Filter_Opposite)
    local group G = GetUnitsInRectMatching(GetPlayableMapRect(), B)
    local group G2 = CreateGroup()
    local real X1
    local real Y1
    local real X2
    local real Y2
    local real X3
    local real Y3
    local real D
    local unit Soul
    local unit U
    local location P
    local location P2

    loop
        set Soul = FirstOfGroup(G)
        exitwhen Soul == null
        set X1 = GetUnitX(Soul)
        set Y1 = GetUnitY(Soul)
        call GroupEnumUnitsInRange(G2, X1, Y1, 90.00, B2)
        loop
            set U = FirstOfGroup(G2)
            exitwhen U == null
            set X2 = GetUnitX(U)
            set Y2 = GetUnitY(U)
            set P = GetUnitLoc(Soul)
            set D = Distance (X1, Y1, X2, Y2)
            set P2 = PolarProjectionBJ(P, 90.00-D, (bj_RADTODEG * Atan2(Y2-Y1, X2-X1))) //Get_Reverse_Angle(GetUnitFacing(U)))
            set X3 = GetLocationX(P2)
            set Y3 = GetLocationY(P2)
            call SetUnitX(U, X3)
            call SetUnitY(U, Y3)
            call RemoveLocation(P)
            call RemoveLocation(P2)
            call BJDebugMsg("Moved")
            call GroupRemoveUnit(G2, U)
        endloop
//        call DestroyGroup(G2)
        call GroupRemoveUnit(G, Soul)
    endloop

    call DestroyGroup(G)
    call DestroyGroup(G2)
    call DestroyBoolExpr(B)
    call DestroyBoolExpr(B2)
    set U = null
    set Soul = null
    set G = null
    set G2 = null
    set B = null
    set B2 = null
    set P = null
    set P2 = null
endfunction

//===========================================================================
function InitTrig_Periodic_Move_Away takes nothing returns nothing
    set gg_trg_Periodic_Move_Away = CreateTrigger(  )
    call TriggerRegisterTimerEventPeriodic( gg_trg_Periodic_Move_Away, 0.8 )
    call TriggerAddAction( gg_trg_Periodic_Move_Away, function Trig_Periodic_Move_Away_Actions )
endfunction
Attached Images
File type: jpgProblem.jpg (38.8 KB)
File type: jpgProblem2.jpg (44.1 KB)
File type: jpgProblem3.jpg (44.8 KB)
02-16-2007, 08:47 AM#2
grim001
If you're going to start using movement effects you should really not use polarproject unless you like lag, instead manually manipulate the x and y values of the units using sin and cos...

newx = currentx + Cos(angle*bj_DEGTORAD)*distance
newy = currenty + Sin(angle*bj_DEGTORAD)*distance

You can use it in conjunction with my function to check pathability and map boundries if you want:

Code:
function CheckPathability takes unit u, real x, real y returns boolean
    local real ox = GetUnitX(u)
    local real oy = GetUnitY(u)
    if x > MAX_X then
        return false
    elseif x < MIN_X then
        return false
    if y > MAX_Y then
        return false
    elseif y < MIN_Y then
        return false
    endif
    call SetUnitX(u, -9472.)
    call SetUnitY(u, 9216.)
    call SetUnitPosition(PATHUNIT, x, y)
    set x = GetUnitX(PATHUNIT)-x
    set y = GetUnitY(PATHUNIT)-y
    call SetUnitX(PATHUNIT, -9472.)
    call SetUnitY(PATHUNIT, 9216.)
    call SetUnitX(u, ox)
    call SetUnitY(u, oy)
    return x*x+y*y <= 100.
endfunction

-9472, 9216 is a chosen safespot on my map where units are moved to temporarily during this function and where the PATHUNIT is stored.

MIN_X, MIN_Y and so on are the map boundries. It's best if you actually place the map boundry numbers directly into the function rather than using a variable since it will be slightly faster.

PATHUNIT is a global unit variable containing a ground unit with 32 collision radius and no model. Do not create and destroy a unit because this function is designed to be as speedy as possible and could wind up executing it hundreds of times per second. Also rapidly creating/destroying handles increases WC3's memory usage substantially.

Really a function like this is the only way to check if another unit gets in the way while you're moving around units with SetUnitX and SetUnitY unless you want the horrible lag of making WC3's check its pathing map.

Code:
function AngleBetweenPointsXY takes real x1, real y1, real x2, real y2 returns real
    return Atan2((y2-y1),(x2-x1))*bj_RADTODEG
endfunction
You may also find that function pretty useful...
so to put it all together...

Code:
set angle = AngleBetweenPointsXY(soulwallx, soulwally, unitx, unity) //or just use the math
set newx = currentx + Cos(angle*bj_DEGTORAD)*distance
set newy = currenty + Sin(angle*bj_DEGTORAD)*distance
if CheckPathability(u, newx, newy) then
    call SetUnitX(u, x)
    call SetUnitY(u, y)
endif
02-16-2007, 10:50 AM#3
The)TideHunter(
Quote:
Originally Posted by grim001
If you're going to start using movement effects you should really not use polarproject unless you like lag, instead manually manipulate the x and y values of the units using sin and cos...

newx = currentx + Cos(angle*bj_DEGTORAD)*distance
newy = currenty + Sin(angle*bj_DEGTORAD)*distance

You can use it in conjunction with my function to check pathability and map boundries if you want:

Code:
function CheckPathability takes unit u, real x, real y returns boolean
    local real ox = GetUnitX(u)
    local real oy = GetUnitY(u)
    if x > MAX_X then
        return false
    elseif x < MIN_X then
        return false
    if y > MAX_Y then
        return false
    elseif y < MIN_Y then
        return false
    endif
    call SetUnitX(u, -9472.)
    call SetUnitY(u, 9216.)
    call SetUnitPosition(PATHUNIT, x, y)
    set x = GetUnitX(PATHUNIT)-x
    set y = GetUnitY(PATHUNIT)-y
    call SetUnitX(PATHUNIT, -9472.)
    call SetUnitY(PATHUNIT, 9216.)
    call SetUnitX(u, ox)
    call SetUnitY(u, oy)
    return x*x+y*y <= 100.
endfunction

-9472, 9216 is a chosen safespot on my map where units are moved to temporarily during this function and where the PATHUNIT is stored.

MIN_X, MIN_Y and so on are the map boundries. It's best if you actually place the map boundry numbers directly into the function rather than using a variable since it will be slightly faster.

PATHUNIT is a global unit variable containing a ground unit with 32 collision radius and no model. Do not create and destroy a unit because this function is designed to be as speedy as possible and could wind up executing it hundreds of times per second. Also rapidly creating/destroying handles increases WC3's memory usage substantially.

Really a function like this is the only way to check if another unit gets in the way while you're moving around units with SetUnitX and SetUnitY unless you want the horrible lag of making WC3's check its pathing map.

Code:
function AngleBetweenPointsXY takes real x1, real y1, real x2, real y2 returns real
    return Atan2((y2-y1),(x2-x1))*bj_RADTODEG
endfunction
You may also find that function pretty useful...
so to put it all together...

Code:
set angle = AngleBetweenPointsXY(unitx, unity, soulwallx, soulwally) //or just use the math
set newx = currentx + Cos(angle*bj_DEGTORAD)*distance
set newy = currenty + Sin(angle*bj_DEGTORAD)*distance
if CheckPathability(u, newx, newy) then
    call SetUnitX(u, x)
    call SetUnitY(u, y)
endif

You are doing the same as polar projection.

Polar Projection method:

Collapse JASS:
function PolarProjectionBJ takes location source, real dist, real angle returns location
    local real x = GetLocationX(source) + dist * Cos(angle * bj_DEGTORAD)
    local real y = GetLocationY(source) + dist * Sin(angle * bj_DEGTORAD)
    return Location(x, y)
endfunction

Your method:

Collapse JASS:
newx = currentx + Cos(angle*bj_DEGTORAD)*distance
newy = currenty + Sin(angle*bj_DEGTORAD)*distance

Pretty much the same.
02-16-2007, 12:28 PM#4
Vexorian
It is not the same, since you are constantly creating locations just do remove them later. But It is not like it would really lag that much...
02-16-2007, 03:08 PM#5
The)TideHunter(
Quote:
Originally Posted by Vexorian
It is not the same, since you are constantly creating locations just do remove them later. But It is not like it would really lag that much...

I mean the method is literally the same.
Calculation of the new x and y is the same in both examples.
02-16-2007, 10:46 PM#6
grim001
Of course the calculation of X and Y is the same in both examples. After all, trig is required either way to solve that problem.

Vexorian is right in that this ability would not lag very much by itself. But it's important to get started on good coding practices for movement effects early. If he starts adding more abilities like this the unnecessary function calls and location usage will eventually take their toll on performance. It can make a very noticable difference in a "real map." The worst thing is the continuous creation and destruction of handles (the locations) since that will eat both performance and memory.

Also, there was no bound checking or collision checking with other units in his example; I was showing him how to do that without causing lag.

Upon looking at your code a little more closely:

set D = Distance (X1, Y1, X2, Y2)
set P2 = PolarProjectionBJ(P, 90.00-D, (bj_RADTODEG * Atan2(Y2-Y1, X2-X1)))

If you do that you aren't going to "push" anything back, but rather you're going to instantly teleport them out of the radius of the pushback. You should use a fixed speed like 5 or something if you want a real pushback.

Also I don't see where you even used getreverseangle since it's commented out, but if you did use it, it would cause your unit to flip back and forth like you showed in your pictures. So there's no need for that function here.
02-17-2007, 01:44 AM#7
Pyrogasm
Quote:
Originally Posted by grim001
Upon looking at your code a little more closely:

set D = Distance (X1, Y1, X2, Y2)
set P2 = PolarProjectionBJ(P, 90.00-D, (bj_RADTODEG * Atan2(Y2-Y1, X2-X1)))

If you do that you aren't going to "push" anything back, but rather you're going to instantly teleport them out of the radius of the pushback. You should use a fixed speed like 5 or something if you want a real pushback.

Also I don't see where you even used getreverseangle since it's commented out, but if you did use it, it would cause your unit to flip back and forth like you showed in your pictures. So there's no need for that function here.
I realize that it's not going to push anything. The interval on this code is supposed to be rather fast such that it won't be noticeable. If it turns out to be rather noticable, I'll employ a moving function.

Get_Reverse_Angle() was a function I was using earlier in an attempt to rectify my problem. At this point, I am not using it.
02-18-2007, 03:34 AM#8
grim001
I just thought it might look cooler if they were pushed back.

It's hard to tell what could be wrong with your code. Sometimes if you start over using a cleaner methodology the problem will correct itself.
02-20-2007, 10:20 PM#9
Pyrogasm
I'm baffled. The code still moves units in a somewhat random direction (though they do eventually get moved far enough away) at an extremely slow rate! The units will move barely any distance when the trigger runs, though they should instantly be moved outside of the radius...
Collapse JASS:
function AngleBetweenPointsXY takes real X1, real Y1, real X2, real Y2 returns real
    return Atan2((Y2-Y1),(X2-X1))*bj_RADTODEG
endfunction

function Distance takes real X1, real Y1, real X2, real Y2 returns real
    return SquareRoot( (X1-X2)*(X1-X2)+(Y1-Y2)*(Y1-Y2) )
endfunction

function Souls_Filter takes nothing returns boolean
    return GetUnitTypeId(GetFilterUnit()) == 'h000'
endfunction

function Souls_Filter_Opposite takes nothing returns boolean
    return GetUnitTypeId(GetFilterUnit()) != 'h000'
endfunction

function Souls_Set_Unit_XY takes unit Soul, unit U returns nothing
    local real X1 = GetUnitX(Soul)
    local real Y1 = GetUnitY(Soul)
    local real X2 = GetUnitX(U)
    local real Y2 = GetUnitY(U)
    local real D = Distance(X1, Y1, X2, Y2)
    local real X3 = X2 + Cos(AngleBetweenPointsXY(X1, Y1, X2, Y2)*(300.00-D))
    local real Y3 = Y2 + Sin(AngleBetweenPointsXY(X1, Y1, X2, Y2)*(300.00-D))

    call SetUnitX(U, X3)
    call SetUnitY(U, Y3)
    call BJDebugMsg("Moved")
endfunction    

function Trig_Periodic_Move_Away_Actions takes nothing returns nothing
    local boolexpr B = Condition(function Souls_Filter)
    local boolexpr B2 = Condition(function Souls_Filter_Opposite)
    local group G = GetUnitsInRectMatching(GetPlayableMapRect(), B)
    local group G2 = CreateGroup()
    local unit Soul
    local unit U
    local real X
    local real Y
//    local real X1
//    local real Y1
//    local real X2
//    local real Y2
//    local real X3
//    local real Y3
//    local real D

    loop
        set Soul = FirstOfGroup(G)
        exitwhen Soul == null
        set X = GetUnitX(Soul)
        set Y = GetUnitY(Soul)
        call GroupEnumUnitsInRange(G2, X, Y, 300.00, B2)
        loop
            set U = FirstOfGroup(G2)
            exitwhen U == null
            call Souls_Set_Unit_XY(Soul, U)
//            set X1 = GetUnitX(Soul)
//            set Y1 = GetUnitY(Soul)
//            set X2 = GetUnitX(U)
//            set Y2 = GetUnitY(U)
//            set D = Distance(X1, Y1, X2, Y2)
//            set X3 = X2 + Cos(AngleBetweenPointsXY(X1, Y1, X2, Y2)*(300.00-D))
//            set Y3 = Y2 + Sin(AngleBetweenPointsXY(X1, Y1, X2, Y2)*(300.00-D))
//            call SetUnitX(U, X3)
//            call SetUnitY(U, Y3)
//            call BJDebugMsg("Moved")
            call GroupRemoveUnit(G2, U)
        endloop
        call GroupRemoveUnit(G, Soul)
    endloop

    call DestroyGroup(G)
    call DestroyGroup(G2)
    call DestroyBoolExpr(B)
    call DestroyBoolExpr(B2)
    set U = null
    set Soul = null
    set G = null
    set G2 = null
    set B = null
    set B2 = null
endfunction

//===========================================================================
function InitTrig_Periodic_Move_Away takes nothing returns nothing
    set gg_trg_Periodic_Move_Away = CreateTrigger(  )
    call TriggerRegisterTimerEventPeriodic( gg_trg_Periodic_Move_Away, 0.5 )
    call TriggerAddAction( gg_trg_Periodic_Move_Away, function Trig_Periodic_Move_Away_Actions )
endfunction
A little help, maybe?
02-21-2007, 06:31 AM#10
Pyrogasm
Ah! I've found my error. I forgot my bj_DEGTORAD...

Silly me. grim001, I appreciate the help; and you too, The)TideHunter(.
Rep'd
02-21-2007, 08:35 AM#11
grim001
You also need to move the distance outside of the parenthesis or you're still going to get incorrect results
02-21-2007, 12:01 PM#12
Rising_Dusk
Grim's right.

Collapse JASS:
    local real X3 = X2 + Cos(AngleBetweenPointsXY(X1, Y1, X2, Y2)*(300.00-D))
    local real Y3 = Y2 + Sin(AngleBetweenPointsXY(X1, Y1, X2, Y2)*(300.00-D))

Should be --

Collapse JASS:
    local real X3 = X2 + (300.00-D)*Cos(AngleBetweenPointsXY(X1, Y1, X2, Y2))
    local real Y3 = Y2 + (300.00-D)*Sin(AngleBetweenPointsXY(X1, Y1, X2, Y2))
02-21-2007, 03:31 PM#13
grim001
but now you forgot the bj_DEGTORAD
02-21-2007, 04:31 PM#14
Rising_Dusk
Most normal people put that in the AngleBetweenPointsXY(..) function call.
02-21-2007, 04:53 PM#15
grim001
nope, what is in the anglebetweenpoints is bj_RADTODEG.

When it calculates the angle it is in radians then it is converted to degrees before being returned by the fucntion

then you have to convert it back to radians again for newx/newy calculation

if you inline the math the RADTODEG and DEGTORAD can cancel out.