View Single Post
Old 04-22-2006, 04:55 PM   #1
knutz
User
 
knutz's Avatar
 
Join Date: Feb 2006
Posts: 80

Submissions (3)

knutz will become famous soon enough (44)knutz will become famous soon enough (44)

Default Moving Stuff With JASS 1 - Trigonometry (The necessary evil)

Introduction
Since I left school 17 years ago, I thought I'd never have to use the stuff (trig) again, but then I discovered I needed it for the most important thing of all. SPELLS! SPELLS! SPELLS! Also, I thought I'd better write it all down before I forgot.

When your hero needs to push, pull or swing another unit or doodad, or if you just need to know where to place your special effects, trigonometry is the way to go, as plotting positions is like drawing a right-angled triangle. See the similarities in these two diagrams?
Click image for larger version

Name:	Triangle_noted.jpg
Views:	202
Size:	6.1 KB
ID:	4878Click image for larger version

Name:	CasterTriangle.jpg
Views:	264
Size:	3.8 KB
ID:	4879


Trig
Trig formulas. We may be referring to some of these in this tutorial, but all are helpful. We will also need to refer to the green triangle (above) quite a bit. If I mention "c", I'm referring to the length of side c. If I mention "A", I'm referring to the size of angle "A", so if you find it hard to follow, scroll back up here for a look-see.

a2+b2=c2
Sin A = a / c, Sin B = b / c
Cos A = b / c, Cos B = a / c
Tan A = a / b, Tan B = b / a
arcsine(a / c) = A, arcsine(b / c) = B
arccosine(b / c) = A, arccosine(a / c) = B
arctangent(a / b) = A, arctangent(b / a) = B
radian measure times radius = arc length


Limitations
IMPORTANT: Use a new map or make sure you save a backup! I don't want to be held responsible for any crashes. This works fine for me, but I don't know what you've got on your map.

IMPORTANT: If you move a unit off the playable map area the map will crash. There are many functions that check for this, but I won't be writing one here. If you want to use this code in your map, replace all instances of "CS_SafeX" and "CS_SafeY" (from Vexorian's Caster System) with the functions you choose to use, or download and implement the latest Caster System.

NOTE: I also make use of Vexorian's CheckPathability() function to ensure the stuff I move doesn't go through trees and over cliffs. You can use this, or replace this with whatever you normally use.

NOTE: Used as is, these functions don't actually look cool. You'd need to increment the positioning, looping the movement in small amounts to get it to look realistic, but that will be covered in the next tutorial (Moving Stuff With JASS 2 - Realistic Movement).


Degrees and Radians
There are 2 main standards for measuring angles. Degrees and Radians. 360 degrees = 2*PI radians. In this tutorial, we will use Degrees to measure angles, since I understand them. Trouble is, JASS uses Radians in it's trig functions. I don't understand them. There are, however, a couple of global constants which convert between the two: bj_DEGTORAD and bj_RADTODEG. So when you see those in the code, that's what they're doing.


Now the intro is over....let's get started.

###################################################

Positioning Helper Function
I wrote a function to help me with repositioning, which I'd like to share with you now.
It uses the formulae "Cos A = b / c" and "Sin A = a / c". The function takes the "c" distance, the "A" angle, the point of origin (usually the position of the caster) and a boolean for whether or not you want to check pathability.

What we are returning is the position where sides c & a of the triangle meet, the destination. The first thing we need to do is find the X,Y coordinates of the point of origin (on the triangle, this is where sides c & b meet).

Collapse JASS:
function knutz_NewPos takes location origin, real c, real A, boolean checkpath returns location
    local real oX = GetLocationX(origin)
    local real oY = GetLocationY(origin)

This established, we need to use Cos A = b / c to find the length of side "b", the difference in X from origin to destination.

Collapse JASS:
    local real newX = CS_SafeX(oX+(Cos(A*bj_DEGTORAD)*c))

Notice here that we multiplied the angle by bj_DEGTORAD before calling Sin. We do this because the trig functions in JASS are set to Radians mode, and we start off with degrees. Then we add the X of the origin. Now we have the X coordinate of the destination.
Now, we need to use Sin A = a / c to find the length of side "a", the difference in Y from origin to destination.

Collapse JASS:
    local real newY = CS_SafeY(oY+(Sin(A*bj_DEGTORAD)*c)) 

Same explanation as above, but for the Y coordinate. Now it's time to see if we CAN move stuff to this location.

Collapse JASS:
    if ((check_path == true) and (CheckPathability(newX,newY) != true)) then
        return null
    endif 

