• Hey! Guest! The 40th (!!!) GMC Jam will take place between February 25th, 12:00 UTC to March 1st 12:00 UTC. Why not join in this very special anniversary jam! Click here to find out more!

3D Rotate a Rectangle Rage (Now with Gifs! Oh boy!)

RujiK

Member
LETS SUPPOSE THAT I HAVE THIS RECTANGLE!

Width : 10
Length: 11
Height: 12

If I want to ROTATE IT HORIZONTALLY, the math to find the dimensions is easy:

Width: lengthdir_x(10,direction);
Length: lengthdir_y(11,direction);
Height: 12

BUT IF I rotate it VERTICALLY and HORIZONTALLY

Width: ?
Length: ?
Height: ?

By 2D brain cannot comprehend. I would appreciate any help and give generous likes if that is any incentive.

Thanks!
 

Joe Ellis

Member
I'd just use a matrix, cus once you apply 2 types of rotation, the human brain is gone,
if you apply the correct matrix to the vertices, it should end up looking how you want, if not then you probably applied the rotations in the wrong order
 

RujiK

Member
@Joe Ellis Thanks for your comment!! Unfortunately, (I think) a matrix won't work in this case. I don't want to actually draw the cube, I just want the actual dimension numbers.

If a matrix CAN give me the dimensions, would you mind putting that information into a baby spoon and shoving it into my mouth?

@DarthTenebris
My googling also led me to some rotation matrix stuff as well, but I'm really bad at converting math equations into code. At a glance, I don't think GMLinear will give me what I'm after, but I'll need to take a closer look to be sure. Also I don't actually want to draw anything, I just want the width, length, and height values.

I really appreciate your help guys, but I don't think I have the answer I want yet. (Or maybe I'm just dumb.)
 

Joe Ellis

Member
I'm pretty sure you can achieve what you want with gm's matrix functions, mainly matrix_build, matrix_multiply (for combining rotations)
and the matrix_transform_vertex which as the name states will transform the coordinates.

For the thing you want, I think you need to apply pitch(vertical) rotation first, then yaw(horizontal).
If you want an explanation, here is my attempt:

Try to think of what's actually happening to the object one step at a time
Pitch basically rotates it along the Y axis, which is a horizontal vector, so turning makes it rotate\tile upwards or downwards
Then you apply yaw, which is rotating it around the upwards\downwards\z vector, which changes which way it's facing horizontally, or like north south east west.
Even now when I'm used to this, it's still abit hard for the brain to deal with, but I've learned a way to visualize what it does. Like rotating "around" a certain vector basically makes it tilt side to side around that line that's going straight through the middle. So then, think of a line that's pointing directly downwards, into a car, the rotation around that line makes the car turn around. So the vector pointing vertically performs the horizontal rotation. And to make something tilt upwards\downwards you need a vector that is shooting horizontally.
This part is more variable, cus there are 360 degrees to rotate around, but people usually make sure that the X axis is pointing forwards (for a character, car or plane), then Pitch, the Y axis rotation will make it look up and down, and X rotation will make it tilt side to side.
So yeah for the block you're rotating, as it's straight along each axis, you can just rotate with the X or Y axis to tilt it and the Z to make it face a certain way. But always do the Z rotation last, if you do it before, the vertical rotation will be applied to it along the x or y axis, after it's been rotated, so it'll make it face upwards\downwards but at a skewey angle. It's kind of what I was saying about the order that the rotations are done in, if you actually visualize looking along the Y vector, and rotate, it makes it look up and down, so if you applied Z rotation before this, it'd be facing another way, basically not directly right, and then you'd be rotating through the Y axis vector still, and it'd rotate in some weird kind of way that no one wants to see lol.
Well, I hope this essay can be helpful.. it might not make much sense at first, but it might help while you're doing this stuff
 

GMWolf

aka fel666
Hmm, but if you rotate a rectangle along two of its axes, it can no longer be drawn as an axis aligned rectangle right?
*Frantically turning a piece of paper around trying to figure this out*

I also think a matrix is the easiest way to go though. It's probably a bit more math than the strict minimum but it's a well understood mathematical tool you can apply to any transformation you need.


You would however need to apply it to each vertex of your rectangle.

If you need to get width and height out of it, well again, is that possible? Does rotating a rectangle along two axes still get you a rectangle? (I actually don't know but it seems to me like you wouldn't).
 

Joe Ellis

Member
Hmm, but if you rotate a rectangle along two of its axes, it can no longer be drawn as an axis aligned rectangle right?
*Frantically turning a piece of paper around trying to figure this out*

I also think a matrix is the easiest way to go though. It's probably a bit more math than the strict minimum but it's a well understood mathematical tool you can apply to any transformation you need.


You would however need to apply it to each vertex of your rectangle.

