Wc3C.net Moving Stuff With JASS 1 - Trigonometry (The necessary evil)
 Register Rules Get Hosted! Chat Pastebin FAQ and Rules Members List Calendar

 04-22-2006, 03: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 Last edited by knutz : 04-30-2006 at 09:49 PM.
 04-23-2006, 11:06 PM #2 vile n00b map maker   Join Date: Jul 2005 Posts: 599 Submissions (5) Awesome tutorial! Will be great for those who want to get started with it. __________________ Check out my maps: Age of Myths Deadly Games Magic & Steel New Project is kicking in! TEASER TRAILER
 04-25-2006, 12:01 PM #3 PitzerMike Alcopops   Tools & Tutorials Moderator   Join Date: Jan 2003 Posts: 2,794 Submissions (12) Yes, very good. Looking forward to your other tutorials. __________________
 04-29-2006, 08:59 AM #4 BertTheJasser xyzi - our universe     Join Date: May 2005 Posts: 742 Submissions (2) That's my daily bread. ;D Quite well explained. +Rep __________________ Note: Bye... I had a lot of fun here! Special thanks to Vexorian who helped me learn jass, the real jass and always helped me when problems occured, I would call him somehow my mentor. Pipedream, who made amazing Grimoire and helped me acclerating my map (currently at 99% finished, no developement atm). Vote for Linux Ports in general of Blizzard products: http://www.PetitionOnline.com/ibpfl/
 04-30-2006, 09:51 PM #5 knutz User     Join Date: Feb 2006 Posts: 80 Submissions (3) Change Log 1 May 2006 - Found out that the function POW() is slow, so did it the other way. __________________ AotD is the new DotA My Project My Spells
 07-28-2006, 01:41 PM #6 randability User   Join Date: Jul 2006 Posts: 4 This was most useful to me The trigometric functions aren't that hard, but the natives you used gave me a jumpstart on learning Jass.
 07-20-2007, 08:31 PM #7 ClichesAreSt00pid User   Join Date: Jan 2007 Posts: 157 I dunno how well this works and you might wanna make the 45 lower or higher but this should detect which direction a wall is from a X Y location so if you wanna use it to make sure a wall isn't there or you want to make it bounce off something this should help. Also can you include the caster system funcs in the tutorial please. JASS:```function GetCliffAngle takes real x, real y returns real local integer co = GetTerrainCliffLevel(x,y) //cliff origin local integer ce = GetTerrainCliffLevel(x+45,y) //cliff east local integer cn = GetTerrainCliffLevel(x,y+45) //cliff north local integer cw = GetTerrainCliffLevel(x-45,y) //cliff west local integer cs = GetTerrainCliffLevel(x,y-45) //cliff south if ce > co and cn <= co and cw <= co and cs <= co then //east return 0. endif if cn > co and ce <= co and cw <= co and cs <= co then //north return 90. endif if cw > co and cn <= co and ce <= co and cs <= co then //west return 180. endif if cs > co and cn <= co and cw <= co and ce <= co then //south return 270. endif return 0. //0 = east, 90 = north, 180 = west, 270 = south endfunction``` Last edited by ClichesAreSt00pid : 07-20-2007 at 08:34 PM.
 05-30-2009, 06:25 AM #8 Frozenhelfire User   Join Date: Apr 2008 Posts: 49 I just found this tutorial, and the link to the CSSafety no longer includes the function as some updates in the caster system have made it un-needed. Could someone please post/link an older caster system with the CSSafety?