What this section does is checks for obsticles, and returns a null if the way is blocked. The calling function should filter out the nulls, as I'm not sure what would happen if you assign a null location to an object.
Then the finale, returning the destination:

Collapse JASS:
    return Location(newX,newY) 
endfunction

Here's the function all together:

Collapse JASS:
function knutz_NewPos takes location origin, real c, real A, boolean check_path returns location
    local real oX = GetLocationX(origin)
    local real oY = GetLocationY(origin)
    local real newX = CS_SafeX(oX+(Cos(A*bj_DEGTORAD)*c))
    local real newY = CS_SafeY(oY+(Sin(A*bj_DEGTORAD)*c)) 
    if ((check_path == true) and (CheckPathability(newX,newY) != true)) then
        return null
    endif 
    return Location(newX,newY) 
endfunction

This function is used in every section from here on. If you are using this in a map, you will need to copy this into your custom text section, below the functions you use in place of CS_SafeX, CS_SafeY and CheckPathability. So all we need to find out now is the point of origin, the angle and distance to the destination and whether or not we want stuff to go through trees and up cliffs.

####################################################

Moving Stuff in a Straight Line
Click image for larger version

Name:	StraightLine.JPG
Views:	125
Size:	6.0 KB
ID:	4880

OK, so now we have our helper function to reposition our stuff, we just need to find the information it requires. When moving stuff in a straight line, this is fairly easy. Set up a trigger to act on a spell event, and we'll play with the "actions" function.

What we need to know for the helper function:
  • The point of origin( We'll assume it's the position of the caster. )
  • The angle from the caster to the destination (angle "A")
  • The distance from the point of origin to the destination (distance between the caster and target + the move distance. The length of side "c")

Once we know these readily available variables, we can work out the rest. Here's how we get them:

Collapse JASS:
function Trig_<SomeTrigger>_Actions takes nothing returns nothing
    local unit caster = GetTriggerUnit()  //Assuming this is the triggeraction function of a spell event trigger
    local unit target = GetSpellTargetUnit()  //Assuming the spell targets a unit
    local location locC = GetUnitLoc(caster)
    local location locT = GetUnitLoc(target)

Now we have the point of origin (LocC), using the location of the target (LocT) we can find both angle "A" and distance "c".

Collapse JASS:
    local real A = AngleBetweenPoints(locC,locT) //Now we know angle A!!!
    local real OLDc = DistanceBetweenPoints(locC,locT) //Length of line "c" of original triangle.
    local real c = OLDc + 100.00 //Lets assume we're moving the target back 100. Now we have final lenth of "c".

We have all we need to use the helper function now. If we assume we don't want the target to go over cliffs or through trees, we'll ask the function to check the pathability.

Collapse JASS:
    local location NewLoc = knutz_NewPos(locC,c,A,true) //Calls the function NewPos to set the new position of the target.

Now to move the target. We need to remember that the helper function returns a null if the way is blocked. So if it is, we'll skip moving the target.

Collapse JASS:
    if (NewLoc != null) then //If the path is blocked, knutz_NewPos returns a null value, so we only move the target if it doesn't.
        call SetUnitPositionLoc(target,NewLoc) //Moves the target
    endif

Done! But before we end the function we should clean up any memory leaks.

Collapse JASS:
    //Don't forget to clean up the memory leaks!
    set caster = null
    set target = null
    call RemoveLocation(locC)
    set locC = null
    call RemoveLocation(locT)
    set locT = null
    call RemoveLocation(NewLoc)
    set NewLoc = null
endfunction

Now let's see it all together:

Collapse JASS:
function Trig_<SomeTrigger>_Actions takes nothing returns nothing
    local unit caster = GetTriggerUnit()  //Assuming this is the triggeraction function of a spell event trigger
    local unit target = GetSpellTargetUnit()  //Assuming the spell targets a unit
    local location locC = GetUnitLoc(caster)
    local location locT = GetUnitLoc(target)
    local real A = AngleBetweenPoints(locC,locT) //Now we know angle A!!!
    local real OLDc = DistanceBetweenPoints(locC,locT) //Length of line "c" of original triangle.
    local real c = OLDc + 100.00 //Lets assume we're moving the target back 100. Now we have final lenth of "c".
    local location NewLoc = knutz_NewPos(locC,c,A,true) //Calls the function NewPos to set the new position of the target.
    if (NewLoc != null) then //If the path is blocked, knutz_NewPos returns a null value, so we only move the target if it doesn't.
        call SetUnitPositionLoc(target,NewLoc) //Moves the target
    endif
    //Don't forget to clean up the memory leaks!
    set caster = null
    set target = null
    call RemoveLocation(locC)
    set locC = null
    call RemoveLocation(locT)
    set locT = null
    call RemoveLocation(NewLoc)
    set NewLoc = null