If you need to get width and height out of it, well again, is that possible? Does rotating a rectangle along two axes still get you a rectangle? (I actually don't know but it seems to me like you wouldn't).
Well it's a cuboid technically cus it has width length and height, but has been transformed, but it still has coordinates, normals & tangents that are 90 degrees apart from eachother, so it's still a cuboid in it's own relative sense after it's been rotated, man I sound like such a 3d nerd, I don't mean to be, but this quite an easy problem, and you don't need to actually learn any complex math to do it, just learn how to use matrices in gm

-Edit
Currently coming up with that baby spoon plan for you, stay tuned
 
Last edited:

Joe Ellis

Member
GML:
///rotate_cuboid(width, length, height, yaw, pitch)

var
w = argument0 * 0.5,
l = argument1 * 0.5,
h = argument2 *0.5,
m = matrix_build(0, 0, 0, 0, argument4, argument3, 1, 1, 1);

var
x1 = -w,
y1 = -l,
z1 = -h,
x2 = w,
y2 = l,
z2 = h,
a;

a[0] = matrix_transform_vertex(m, x1, y1, z1)
a[1] = matrix_transform_vertex(m, x2, y1, z1)
a[2] = matrix_transform_vertex(m, x1, y2, z1)
a[3] = matrix_transform_vertex(m, x2, y2, z1)
a[4] = matrix_transform_vertex(m, x1, y1, z2)
a[5] = matrix_transform_vertex(m, x2, y1, z2)
a[6] = matrix_transform_vertex(m, x1, y2, z2)
a[7] = matrix_transform_vertex(m, x2, y2, z2)

return a
This should return an array of 8 3d vertices, so then you've got the coordinates you need. The coordinates outputted are centered around zero, so if you need to move them into a specific place in the level you can just add the exact coordinates to them

The order I made it in is, top left, top right, bottom left, bottom right, and 3D'ly the highest layer is first, then the lower
 
Last edited:

GMWolf

aka fel666
Well it's a cuboid technically cus it has width length and height, but has been transformed, but it still has coordinates, normals & tangents that are 90 degrees apart from eachother, so it's still a cuboid in it's own relative sense after it's been rotated, man I sound like such a 3d nerd, I don't mean to be, but this quite an easy problem, and you don't need to actually learn any complex math to do it, just learn how to use matrices in gm

-Edit
Currently coming up with that baby spoon plan for you, stay tuned
Yes, I meant, can you still render it as a rectangle, which I think it's what Rujik was getting at?
If you just rotate along the X or Y axis you can squish the rectangle to make it appear like it is rotating. (Like the equations in the op).
If you do two axes though I don't think the trick works. So full vertex transformation is needed.

Btw, you can just set you matrix transform using matrix set rather than transforming the vertices on the CPU.
That might break the batch (or not, depending on how YYG implemted their sprite batch).
 

RujiK

Member
@SSJCoder Thanks for the link. I actually clicked on that during my googling but didn't scroll down far enough to find the relevant info.

@GMWolf I guess technically I just wanted the MAX width/length for the rotation info. So the widest and tallest points. Thanks for your matrix input too.

@Joe Ellis Ahhh! You are the man! :D :D Here is my selfie for you.


Thanks for all your help guys!
 

RujiK

Member
Hey guys! Remember me? I'm stuck again! Oh boy!

So I got rotating around zero working fine, but for the life of me I can't figure out how to rotate around an arbitrary axis. BEHOLD THIS GIF:


MY GOAL is to have the "Planet" in the middle rotate around the red axis. (This is a 3d simulation) It KIND OF works, but I just don't know enough about matrices to fix it. At many angles everything goes wonky.
Here is the basic code. (Everything should work in the draw event)
GML:
COUNTER+=1;


x0 = 210;     y0 = 210;     z0 = 21;//START positions
x1 = mouse_x; y1 = mouse_y; z1 = 60;//END positions.
xx = 240;     yy = 240;     zz = 23;//random points that I want to circle around the START and END positions

distance = point_distance_3d(x0,y0,z0,x1,y1,z1);
normal_z=clamp((z0-z1)/distance,-1,1);
pitch = darcsin(normal_z);
dir = point_direction(x0,y0,x1,y1);

var _mat = matrix_build(x0,y0,z0,0,pitch,dir + COUNTER,1,1,1);
pos = matrix_transform_vertex(_mat,xx,yy,zz);

//DRAW THE LINES
draw_circle(x0,y0-z0,4,1); //start point
draw_circle(x1,y1-z1,4,1); //end point
draw_circle(pos[0],pos[1]-pos[2],4,1); //middle point

draw_line_color(x0,y0-z0,x1,y1-z1,c_red,c_red); //line from start to end
draw_line(x0,y0-z0,pos[0],pos[1]-pos[2]); //line from start to middle rotating planet
draw_line(x1,y1-z1,pos[0],pos[1]-pos[2]); //line from END to middle rotating planet
If it's not obvious, I want the distance from the "planet" to the line to remain constant and just revolve around the line. I messed around with this all weekend but I haven't been able to fix it. I'd appreciate any help. Thanks!
 

SSJCoder

Member
well can't you just translate, then rotate, then translate back? then the rotation should happen relative to the place where you translated.
 

