View Single Post 04-22-2006, 04:55 PM #1 knutz User   Join Date: Feb 2006 Posts: 80 Submissions (3)   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?  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). 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. 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. 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. 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: JASS:``` return Location(newX,newY) endfunction``` Here's the function all together: 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 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: JASS:```function Trig__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". 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. 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. 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. 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: JASS:```function Trig__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 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__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: JASS:```function Trig__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. 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. JASS:` local real c = DistanceBetweenPoints(locC,locT) //Length of line "c" of triangle.` Now to set the new location, checking the path is clear. 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. 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. 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: JASS:```function Trig__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 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__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: JASS:```function Trig__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. 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. 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. 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. 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. 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. JASS:```function Trig__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: 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. JASS:```function Trig__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 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". 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. 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. 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. 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. 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. JASS:```function Trig__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  