endfunction

####################################################

Moving in a Curve by degrees
Click image for larger version

Name:	Move on Curve.JPG
Views:	102
Size:	5.4 KB
ID:	4881

So we want our caster to swing the enemy sideways along a curve (or just place stuff in a circle from a central location). We already have the useful "NewPos" function, we just have to alter our "Trig_<SomeTrigger>_Actions" a bit for this. Instead of changing the lenth of "c", we'll change the size of "A".

What we need to know for the helper function:
  • The point of origin( We'll assume it's the position of the caster. )
  • The angle from the caster to the destination (angle "A"+ or - angle of move)
  • The distance from the point of origin to the destination (distance between the caster and target. The length of side "c")
Once we know these readily available variables, we can work out the rest. Here's how we get them:
Collapse JASS:
function Trig_<SomeTrigger>_Actions  takes nothing returns nothing
    local unit caster = GetTriggerUnit()  //Assuming this is the triggeraction function of a spell event trigger
    local unit target = GetSpellTargetUnit()  //Assuming the spell targets a unit
    local location locC = GetUnitLoc(caster)
    local location locT = GetUnitLoc(target)

Now to retrieve the original angle and adjust it for the move. If you subtract from the original angle the movement will be clockwise, if you add, anti-clockwise.

Collapse JASS:
    local real OldA = AngleBetweenPoints(locC,locT) //Now we know original angle A!!!
    local real A = OldA + 15.00 //Let's assume we're moving the unit 15 degrees.

Next, we will find out the "c" distance.

Collapse JASS:
    local real c = DistanceBetweenPoints(locC,locT) //Length of line "c" of  triangle.

Now to set the new location, checking the path is clear.

Collapse JASS:
    local location NewLoc = knutz_NewPos(locC,c,A,true) //Calls the function NewPos to set the new position of the target.

At last, we move the target, remembering that if the path is blocked a null value will be returned.

Collapse JASS:
    if (NewLoc != null) then //If the path is blocked, knutz_NewPos returns a null value, so we only move the target if it doesn't.
        call SetUnitPositionLoc(target,NewLoc) //Moves the target
    endif

Finally, we clean up any memory leaks before ending the function.

Collapse JASS:
    //Don't forget to clean up the memory leaks!
    set caster = null
    set target = null
    call RemoveLocation(locC)
    set locC = null
    call RemoveLocation(locT)
    set locT = null
    call RemoveLocation(NewLoc)
    set NewLoc = null
endfunction

Now let's see it all together:

Collapse JASS:
function Trig_<SomeTrigger>_Actions  takes nothing returns nothing
    local unit caster = GetTriggerUnit()  //Assuming this is the triggeraction function of a spell event trigger
    local unit target = GetSpellTargetUnit()  //Assuming the spell targets a unit
    local location locC = GetUnitLoc(caster)
    local location locT = GetUnitLoc(target)
    local real OldA = AngleBetweenPoints(locC,locT) //Now we know original angle A!!!
    local real A = OldA + 15.00 //Let's assume we're moving the unit 15 degrees.
    local real c = DistanceBetweenPoints(locC,locT) //Length of line "c" of  triangle.
    local location NewLoc = knutz_NewPos(locC,c,A,true) //Calls the function NewPos to set the new position of the target.
    if (NewLoc != null) then //If the path is blocked, knutz_NewPos returns a null value, so we only move the target if it doesn't.
        call SetUnitPositionLoc(target,NewLoc) //Moves the target
    endif
    //Don't forget to clean up the memory leaks!
    set caster = null
    set target = null
    call RemoveLocation(locC)
    set locC = null
    call RemoveLocation(locT)
    set locT = null
    call RemoveLocation(NewLoc)
    set NewLoc = null
endfunction

####################################################

Moving in a Curve by distance
Click image for larger version

Name:	Move on Curve.JPG
Views:	102
Size:	5.4 KB
ID:	4881

So we want our caster to swing the enemy sideways along a curve (or just place stuff in a circle from a central location). We already have the useful "NewPos" function, we just have to alter our "Trig_<SomeTrigger>_Actions" a bit for this. Instead of changing the lenth of "c", we'll change the size of "A". As we're using distance along the curve though, we'll have to refer to another formula:

radian measure times radius = arc length
(the arc length being our move distance, the radius being our "c" distance)