RujiK

Member
Thanks for your comment! I've barely used matrices until a week ago so I'm not sure I follow you.

I assume you mean translate the coordinates in relation to the vector but I was under the impression that the matrix_build command did that for you with the first 3 arguments:
matrix_build(x0,y0,z0,0,pitch,dir,1,1,1);

I am googling "3d vector coordinate transformation" but there are apparently a lot of different methods (Euler Angles, Eular Axis, Quaternions, Rotation Matrix) and I'm not sure which one is the one I need.

I would appreciate it if you could give me some more information with the knowledge that I suck at 3d math. Again, thanks for your comment though.
 

SSJCoder

Member
so, for every (x, y, z), a translation simply adds to each, so, you would have (x+xtranslation, y+ytranslation, z+ztranslation), that is the concept. However, since I don't use matrices (in GameMaker) someone else can probably tell you exactly how to apply it to your program.

by the way, translation simply means "move", so that's what you're doing. (also you need to be googling "translation" not transformation, in case you were trying to google the term I used here lol)

EDIT:
there is a translation section in the matrix building:

so, you need to build two translation matrices maybe, one for translating to your origin (place from where you rotate), then one to translate back, then in-between, apply the rotation matix.
 
Last edited:

GMWolf

aka fel666
This might help you : https://en.m.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angle
It allows you to define an axis, and an angle to to get a rotation matrix.

Remember how transformations work. If you apply a rotation matrix at a point at (0,0) it won't move. If you apply it to a point at (10, 0), it will rotate around the axis, but keep a distance of 10.

So usually, you would transform the whole system so the center of rotation is at the origin, apply your rotation matrix, then transform it back.

Good luck
 

RujiK

Member
@SSJCoder Three matrices for one rotation of a single point?! How the heck is this so complicated...
@GMWolf I did see that rotation matrix on the wiki, but I don't really see how that helps me since GM does all that under the hood and I just need to provide the input. I'm probably missing something though.
So I THINK I managed to find the "relative/translated" coordinates in relation to the axis:


(The blue sphere is the thing I want to rotate. The green line represents the relative position. The red line is moved with the mouse)

Code for finding "Relative" position
GML:
P1 = [xx,yy,zz]; //point to rotate
V1 = [x0,y0,z0]; //start of arbitrary axis vector
V2 = [x1,y1,z1]; //end of arbitrary axis vector

var u = ((P1[0] - V1[0]) * (V2[0] - V1[0]))
      + ((P1[1] - V1[1]) * (V2[1] - V1[1]))
      + ((P1[2] - V1[2]) * (V2[2] - V1[2]));
     
var dist = point_distance_3d(x0,y0,z0,x1,y1,z1);//distance(V1, V2)
u = u/(dist*dist)

//var t = [0,0,0]
pos[0] = V1[0] + u * (V2[0] - V1[0]); //This is the nearest point of interestion
pos[1] = V1[1] + u * (V2[1] - V1[1]);
pos[2] = V1[2] + u * (V2[2] - V1[2]);

rel[0] = xx - pos[0]; //this is the "translated" difference between the point and the line. RELATIVE position to rotation axis
rel[1] = yy - pos[1];
rel[2] = zz - pos[2];

diff[0] = pos[0] - rel[0]; //I Need this to add back to my position at the end?
diff[1] = pos[1] - rel[1];
diff[2] = pos[2] - rel[2];

Unfortunately I'm still not sure how to rotate the dang point even though I have the relative position. I assume I apply the matrix to the "rel" positions but I haven't had any luck.

FYI, these configurations don't work:
Code:
if num1 == 0 {var _mat = matrix_build(0,0,0,COUNTER,0,0,1,1,1);}
if num1 == 1 {var _mat = matrix_build(0,0,0,0,COUNTER,0,1,1,1);}
if num1 == 2 {var _mat = matrix_build(0,0,0,0,0,COUNTER,1,1,1);}
if num1 == 3 {var _mat = matrix_build(x0,y0,z0,COUNTER,0,0,1,1,1);}
if num1 == 4 {var _mat = matrix_build(x0,y0,z0,0,COUNTER,0,1,1,1);}
if num1 == 5 {var _mat = matrix_build(x0,y0,z0,0,0,COUNTER,1,1,1);}
if num1 == 6 {var _mat = matrix_build(pos[0],pos[1],pos[2],COUNTER,0,0,1,1,1);}
if num1 == 7 {var _mat = matrix_build(pos[0],pos[1],pos[2],0,COUNTER,0,1,1,1);}
if num1 == 8 {var _mat = matrix_build(pos[0],pos[1],pos[2],0,0,COUNTER,1,1,1);}
Ugh... My head. Thanks for your patience guys.
 

SSJCoder

