Could someone please explain me this code? | Trigonometry

J

Jeshuakrc

Guest
I was looking for a way to make a "perfect" platform collisions code. I found this in this comunity:
Code:
dist=point_distance(x,y,xprevious,yprevious);
dir=degtorad(point_direction(xprevious,yprevious,x,y));
xdir=cos(dir);
ydir=-sin(dir);

x=xprevious;
y=yprevious;

repeat dist{
    if place_meeting(x+xdir,y,all){
        hsp=0;
        }else{
        x+=xdir;
        }
    if place_meeting(x,y+ydir,all){
        fall=0;
        }else{
        y+=ydir;
        }
    }
And it worked realy well, but I didn't have got in touch with trigonometry since secundary, so I searched in Google and freshened my memory, but I don't understand the math in the code. Why convert the degrees to radians? Why x+xdir / y+ydir? I realy can't get that code.
 

TsukaYuriko

🌠
Forum Staff
Moderator
The code you posted looks like a post-movement-application "cleanup fix" type of deal that "adds" precise collisions on top of whatever movement/collision mechanism you already have in place (as it first undoes your changes to the instance's position and then re-applies movement in the way it sees fit).

I can't say I'm a fan of these types of approaches as they are not only not very clean (doing something only to undo it and do it again in a different way breaks so many principles...), but also (at least) doubles the margin of error of any code they are used in conjunction with (which, as you will realize after reading this post, often happens without you realizing, as you did state that you thought it "worked really well" when, in reality, it didn't). I can't think of a reason why one would want to use it instead of properly implementing one-pass pixel perfect collisions, but let's focus on your questions instead of mine.


The degree to radian conversion is required because the cos and sin functions use radian input, not degrees. Another way to solve this is using dcos and dsin instead - these two use degrees as their input.

The addition to x and y (and the collision checking beforehand) is performed in order to apply horizontal/vertical collisions and movement. The code handles collisions more or less* pixel-perfectly as it moves the calling instance in small increments until a collision is detected (or until the maximum travel distance for the current step is reached).

*More on the "less" side, as the instance will be moved at non-integer steps unless the movement direction is evenly divisible by 90. This may or may not be intended or expected of the code - I wouldn't know because I didn't write it - but not being pixel-perfect seems like a fatal flaw for a code that is supposed to be pixel-perfect.

One more thing that feels off about the code is that it doesn't handle non-integer movement speeds. At all. repeat dist will cause the following code block to be repeated dist times - however, if dist is a non-integer such as 1.41, the fractional portion .41 will simply be ignored because there is no way to repeat a code block .41 times. It would only be repeated once, resulting in the instance's overall movement speed being lowered (most of the time, unless it already is a multiple of 1) and restricted to multiples of 1.


Overall, this is not a very elegant solution (if it can even be called that) with not-very-elegant side effects that you may not even notice when just casually testing it out. I suggest finding a different solution.
 
Last edited:
J

Jeshuakrc

Guest
Thank you so much for your answer! I'm a begginer in GameMaker (I thought if shows). I'd like a lot to see how do you would do it. I've tried several tipes of code for make colitions, and was looking for diferent tenics because of trying to solve my verlical moving platforms trouble. So, What's the better way to achieve it for you?

Sorry for my english skills
 

TsukaYuriko

🌠
Forum Staff
Moderator
I'd keep track of two separate pairs of numbers. One of them would always be integers and be used as the instance's coordinates (x and y), the other one would keep track of excess movement speed that couldn't be applied to the integer pair (basically, the fractional parts that are currently being ignored by the code you are using). Let's call those xdelta and ydelta.

Collision and movement would be handled in a similar loop, looping floor(directional movement speed) times, where directional movement speed can be retrieved by using lengthdir_x/y using the instance's movement speed and direction as arguments for horizontal/vertical movement speed, respectively, incrementing x/y by 1 per iteration.

After that part is done, I would take care of the excess movement speed by adding the fractional part of the directional movement speeds to xdelta/ydelta. If either of these variables are ever greater than or equal to 1 / less than or equal to -1, I perform an additional collision/movement check in the corresponding direction (identical in nature to the main collision/movement loop I explained above) and subtract/add 1 from/to the respective variable. All fractional movement speed will therefore "accumulate" over time and will be added once the sum of all built-up excess speed is viable to be added in an integer-based system (as soon as it exceeds or equals 1 on either the positive or negative side on either plane).

No precision is lost this way... unless we factor in that the float/double data type used by the real data type in GM:S is not precise and subject to rounding errors, but there's not really a lot we can do about that. The negative effects of that are most likely so minimal that they are unnoticeable, anyway (unlike the complete loss of all fractional movement speed).


Since you're a beginner, I'm not expecting you to be able to write your own code based on this explanation (although it includes all relevant details). However, please try to understand as much as you can, and please do try to write your own code based on this explanation - no matter how far you get before getting stuck, you'll surely have learned a thing or two or realized what would be a good way to ask for more details, and it will also allow me to provide more context-specific help as it will show me which part(s) you're struggling to understand.
 
J

Jeshuakrc

Guest
I'd keep track of two separate pairs of numbers. One of them would always be integers and be used as the instance's coordinates (x and y), the other one would keep track of excess movement speed that couldn't be applied to the integer pair (basically, the fractional parts that are currently being ignored by the code you are using). Let's call those xdelta and ydelta.
But is something I can't undertand. The code is repeated dist times. dist is ever a integer number, because it is the distance between xprevious/yprevious and x/y. Ehere does is lost precision?
 

TsukaYuriko

🌠
Forum Staff
Moderator
I'm not sure whether I understood that correctly. Is that "ever" a misspelling of "never" or your way of saying "always"? Also, are you inquiring about precision loss of the code you posted, or the method I outlined above?
 
J

Jeshuakrc

Guest
I'm not sure whether I understood that correctly. Is that "ever" a misspelling of "never" or your way of saying "always"? Also, are you inquiring about precision loss of the code you posted, or the method I outlined above?
The precicion lost on my method. The dist number, which is the numebr of times the code will be executed, will never be a float number.
 
cosine and negative sine of a direction are the normalized components of the vector that points in that direction. When you normalize a vector, you have a new vector which points in the same direction, but has a length of 1.

so every time you go through that loop (and there is nothing in the way), you move exactly 1 pixel in the direction of the vector that points from (xprevious,yprevious) to (x,y).

Note: repeating the loop dist times will only give exact results if dist is an integer. What you could do to fix this is repeat while dist > zero, reduce dist by 1 every time through the loop, and increase x and y by "xdir" and "ydir" multiplied by min(1,dist), that is to say, 1 or dist, whichever is smaller.

Another way to normalize a vector is to divide each component by the length of the vector. So another way to get xdir, and ydir, without having to use trig or point_direction would be to do this:

dist = point_distance(xprevious,yprevious,x,y);
xdir = (x - xprevious) / dist;
ydir = (y - yprevious) / dist;
 
Last edited:

TsukaYuriko

🌠
Forum Staff
Moderator
The precicion lost on my method. The dist number, which is the numebr of times the code will be executed, will never be a float number.
Then there will be no precision loss at this point of the code when used in conjunction with your implementation of movement. Using it in conjunction with a movement system that employs a method that can result in fractional numbers being returned at this point, however, will introduce precision loss.
 
J

Jeshuakrc

Guest
Ok guys... I've re-code the motion script. Now It's like this:

Code:
//Inputs
Iright=keyboard_check(ord("D"))||keyboard_check(vk_right)
Ileft=keyboard_check(ord("A"))||keyboard_check(vk_left)
Ijump=keyboard_check_pressed(vk_space)
if keyboard_check_direct(vk_escape) game_restart() //provitional

//Muvement
hsp=msp*(Iright-Ileft)

if(place_free(x,y+1)){
    if(fall<maxfall){
        fall+=grav
        }
    }else{
    fall=Ijump*-jump
    }

//Collisions
dir=point_direction(x,y,x+hsp,y+fall)
xdir=dcos(dir)
ydir=dsin(dir)

if(place_meeting(x+hsp,y+fall,all)){
    while(place_free(x+sign(hsp),y+sign(fall))){
        x+=xdir
        y-=ydir
        }
    if(place_meeting(x+hsp,y,all)) hsp=0
    if(place_meeting(x,y+fall,all)) fall=0
    if(hsp!=0&&fall!=0){
        hsp=0
        fall=0
        }
    }

//Asign muvement
x+=hsp
y+=fall
What do you think?
 
Top