We need to find our new angle from the distance along the curve. So we must divide the move distance by "c", then convert the radians to degrees before we use our knutz_NewPos function.

What we need to know for the helper function:
  • The point of origin( We'll assume it's the position of the caster. )
  • The angle from the caster to the destination (angle "A"+ or - angle of move)
  • The distance from the point of origin to the destination (distance between the caster and target. The length of side "c")

Let's get all the info:

Collapse JASS:
function Trig_<SomeTrigger>_Actions takes nothing returns nothing
    local unit caster = GetTriggerUnit()  //Assuming this is the triggeraction function of a spell event trigger
    local unit target = GetSpellTargetUnit()  //Assuming the spell targets a unit
    local location locC = GetUnitLoc(caster)
    local location locT = GetUnitLoc(target)

As we need the "c" distance to find the angle adjustment, let's get that first.

Collapse JASS:
    local real c = DistanceBetweenPoints(locC,locT) //Length of line "c" of  triangle.

Now to sort out the angle using our new formula. Remember, subtract from the original angle for clockwise, add for anti-clockwise.

Collapse JASS:
    local real OldA = AngleBetweenPoints(locC,locT) //Now we know original angle A!!!
    local real A = OldA + ((400.00/c)*bj_RADTODEG) //Let's assume we're moving the unit 400 along the curve.

Now to set the new location, checking the path is clear.

Collapse JASS:
    local location NewLoc = knutz_NewPos(locC,c,A,true) //Calls the function NewPos to set the new position of the target.

At last, we move the target along the curve! Remembering that a blocked path returns a null location.

Collapse JASS:
    if (NewLoc != null) then //If the path is blocked, knutz_NewPos returns a null value, so we only move the target if it doesn't.
        call SetUnitPositionLoc(target,NewLoc) //Moves the target
    endif

Finally, we clean up the memory leaks before ending the function.

Collapse JASS:
    //Don't forget to clean up the memory leaks!
    set caster = null
    set target = null
    call RemoveLocation(locC)
    set locC = null
    call RemoveLocation(locT)
    set locT = null
    call RemoveLocation(NewLoc)
    set NewLoc = null
endfunction

EASY AS PI! Now let's see it all together.

Collapse JASS:
function Trig_<SomeTrigger>_Actions takes nothing returns nothing
    local unit caster = GetTriggerUnit()  //Assuming this is the triggeraction function of a spell event trigger
    local unit target = GetSpellTargetUnit()  //Assuming the spell targets a unit
    local location locC = GetUnitLoc(caster)
    local location locT = GetUnitLoc(target)
    local real c = DistanceBetweenPoints(locC,locT) //Length of line "c" of  triangle.
    local real OldA = AngleBetweenPoints(locC,locT) //Now we know original angle A!!!
    local real A = OldA + ((400.00/c)*bj_RADTODEG) //Let's assume we're moving the unit 400 along the curve.
    local location NewLoc = knutz_NewPos(locC,c,A,true) //Calls the function NewPos to set the new position of the target.
    if (NewLoc != null) then //If the path is blocked, knutz_NewPos returns a null value, so we only move the target if it doesn't.
        call SetUnitPositionLoc(target,NewLoc) //Moves the target
    endif
    //Don't forget to clean up the memory leaks!
    set caster = null
    set target = null
    call RemoveLocation(locC)
    set locC = null
    call RemoveLocation(locT)
    set locT = null
    call RemoveLocation(NewLoc)
    set NewLoc = null
endfunction

####################################################

Moving something sideways In a straight line

Ok. This one freaked me out until I realised it was just 3 right-angled triangles where our original "c" distance becomes the second triangle's "b" distance. Only now that "b" isn't parallel to the east-west of the map, we can't use "b2" and "a2" for "x" and "y". We'll have to come up with yet a third triangle to do that. Confused? I was too, but to help, I'll give the suffix "2" to the second triangle and "3" to the third. And here's a little diagram to explain what I just said:
Click image for larger version

Name:	3Triangles.JPG
Views:	139
Size:	11.7 KB
ID:	4882

Formulas we will refer to here are "arctangent of (a/b) = A" and "a2+b2=c2"

What we need to know for the helper function:
  • The point of origin( We'll assume it's the position of the caster. )
  • The angle from the caster to the destination (angle "A"+ or - angle of move)
  • The distance from the point of origin to the destination (we will need to work this out with trig first)