Member
Unfortunately I'm still not sure how to rotate the dang point even though I have the relative position. I assume I apply the matrix to the "rel" positions but I haven't had any luck.
Alright, well first I will have you know matrices are meant to be stacked onto each other for every specific operation. (you're meant to use many matrices on top of each other)

I'll try to see in the docs if I can figure out how to make that happen.
 
Forget about using mathimatical terms for a moment. Can you describe, in your own words, what exactly you are trying to achieve here? It looks like you are trying to define some element of the planet's position or orientation or orbit based on the mouse position in the current view. But I don't know precisely what you're doing.
 

GMWolf

aka fel666
@GMWolf I did see that rotation matrix on the wiki, but I don't really see how that helps me since GM does all that under the hood and I just need to provide the input. I'm probably missing something though.
Matrices are just arrays with 16 values in them
You can create your own however you like.
The equation I linked to takes an axis and an angle, and spits out a matrix. The built in matrix function take Euler angles.
 

SSJCoder

Member
So, I managed to make something, but since I don't have GMS I can't test, and there may be errors, but hopefully at least it can help you achieve your goal.

GML:
// script: PointCreate (x, y, z);
var coords = ds_list_create();
ds_list_add( coords, argument0 );
ds_list_add( coords, argument1 );
ds_list_add( coords, argument2 );

return coords;
GML:
// script: PointGetX (point);
return ds_list_find_value( argument0, 0 );
GML:
// script: PointGetY (point);
return ds_list_find_value( argument0, 1 );
GML:
// script: PointGetZ (point);
return ds_list_find_value( argument0, 2 );
GML:
// script: PointApplyMatrices (coordinates[x, y, z], matrices-stack[]);
// point
var coords = argument0;
// matrices list
var matrices = argument1;
// apply matrices
for (var i=0; i<ds_list_size( matrices ); i+=1) {
    var res = matrix_transform_vertex( ds_list_find_value( matrices, i ), ds_list_find_value( coords, 0 ), ds_list_find_value( coords, 1 ), ds_list_find_value( coords, 2 ) );
    ds_list_replace( coords, 0, res[0] );
    ds_list_replace( coords, 1, res[1] );
    ds_list_replace( coords, 2, res[2] );
}
And, some test code:
GML:
// code
var ox = /* origin-x (where to rotate from) */;
var oy = /* origin-y */;
var oz = /* origin-z */;

var rx = /* rotate-x */;
var ry = /* rotate-y */;
var rz = /* rotate-z */;

// create matrix stack
var matrices = ds_list_create();
// translate matrix
ds_list_add( matrices, matrix_build( ox, oy, oz, 0, 0, 0, 1, 1, 1 ) );
// rotate matrix
ds_list_add( matrices, matrix_build( 0, 0, 0, rx, ry, rz, 1, 1, 1 ) );
// untranslate matrix
ds_list_add( matrices, matrix_build( -ox, -oy, -oz, 0, 0, 0, 1, 1, 1 ) );

var point1 = PointCreate( /* x */, /* y */, /* z */ );
PointApplyMatrices( point1, matrices );
/* x */ = PointGetX( point1 );
/* y */ = PointGetY( point1 );
/* z */ = PointGetZ( point1 );

// then when you're done..
ds_list_destroy( point1 );
ds_list_destroy( matrices );
// I also wanted to destroy the matrices .. but didn't see any documentation in the "Matrix Functions" section for it
if anyone sees any error they could correct in my code, please do !
 

RujiK

Member
@SSJCoder Thanks for writing that out! I tried it but by point that I wanted to rotate kept zooming off into space. I'm not sure if I'm supposed to combine your code with mine to find rx,ry, and rz though, so I am probably messing something up... I'll have to test some more tonight.

@GMWolf Euler angles?! What?? So does this mean just using point_direction() and darcsin() are in the wrong format or whatever?

@flyingsaucerinvasion I have a simple point in 3d space and a 3d line. I want the 3d point to rotate around the line. So like if there was a perpendicular rope attached from the line to the point. Like this:


The mouse was just moving the 3d line around. The mouse can be ignored.
This is currently the best I have. It looks pretty good when the line is vertical, but it messes up when the line is horizontal.


Thanks for your help guys.
 

GMWolf

aka fel666
@GMWolf Euler angles?! What?? So does this mean just using point_direction() and darcsin() are in the wrong format or whatever?
matrix build takes an X, Y and Z rotation. This is complicated because you x, y and z rotations all intefere with each other.
But what you really want, is to give it an arbitrary axis to rotate about. This means no confusing trigonometry! (outside of the custom matrix build script).
You just give it an axis, and an angle to rotate about, which is exactly what you have!
 
Last edited:

SSJCoder

Member
@SSJCoder Thanks for writing that out! I tried it but by point that I wanted to rotate kept zooming off into space. I'm not sure if I'm supposed to combine your code with mine to find rx,ry, and rz though, so I am probably messing something up... I'll have to test some more tonight.
zooming off into space? that's probably because you kept transforming the same coordinates over and over again xD
so, you need to use something like:
GML:
// script: PointSet (point, x, y, z);
ds_list_replace( argument0, 0, argument1 );
ds_list_replace( argument0, 1, argument2 );
ds_list_replace( argument0, 2, argument3 );
and call that before you do the transformations, to set to the original coordinates of the point.

So, in this case, I assume you used PointCreate( x, y, z ); in a create event or something, then you re-used the transformed coordinates for every single frame -- but you need to reset the point to this position before applying all the transformations ..

So yea, then use PointSet, before beginning any transformations on the points.

EDIT:
And, if you don't want to set the coordinates both at the create/init event, and later, you could just use this script:
GML:
// script: PointCreateZero ();
var coords = ds_list_create();
ds_list_add( coords, 0 );
ds_list_add( coords, 0 );
ds_list_add( coords, 0 );

return coords;
Then only set the coordinates with PointSet before the transformations .
 
Last edited:

RujiK

Member
@SSJCoder I think my issue is finding rx,ry, and rz, for the rotation values. This is actually the same problem I'm having with my own code. If I have a vector (2,4,6) what are the values for a perpendicular rotation? I've been trying loads of different lengthdir_x,y combinations but nothing sticks. Did you run your own code? What values did you use?

@GMWolf So I tried the wiki matrix thing, (Which took me 10 minutes to type out) but my points for rotation just go zooming all over the place. I may have messed something up in converting a 3x3 matrix to a 4x4 matrix for GM but the results was just complete rubbish.

Here is the matrix code I tried:
GML:
//vector is defined as x0 -> x1, y0 -> y1, z0 -> z1;
dist = point_distance_3d(x0,y0,z0,x1,y1,z1);

xm = x0-x1; //magnitude of vectors
ym = y0-y1;
zm = z0-z1;

xn = (x0-x1)/dist; //normalized vector
yn = (y0-y1)/dist;
zn = (z0-z1)/dist;
ang = point_direction(0,0,xn,yn);
cosA = cos(ang);
sinA = sin(ang);

    _matrix[0] = cosA + xn*(1-cosA);        _matrix[1] = xm*ym*(1-cosA) - zm*sinA; _matrix[2] = xm*zm*(1-cosA) + ym*sinA; //x
    _matrix[4] = ym*xm*(1-cosA) + zm*sinA;  _matrix[5] = cosA + yn*(1-cosA);       _matrix[6] = ym*zm*(1-cosA)-xm*sinA; //y
    _matrix[8] = zm*xm*(1-cosA) - ym*sinA;  _matrix[9] = zm*ym*(1-cosA)+xm*sinA    _matrix[10] = cosA + zn*(1-cosA);     //z
	
    _matrix[12] = 0;
    _matrix[13] = 0;
    _matrix[14] = 0;  
    _matrix[15] = 1;


pos = matrix_transform_vertex(_matrix,xx,yy,zz);
And this is the matrix I tried to implement: (From GMWolfs rotation link)

Man, this is turning into quite the rabbit hole... I never expected a "simple" rotation could be so complex... Thanks for helping.
 

SSJCoder

Member
I think my issue is finding rx,ry, and rz, for the rotation values. This is actually the same problem I'm having with my own code. If I have a vector (2,4,6) what are the values for a perpendicular rotation? I've been trying loads of different lengthdir_x,y combinations but nothing sticks. Did you run your own code? What values did you use?
I haven't run my own code since I don't use GMS, only GM8, and in that one there are no matrices (as far as I know..).
I am playing around with some 3D stuff in GM8, and I tested my rotation relative to a point (meaning I translated, rotated, then translated back, and it worked as I expected) - the only issue I haven't quite figured out how to make the "planet" rotate around that line yet.

but, if I figure it out, I'll let you know xD
 

SSJCoder

Member
here's my current rotation code, for your reference:
GML:
// rotate x:
var a = /* angle */;
var rx = x; // rx = result-x
var ry = y* dcos(a) + z*dsin(a);
var rz = y*-dsin(a) + z*dcos(a);
GML:
// rotate y:
var a = /* angle */;
var rx = x* dcos(a) + z*dsin(a); // rx = result-x
var ry = y;
var rz = x*-dsin(a) + z*dcos(a);
GML:
// rotate z:
var a = /* angle */;
var rx = x*dcos(a) + y*-dsin(a); // rx = result-x
var ry = x*dsin(a) + y* dcos(a);
var rz = z;

And translation:
GML:
// translate:
var rx = x+/* translate-x */;
var ry = y+/* translate-y */;
var rz = z+/* translate-z */;
 

SSJCoder

Member
It seems I have figured it out, testing it around and it seems to be working fine in GM8.


Create/init event:
GML:
target = PointCreate( /*line-end-x*/, /*line-end-y*/, /*line-end-z*/ ); // this 'point' is the end of the line relative to the center of the world
// planet
PlanetRot = 0;
PlanetPoint = PointCreate( 0, 0, 0 );
Step event:
GML:
// planet rotation
PlanetRot+=(180/room_speed) mod 360;

// determine planet location
PointSet( PlanetPoint, 0, 1, 0 ); // change the '1' to the distance from the line
PointRotateX( PlanetPoint, degtorad( PlanetRot ) );

// target x/y/z
var tx, ty, tz;
tx = PointGetX( target );
ty = PointGetY( target );
tz = PointGetZ( target );

// get direction & distance
var dir, dis;
dir = pdir( 0, 0, tx, ty );
dis = pdis( 0, 0, tx, ty );

// rotate around z-axis to face line/target
PointRotateZ( PlanetPoint, dir );

// rotate around y-axis to face up, at the line/target
PointRotateY( PlanetPoint, arctan2( tz, dis ) );

// translate half-way through line
PointTranslate( PlanetPoint, tx*0.5, ty*0.5, tz*0.5 );
Draw event:
GML:
// PlanetPoint has the coordinates for the planet
// PointGetX( PlanetPoint );
// PointGetY( PlanetPoint );
// PointGetZ( PlanetPoint );
Helper functions:
GML:
// PointCreate (x, y, z);
var point = ds_list_create();
ds_list_add( point, argument0 );
ds_list_add( point, argument1 );
ds_list_add( point, argument2 );

return point;
GML:
// PointSet (point, x, y, z);
ds_list_replace( argument0, 0, argument1 );
ds_list_replace( argument0, 1, argument2 );
ds_list_replace( argument0, 2, argument3 );
GML:
// PointRotateX (point, angle-in-radians);

var point = argument0;
var     a = argument1;

var xx = ds_list_find_value( point, 0 );
var yy = ds_list_find_value( point, 1 );
var zz = ds_list_find_value( point, 2 );

var rx = xx;
var ry = yy* cos(a) + zz*sin(a);
var rz = yy*-sin(a) + zz*cos(a);

ds_list_replace( point, 0, rx );
ds_list_replace( point, 1, ry );
ds_list_replace( point, 2, rz );
GML:
// PointRotateY (point, angle-in-radians);

var point = argument0;
var     a = argument1;

var xx = ds_list_find_value( point, 0 );
var yy = ds_list_find_value( point, 1 );
var zz = ds_list_find_value( point, 2 );

var rx = xx* cos(a) + zz*sin(a);
var ry = yy;
var rz = xx*-sin(a) + zz*cos(a);

ds_list_replace( point, 0, rx );
ds_list_replace( point, 1, ry );
ds_list_replace( point, 2, rz );
GML:
// PointRotateZ (point, angle-in-radians);

var point = argument0;
var     a = argument1;

var xx = ds_list_find_value( point, 0 );
var yy = ds_list_find_value( point, 1 );
var zz = ds_list_find_value( point, 2 );

var rx = xx*cos(a) + yy*-sin(a);
var ry = xx*sin(a) + yy* cos(a);
var rz = zz;

ds_list_replace( point, 0, rx );
ds_list_replace( point, 1, ry );
ds_list_replace( point, 2, rz );
GML:
// PointTranslate (point, x, y, z);

ds_list_replace( argument0, 0, ds_list_find_value( argument0, 0 )+argument1 );
ds_list_replace( argument0, 1, ds_list_find_value( argument0, 1 )+argument2 );
ds_list_replace( argument0, 2, ds_list_find_value( argument0, 2 )+argument3 );
GML:
// pdir (x, y, target-x, target-y);
return arctan2( argument3-argument1, argument2-argument0 ) + pi*2;
GML:
// pdis (x, y, target-x, target-y);

var dx, dy;
dx = argument0-argument2;
dy = argument1-argument3;

return sqrt( dx*dx + dy*dy );

And here is a little extra in case you want to move your 'target' around with your keyboard:
(put in step event)
GML:
PointTranslate( target,
    (keyboard_check(ord('A'))-keyboard_check(ord('Z'))),
    (keyboard_check(ord('S'))-keyboard_check(ord('X'))),
    (keyboard_check(ord('D'))-keyboard_check(ord('C')))
);
Well, hopefully that is of help, if there are any incompatibilities/problems running this in GMS let me know ! (or any typos/errors/missing functions/etc)
 

RujiK

Member
Thank you! I tested your code and it works fine in GMS2 as well. (All of it minus the draw code anyway! :p I think you forgot something)

I'll need to tweak the code a little to get the effect I want, but the hard work by far is already done so that should be trivial.

Thank you again, for all TEN of your comments in this thread!
 

SSJCoder

Member
I'll need to tweak the code a little to get the effect I want, but the hard work by far is already done so that should be trivial.
if you need to understand any of the code, or tweak some stuff (like the rotation), you could probably figure it out, but if not lemme know, I can try to explain it further.
 

jo-thijs

Member
@SSJCoder I think my issue is finding rx,ry, and rz, for the rotation values. This is actually the same problem I'm having with my own code. If I have a vector (2,4,6) what are the values for a perpendicular rotation? I've been trying loads of different lengthdir_x,y combinations but nothing sticks. Did you run your own code? What values did you use?

@GMWolf So I tried the wiki matrix thing, (Which took me 10 minutes to type out) but my points for rotation just go zooming all over the place. I may have messed something up in converting a 3x3 matrix to a 4x4 matrix for GM but the results was just complete rubbish.

Here is the matrix code I tried:
GML:
//vector is defined as x0 -> x1, y0 -> y1, z0 -> z1;
dist = point_distance_3d(x0,y0,z0,x1,y1,z1);

xm = x0-x1; //magnitude of vectors
ym = y0-y1;
zm = z0-z1;

xn = (x0-x1)/dist; //normalized vector
yn = (y0-y1)/dist;
zn = (z0-z1)/dist;
ang = point_direction(0,0,xn,yn);
cosA = cos(ang);
sinA = sin(ang);

    _matrix[0] = cosA + xn*(1-cosA);        _matrix[1] = xm*ym*(1-cosA) - zm*sinA; _matrix[2] = xm*zm*(1-cosA) + ym*sinA; //x
    _matrix[4] = ym*xm*(1-cosA) + zm*sinA;  _matrix[5] = cosA + yn*(1-cosA);       _matrix[6] = ym*zm*(1-cosA)-xm*sinA; //y
    _matrix[8] = zm*xm*(1-cosA) - ym*sinA;  _matrix[9] = zm*ym*(1-cosA)+xm*sinA    _matrix[10] = cosA + zn*(1-cosA);     //z
  
    _matrix[12] = 0;
    _matrix[13] = 0;
    _matrix[14] = 0;
    _matrix[15] = 1;


pos = matrix_transform_vertex(_matrix,xx,yy,zz);
And this is the matrix I tried to implement: (From GMWolfs rotation link)

Man, this is turning into quite the rabbit hole... I never expected a "simple" rotation could be so complex... Thanks for helping.
There are a couple of small mistakes in this script:
1) You defined the vector to go from (x0, y0, z0) to (x1, y1, z1), but in code you subtract (x0, y0, z0) from (x1, y1, z1), obtaining the vector from (x1, y1, z1) to (x0, y0, z0).
2) You calculate ang using point_direction, which returns an angle in degrees with an inverted y-axis. You then apply the functions cos and sin, which expect angles in radians without inverted y-axis.
3) You calculate ang as the direction of the vector (xn, yn), which I'm not sure if that was intentional or not. Ang is supposed to be the angle of the rotation and has nothing to do with the rotation axis.
4) You forgot to square xn at _matrix[0], yn at _matrix[5] and zn at _matrix[10].
5) In _matrix[1], [2], [4], [6], [8] and [9] you consistently used the non-normalized vector (xm, ym, zn), rather than (xn, yn, zn).
6) The rotation matrix assumes the rotation axis moves through the origin, so you need to translate the vector (xx, yy, zz) before the rotation by the vector (-x0, -y0, -z0) and to translate pos after the rotation by the vector (x0, y0, z0).


