#define quaternion_documentation_placeholder /* Placeholder script, does nothing * 20 July 2006 */ /** \file Quaternions.gml * * A collection of Gamemaker scripts for rotation using simplified * quaternion algebra. * * Quaternions are derived from complex numbers, and whereas a unit * complex number can describe an orientation in a 2D space a * quaternion can describe any orientation in a 3D space and * provides a code-efficient means to transform orientations * particularly in Gamemaker where the more usual matrix algebra * would require a larger number of operations for the same result * * Quaternions have many of the characteristics of rotation matrices * and indeed the same results could be obtained from matrices, however * as Gamemaker does not have vectorised math functions (matrix functions) * quaternions appear preferable due to requiring less operations. * */ #define d3d_quaternion_set_projection_neg /** \def d3d_quaternion_set_projection_neg(x,y,z,q0,q1,q2,q3) * d3d_quaternion_set_projection_neg(x,y,z,q0,q1,q2,q3) * * Set a camera orientation from a quaternion * This script treats -x as the base direction, eg looking LEFT on the map, * which is inconsistant with the rest of Gamemaker but was convenient in * d3d as on a textured sphere the middle of the texture faces left. * This function will be replaced, for consistancy with 2D games where * zero degrees means looking right. * * */ //Optimised vectorisation for camera? //Formulae obtained by expanding out q.v.q* formula to give formulae //for the object's x and z vectors //precalculate common expressions var t1,t2; t1=sqr(argument3)-sqr(argument5) t2=sqr(argument4)-sqr(argument6) d3d_set_projection(argument0,argument1,argument2,argument0-(t1+t2),argument1-2*(argument4*argument5+argument3*argument6),argument2-2*(argument4*argument6-argument3*argument5),2*(argument4*argument6+argument3*argument5),2*(argument5*argument6-argument3*argument4),t1-t2); #define d3d_quaternion_set_projection /** \def d3d_quaternion_set_projection(x,y,z,q0,q1,q2,q3) * d3d_quaternion_set_projection(x,y,z,q0,q1,q2,q3) * * Set a camera orientation from a quaternion * This script treats +x as the base direction, eg looking RIGHT on the map, * to be consistant with the rest of Gamemaker * * */ //Optimised vectorisation for camera? //Formulae obtained by expanding out q.v.q* formula to give formulae //for the object's x and z vectors //precalculate common expressions var t1,t2; t1=sqr(argument3)-sqr(argument5) t2=sqr(argument4)-sqr(argument6) d3d_set_projection(argument0,argument1,argument2,argument0+(t1+t2),argument1+2*(argument4*argument5+argument3*argument6),argument2+2*(argument4*argument6-argument3*argument5),2*(argument4*argument6+argument3*argument5),2*(argument5*argument6-argument3*argument4),t1-t2); #define d3d_quaternion_set_projection_neg_ext /** \def d3d_quaternion_set_projection_neg_ext(x,y,z,q0,q1,q2,q3,angle,aspect,znear ,zfar) ) * d3d_quaternion_set_projection_neg_ext(x,y,z,q0,q1,q2,q3,angle,aspect,znear ,zfar) * * Set a camera orientation from a quaternion using the extended form * This script treats -x as the base direction, eg looking LEFT on the map, * which is inconsistant with the rest of Gamemaker but was convenient in * d3d as on a textured sphere the middle of the texture faces left. * This function will be replaced, for consistancy with 2D games where * zero degrees means looking right. * * Derived from d3d_quaternion_set_projection_neg(); */ //Optimised vectorisation for camera? //Formulae obtained by expanding out q.v.q* formula to give formulae //for the object's x and z vectors //precalculate common expressions var t1,t2; t1=sqr(argument3)-sqr(argument5) t2=sqr(argument4)-sqr(argument6) d3d_set_projection_ext(argument0,argument1,argument2,argument0-(t1+t2),argument1-2*(argument4*argument5+argument3*argument6),argument2-2*(argument4*argument6-argument3*argument5),2*(argument4*argument6+argument3*argument5),2*(argument5*argument6-argument3*argument4),t1-t2,argument7,argument8,argument9,argument10); #define d3d_quaternion_set_projection_ext /** \def d3d_quaternion_set_projection_ext(x,y,z,q0,q1,q2,q3,angle,aspect,znear ,zfar) ) * d3d_quaternion_set_projection_ext(x,y,z,q0,q1,q2,q3,angle,aspect,znear ,zfar) * * Set a camera orientation from a quaternion using the extended form * This script treats +x as the base direction, eg looking RIGHT on the map, * to be consistant with the rest of Gamemaker * * Derived from d3d_quaternion_set_projection_neg(); */ //Optimised vectorisation for camera? //Formulae obtained by expanding out q.v.q* formula to give formulae //for the object's x and z vectors //precalculate common expressions var t1,t2; t1=sqr(argument3)-sqr(argument5) t2=sqr(argument4)-sqr(argument6) d3d_set_projection_ext(argument0,argument1,argument2,argument0+(t1+t2),argument1+2*(argument4*argument5+argument3*argument6),argument2+2*(argument4*argument6-argument3*argument5),2*(argument4*argument6+argument3*argument5),2*(argument5*argument6-argument3*argument4),t1-t2,argument7,argument8,argument9,argument10); #define d3d_quaternion_light_direction_neg /** \def d3d_quaternion_light_direction_neg(ind,q0,q1,q2,q3,color) * d3d_quaternion_light_direction_neg(ind,q0,q1,q2,q3,color) * * Arguments are ind,q0,q1,q2,q3,color * * Applies a directional light according to a quaternion * * Script treats -x as the base direction. */ //vectorisation of -x direction //Formulae obtained by expanding out q.v.q* formula; d3d_light_define_direction(argument0,-(sqr(argument1)+sqr(argument2)-sqr(argument3)-sqr(argument4)),-2*(argument2*argument3+argument1*argument4),-2*(argument2*argument4-argument1*argument3),argument5); #define d3d_quaternion_light_direction /** \def d3d_quaternion_light_direction(ind,q0,q1,q2,q3,color) * d3d_quaternion_light_direction(ind,q0,q1,q2,q3,color) * * Arguments are ind,q0,q1,q2,q3,color * * Applies a directional light according to a quaternion * * Script treats x as the base direction. */ //vectorisation of +x direction //Formulae obtained by expanding out q.v.q* formula; d3d_light_define_direction(argument0,(sqr(argument1)+sqr(argument2)-sqr(argument3)-sqr(argument4)),2*(argument2*argument3+argument1*argument4),2*(argument2*argument4-argument1*argument3),argument5); #define d3d_add_rotation_quaternion /** \def d3d_add_rotation_quaternion(q0,q1,q2,q3) * d3d_add_rotation_quaternion(q0,q1,q2,q3) * * Function to apply quaternion q0,q1,q2,q3 to the current drawing state * using a rotation about an axis. * The function uses d3d_transform_add_rotation_axis * * note that although I do not have mathematical proof it appears * as though any orientation may be expressed as a single rotation about * an axis, and therefore that any combination of rotations about multiple * axes may be combined into one rotation about new axis */ // The commented version is the code written out in full for clarity //var q0,q1,q2,q3,mag,theta; //q0=argument0; //q1=argument1; //q2=argument2; //q3=argument3; //mag=sqrt(sqr(q1)+sqr(q2)+sqr(q3)); //theta=radtodeg(arctan2(mag,q0))*2; //d3d_transform_add_rotation_axis(q1,q2,q3,theta); d3d_transform_add_rotation_axis(argument1,argument2,argument3,-radtodeg(arctan2(sqrt(sqr(argument1)+sqr(argument2)+sqr(argument3)),argument0))*2); #define d3d_set_rotation_quaternion /** \def d3d_set_rotation_quaternion(q0,q1,q2,q3) * d3d_set_rotation_quaternion(q0,q1,q2,q3) * * Function to apply quaternion q0,q1,q2,q3 to the current drawing state * using a rotation about an axis. * The function uses d3d_transform_set_rotation_axis * * Derived from d3d_add_rotation_quaternion() script * */ d3d_transform_set_rotation_axis(argument1,argument2,argument3,-radtodeg(arctan2(sqrt(sqr(argument1)+sqr(argument2)+sqr(argument3)),argument0))*2); #define quaternion_ball_roll_simple /** \def quaternion_ball_roll_simple(radius) * quaternion_ball_roll_simple(radius) * * This function implements a simple rolling motion * by converting the object's hspeed,vspeed into * a rotation and applying that to the object's * orientation (q0,q1,q2,q3). */ var t0,t1,t2,t3,vx,vy,vz,radius,temp,temp1; radius=argument0; // ang=degtorad(-5)/2; // note that this function takes advantage of the fact that // the angle in radians that the object has rolled is // equal to the linear distance divided by the radius // also note that the factor of 2 that turns up repeatedly // is because quaternion rotations require the angle to be halved // sinc(x)=sin(x)/x but fiddled such that sinc(0)=1 // it is used to prevent division-by-zero errors /*if speed*100>radius { temp=sin(speed/(radius*2))/speed; } else { temp=1/(radius*2); } */ /* movement vector is (hspeed,vspeed,0) the cross-product of this and the vector pointing to the point of contact: (0,0,-1) yields (-vspeed,hspeed,0) */ vx=-vspeed/(radius*2); vy=hspeed/(radius*2); vz=0; temp=sqrt(sqr(vx)+sqr(vy)+sqr(vz)); temp1=sinc(temp); t0=cos(temp); t1=vx*temp1; t2=vy*temp1; t3=vz; // We use a LEFT multiply to carry out the rotation relative to the games // reference not the object's reference multiply_quaternion_left(t0,t1,t2,t3); #define init_quaternion /** \def init_quaternion() * init_quaternion() * * Sets the variables q0,q1,q2,q3 to the basic unit * quaternion (1,0,0,0); */ q0=1; q1=0; q2=0; q3=0; #define init_quaternion_random /** \def init_quaternion_random() * init_quaternion_random() * * Sets the variables q0,q1,q2,q3 to a random orientation * quaternion. This is done by generating random angles * and converting the result to a quaternion * note the special distribution of "theta" needed to * prevent bias towards certain orientations * * Picture a globe, phi is longitude, theta is latitude and psi is which way * you are facing. Phi and Psi can be simply random but if theta is a simple * random number then if you are near the poles the lines of longitude are * closer together. This skews things * * Starting from the equator on a radius 1 globe the area of the northen * hemisphere is 2*pi and the length of a line of latitude is 2*pi*cos(theta). * The area between the equator is 2*pi*sin(theta) (basic integral, only works * in radians otherwise it gets ugly) and from this I infer that sin(theta) * should have a flat distribution and so if theta=arcsin(random) then * sin(theta)=sin(arcsin(random)) so sin(theta)=random (provided random lies * in the range -1 to +1. */ var phi,theta,psi; phi=pi*(random(2)-1); theta=arcsin(random(2)-1); psi=pi*(random(2)-1); //it should give an even spread and not favour the "poles" //as a flat spread of angles would multiply_quaternion(cos(phi/2),sin(phi/2),0,0,cos(theta/2),0,sin(theta/2),0); multiply_quaternion_right(cos(psi/2),0,0,sin(phi/2)); #define normalise_quaternion /** \def normalise_quaternion() * normalise_quaternion() * * Normalises (q0,q1,q2,q3) * Ensures that math errors have not shifted the absolute value * away from 1. Note that this function uses slow math functions * but the optimised math form uses more code and so would * run slower in Gamemaker. */ var temp; temp=1/sqrt(sqr(q0)+sqr(q1)+sqr(q2)+sqr(q3)); q0*=temp; q1*=temp; q2*=temp; q3*=temp; #define quaternion_fly_roll /** \def quaternion_fly_roll(angle) * quaternion_fly_roll(angle) * * roll manouver script for aircraft-like movement * * causes an object to roll by an amount angle * * using multiply_quaternion_right causes the rotation to be carried out * in the object's reference frame instead of the room's frame */ /* this is old code left in for reference ang=degtorad(argument0)/2; vx=1; vy=0; vz=0; t0=cos(ang); t1=vx*sin(ang); t2=vy*sin(ang); t3=vz*sin(ang); multiply_quaternion_right(t0,t1,t2,t3); */ var ang; ang=degtorad(argument0)/2; multiply_quaternion_right(cos(ang),sin(ang),0,0); #define quaternion_fly_pitch /** \def quaternion_fly_pitch(angle) * quaternion_fly_pitch(angle) * * pitch manouver script for aircraft-like movement * * causes an object to pitch by an amount angle * * see quaternion_fly_roll for details */ var ang; ang=degtorad(argument0)/2; multiply_quaternion_right(cos(ang),0,sin(ang),0); #define quaternion_fly_yaw /** \def quaternion_fly_yaw(angle) * quaternion_fly_yaw(angle) * * yaw manouver script for aircraft-like movement * * causes an object to yaw by an amount angle * * see quaternion_fly_roll for details */ var ang; ang=degtorad(argument0)/2; multiply_quaternion_right(cos(ang),0,0,sin(ang)); #define quaternion_rotate_vector /** \def quaternion_rotate_vector(x,y,z) * quaternion_rotate_vector(x,y,z) * * script to rotate a vector by an object's quaternion * * arguments are the x,y,z components of the vector * the result is put into variables vx,vy,vz * * this can be called with (1,0,0) to obtain a vector * pointing the way the object is facing. Use (-1,0,0) * if you are using the _neg functions */ var qq0,qq1,qq2,qq3; qq0=q0; qq1=q1; qq2=q2; qq3=q3; multiply_quaternion_right(0,argument0,argument1,argument2); multiply_quaternion_right(qq0,-qq1,-qq2,-qq3); vx=q1; vy=q2; vz=q3; q0=qq0; q1=qq1; q2=qq2; q3=qq3; #define sinc /** \def sinc(x) * sinc(x) * * This function is defined as sin(x)/x * It is used by the quaternion_roll function as part of the * formula for converting a vector into a rotation axis * because it is valid for x=0 and so it won't cause * divide by zero errors * * note that for very small x sinc(x) is close to 1-sqr(x)/6 */ if abs(argument0)>0.0000001 { return sin(argument0)/argument0; }; return 1-sqr(argument0)*0.16666666666666666666666666666667; #define multiply_quaternion /** \def multiply_quaternion(r0,r1,r2,r3,s0,s1,s2,s3) * multiply_quaternion(r0,r1,r2,r3,s0,s1,s2,s3) * * multiply two quaternions r and s * arguments are real,i,j,k,real2,i2,j2,k2 * result is returned in variables q0,q1,q2,q3 * * note that the order of the two quaternions is very * important. multiply_quaternion(s,r) is not the same as * multiply_quaternion(r,s) */ q0=argument0*argument4-argument1*argument5-argument2*argument6-argument3*argument7; q1=argument0*argument5+argument1*argument4+argument2*argument7-argument3*argument6; q2=argument0*argument6+argument2*argument4+argument3*argument5-argument1*argument7; q3=argument0*argument7+argument3*argument4+argument1*argument6-argument2*argument5; #define multiply_quaternion_left /** \def multiply_quaternion_left(l0,l1,l2,l3) * multiply_quaternion_left(l0,l1,l2,l3) * * multiply two quaternions l (from arguments) and q (from object) * arguments are real,i,j,k parts of quaternion * * note that the order of the two quaternions is very * important. multiply_quaternion_left will apply a rotation in the * room's reference frame not the object's frame. */ var qq0,qq1,qq2,qq3; qq0=q0; qq1=q1; qq2=q2; qq3=q3; q0=argument0*qq0-argument1*qq1-argument2*qq2-argument3*qq3; q1=argument0*qq1+argument1*qq0+argument2*qq3-argument3*qq2; q2=argument0*qq2+argument2*qq0+argument3*qq1-argument1*qq3; q3=argument0*qq3+argument3*qq0+argument1*qq2-argument2*qq1; #define multiply_quaternion_right /** \def multiply_quaternion_right(r0,r1,r2,r3) * multiply_quaternion_right(r0,r1,r2,r3) * * multiply two quaternions q (from object) and r (from arguments) * arguments are real,i,j,k parts of quaternion * result is returned in variables q0,q1,q2,q3 * * note that the order of the two quaternions is very * important. multiply_quaternion_right will apply a rotation in the * object's reference frame * */ // multiply q0,q1,q2,q3 by a quaternion argument // argument is on the right (the order is important with quaternions) // arguments are real,i,j,k // multiplies q*argument var qq0,qq1,qq2,qq3; qq0=q0; qq1=q1; qq2=q2; qq3=q3; q0=qq0*argument0-qq1*argument1-qq2*argument2-qq3*argument3; q1=qq0*argument1+qq1*argument0+qq2*argument3-qq3*argument2; q2=qq0*argument2+qq2*argument0+qq3*argument1-qq1*argument3; q3=qq0*argument3+qq3*argument0+qq1*argument2-qq2*argument1;