Let's get all the info:
Note: I'll be creating allot of unnecessary variables to help you follow what I'm doing.
Collapse JASS:
function Trig_<SomeTrigger>_Actions takes nothing returns nothing
    local unit caster = GetTriggerUnit()  //Assuming this is the triggeraction function of a spell event trigger
    local unit target = GetSpellTargetUnit()  //Assuming the spell targets a unit
    local location locC = GetUnitLoc(caster)
    local location locT = GetUnitLoc(target)

Now to get the info we need about triangle 1

Collapse JASS:
    local real c = DistanceBetweenPoints(locC,locT) //Length of line "c" of  triangle.
    local real A = AngleBetweenPoints(locC,locT) //Now we know original angle A!!!

Now for the second triangle. This is joined to the first on the "c" side, which has become "b2".

Collapse JASS:
    local real b2 = c // unnecessary step but will help you follow allong
    local real a2 = 100.00 // the sideways distance we want to move the target
    local real c2 = SquareRoot(Pow(a2,2)+Pow(b2,2)) //as "a2+b2=c2" then c = the square root of a2+b2"
    local real A2 = Atan2(a2,b2)*bj_RADTODEG //Atan2(a2,b2) returns the arctangent of a2/b2 in radians. Convert'em to degrees.

Now for the third triangle, the one we use for our final positioning.

Collapse JASS:
    local real A3 = A+A2 // unnecessary step but will help you follow allong
    local real c3 = c2 // unnecessary step but will help you follow allong

Next, we call our helper function for the location of the destination, and to check the pathing.

Collapse JASS:
    local location NewLoc = knutz_NewPos(locC,c3,A3,true) //Calls the function NewPos to set the new position of the target.

Remembering that a blocked path will return a null value, move the target if it doesn't.

Collapse JASS:
    if (NewLoc != null) then //If the path is blocked, knutz_NewPos returns a null value, so we only move the target if it doesn't.
        call SetUnitPositionLoc(target,NewLoc) //Moves the target
    endif

Finally, clean up the memory leaks and end the function.

Collapse JASS:
    //Don't forget to clean up the memory leaks!
    set caster = null
    set target = null
    call RemoveLocation(locC)
    set locC = null
    call RemoveLocation(locT)
    set locT = null
    call RemoveLocation(NewLoc)
    set NewLoc = null
endfunction

Now let's see it all together.

Collapse JASS:
function Trig_<SomeTrigger>_Actions takes nothing returns nothing
    local unit caster = GetTriggerUnit()  //Assuming this is the triggeraction function of a spell event trigger
    local unit target = GetSpellTargetUnit()  //Assuming the spell targets a unit
    local location locC = GetUnitLoc(caster)
    local location locT = GetUnitLoc(target)
    local real c = DistanceBetweenPoints(locC,locT) //Length of line "c" of  triangle.
    local real A = AngleBetweenPoints(locC,locT) //Now we know original angle A!!!
    local real b2 = c // unnecessary step but will help you follow allong
    local real a2 = 100.00 // the sideways distance we want to move the target
    local real c2 = SquareRoot((a2*a2)+(b2*b2)) //as "a2+b2=c2" then c = the square root of a2+b2"
    local real A2 = Atan2(a2,b2)*bj_RADTODEG //Atan2(a2,b2) returns the arctangent of a2/b2 in radians. Convert'em to degrees.
    local real A3 = A+A2 // unnecessary step but will help you follow allong
    local real c3 = c2 // unnecessary step but will help you follow allong
    local location NewLoc = knutz_NewPos(locC,c3,A3,true) //Calls the function NewPos to set the new position of the target.
    if (NewLoc != null) then //If the path is blocked, knutz_NewPos returns a null value, so we only move the target if it doesn't.
        call SetUnitPositionLoc(target,NewLoc) //Moves the target
    endif
    //Don't forget to clean up the memory leaks!
    set caster = null
    set target = null
    call RemoveLocation(locC)
    set locC = null
    call RemoveLocation(locT)
    set locT = null
    call RemoveLocation(NewLoc)
    set NewLoc = null
endfunction

####################################################

Author's Notes
For the experienced JASSers on this site, thanks for answering all my stupid questions. I hope I can repay the favours by answering some for other members now.

Coming Tutorials:
(If this one is approved that is)
  • Moving Stuff With JASS 2 -Realistic movement (incrementing using distance and speed.)
  • Moving Stuff With JASS 3 - 3D movement (yes more trig! (Although I'll have to learn this first, which I will as I need it for a system I'm developing))
__________________
AotD is the new DotA

My Project

My Spells
knutz is offline   Reply With Quote
Sponsored Links - Login to hide this ad!