It seems I have figured it out, testing it around and it seems to be working fine in GM8.


Create/init event:
GML:
target = PointCreate( /*line-end-x*/, /*line-end-y*/, /*line-end-z*/ ); // this 'point' is the end of the line relative to the center of the world
// planet
PlanetRot = 0;
PlanetPoint = PointCreate( 0, 0, 0 );
Step event:
GML:
// planet rotation
PlanetRot+=(180/room_speed) mod 360;

// determine planet location
PointSet( PlanetPoint, 0, 1, 0 ); // change the '1' to the distance from the line
PointRotateX( PlanetPoint, degtorad( PlanetRot ) );

// target x/y/z
var tx, ty, tz;
tx = PointGetX( target );
ty = PointGetY( target );
tz = PointGetZ( target );

// get direction & distance
var dir, dis;
dir = pdir( 0, 0, tx, ty );
dis = pdis( 0, 0, tx, ty );

// rotate around z-axis to face line/target
PointRotateZ( PlanetPoint, dir );

// rotate around y-axis to face up, at the line/target
PointRotateY( PlanetPoint, arctan2( tz, dis ) );

// translate half-way through line
PointTranslate( PlanetPoint, tx*0.5, ty*0.5, tz*0.5 );
Draw event:
GML:
// PlanetPoint has the coordinates for the planet
// PointGetX( PlanetPoint );
// PointGetY( PlanetPoint );
// PointGetZ( PlanetPoint );
Helper functions:
GML:
// PointCreate (x, y, z);
var point = ds_list_create();
ds_list_add( point, argument0 );
ds_list_add( point, argument1 );
ds_list_add( point, argument2 );

return point;
GML:
// PointSet (point, x, y, z);
ds_list_replace( argument0, 0, argument1 );
ds_list_replace( argument0, 1, argument2 );
ds_list_replace( argument0, 2, argument3 );
GML:
// PointRotateX (point, angle-in-radians);

var point = argument0;
var     a = argument1;

var xx = ds_list_find_value( point, 0 );
var yy = ds_list_find_value( point, 1 );
var zz = ds_list_find_value( point, 2 );

var rx = xx;
var ry = yy* cos(a) + zz*sin(a);
var rz = yy*-sin(a) + zz*cos(a);

ds_list_replace( point, 0, rx );
ds_list_replace( point, 1, ry );
ds_list_replace( point, 2, rz );
GML:
// PointRotateY (point, angle-in-radians);

var point = argument0;
var     a = argument1;

var xx = ds_list_find_value( point, 0 );
var yy = ds_list_find_value( point, 1 );
var zz = ds_list_find_value( point, 2 );

var rx = xx* cos(a) + zz*sin(a);
var ry = yy;
var rz = xx*-sin(a) + zz*cos(a);

ds_list_replace( point, 0, rx );
ds_list_replace( point, 1, ry );
ds_list_replace( point, 2, rz );
GML:
// PointRotateZ (point, angle-in-radians);

var point = argument0;
var     a = argument1;

var xx = ds_list_find_value( point, 0 );
var yy = ds_list_find_value( point, 1 );
var zz = ds_list_find_value( point, 2 );

var rx = xx*cos(a) + yy*-sin(a);
var ry = xx*sin(a) + yy* cos(a);
var rz = zz;

ds_list_replace( point, 0, rx );
ds_list_replace( point, 1, ry );
ds_list_replace( point, 2, rz );
GML:
// PointTranslate (point, x, y, z);

ds_list_replace( argument0, 0, ds_list_find_value( argument0, 0 )+argument1 );
ds_list_replace( argument0, 1, ds_list_find_value( argument0, 1 )+argument2 );
ds_list_replace( argument0, 2, ds_list_find_value( argument0, 2 )+argument3 );
GML:
// pdir (x, y, target-x, target-y);
return arctan2( argument3-argument1, argument2-argument0 ) + pi*2;
GML:
// pdis (x, y, target-x, target-y);

var dx, dy;
dx = argument0-argument2;
dy = argument1-argument3;

return sqrt( dx*dx + dy*dy );

And here is a little extra in case you want to move your 'target' around with your keyboard:
(put in step event)
GML:
PointTranslate( target,
    (keyboard_check(ord('A'))-keyboard_check(ord('Z'))),
    (keyboard_check(ord('S'))-keyboard_check(ord('X'))),
    (keyboard_check(ord('D'))-keyboard_check(ord('C')))
);
Well, hopefully that is of help, if there are any incompatibilities/problems running this in GMS let me know ! (or any typos/errors/missing functions/etc)
I like your coding style.
You have some mistakes in your math though.
Let me illustrate by an example:
Code:
PlanetRot = 0
InitialPlanetPoint = (0,1,0)
target = (0,1,1)
PlanetPointAfterXRot = (0,1,0)
PlanetPointAfterZRot = (-1,0,0)
PlanetPointAfterYRot = (-sqrt(2)/2,0,-sqrt(2)/2)
dot product between PlanetPointAfterYRot and target = -sqrt(2)/2
Your method is to construct, given the target vector, a 3D cartesian coordinate system where one of the axis aligns with the target vector.
You then describe a rotation in this new coordinate system and transform it back into the original coordinate system, which is a simple matter of adding some vectors together.
This is an excellent method to acheive what Rujik wants (as far as I understand it, skimming through the thread),
but the axis you construct for the new coordinate system aren't always perpendicular and as a result you don't always successfully construct a cartesian coordinate system.

2 vectors are perpendicular with respect to each other if and only if their dot product is 0.
I would expect the vector from the origin to PlanetPoint (right after its Y-axis rotation) to be perpendicular to the target vector.
The dot product is not always 0 however as is shown in the example above.

Creating perpendicular axis to a given vector is not difficult, but it's a bit awkward due to some edge cases.
If we have the target vector (tx, ty, tz), we first search for a non-parallel vector "v0".
We consider 2 simple candidates: (1,0,0) and (0,1,0).
The best candidate is the one with the highest absolute value of the dot product with the target value.
This means that if abs(tx) < abs(ty), then we take v0 = (0,1,0), otherwise we take v0 = (1,0,0).
We then turn v0 into a vector v1 perpendicular to the target vector: v1 = v0 - ((v0 . target) / || target ||²) * target
Here the dot indicates the dot product and || target || indicates the length of the target vector.
Now that we have 2 perpendicular vectors, we can easily find a third one "v2" by applying the cross product to the former two: v2 = target x v1
Our final coordinate system is then given by the axis corresponding to these vectors after normalization:
ux = target / || target ||
uy = v1 / || v1 ||
uz = v2 / || v2 ||

We then describe the rotation in this coordinate system: (1,cos(angle),sin(angle))
And finally, we convert this to the original coordinate system as: 1 * ux + cos(angle) * uy + sin(angle) * uz

This approach does have a disadvantage however: if the target vector moves a lot, the planet will get pretty jumpy.
This approach only works well visually if the target vector never changes.
Tackling that issue can be pretty tough using this method (if it is possible at all).

Also, all of the above assumes the target vector never becomes 0, as the problem becomes somewhat poorly defined at that point.

EDIT: Yeah, I don't think tackling the jumpiness using this method is possible due to the hairy ball theorem.
 
Last edited:
Top