Jump to content

Prospero

Member
  • Content Count

    478
  • Joined

  • Last visited

  • Medals

Everything posted by Prospero

  1. Prospero

    Vtol - using the keyboard

    Been away. But I'm glad somebody finally used that script;) Took me bloody ages to write it. EDIT: From memory, and I could be wrong because it's been so long, deleting PSda, PSdb and PSdc will mean that the B-axis won't work. These pieces of code are used to set up 3 reference points (using the Drop command) which can then be used to determine the attitude of the (invisible) camera object which is tracked in order to get keypresses (which is how ProSphere works). EDIT: The triggers which are placed by the code called by these pieces of code are used to determine the absolute height of the reference points. EDIT: A while back, I heard you can createvehicle an "emptydetector" to create triggers without mucking around in the mission editor. I tried it, and it didn't work. Mine is not to reason why, but if it does work, then I'd use it. Prospero
  2. Hi all, I don't understand how to define one's own object class. Anyone got an example? The reason I'm asking is: Does this mean one can use nearestObject (which now works again - joy! to scan for one's own custom class? Oh, and while I'm here, could someone tell me which types work for nearestObject? I've tried things like "car" and "air" but they don't seem to work? I notice that leaving the type blank - just "" - scans for all objects. Prospero
  3. Prospero

    Calculating air resistance

    I mentioned a "display script" for the framework flight-model code I posted earlier in this thread. So, for the sake of completeness, here's an example of one which takes care of the NED to ENU conversion without having to resort back to Euler angles. I use it to display objects made out of lots of separate spherical objects - driven by the flight-model script. Also note that a trigger called "base" needs to be created (one way or another) for this script to work as intended. The "mysphere" object is just a custom sphere created using O2. ---------- s1 = "mysphere" camcreate [0, 0, 0] s2 = "mysphere" camcreate [0, 0, 0] s3 = "mysphere" camcreate [0, 0, 0] s4 = "mysphere" camcreate [0, 0, 0] s5 = "mysphere" camcreate [0, 0, 0] s6 = "mysphere" camcreate [0, 0, 0] s7 = "mysphere" camcreate [0, 0, 0] s8 = "mysphere" camcreate [0, 0, 0] s9 = "mysphere" camcreate [0, 0, 0] s10 = "mysphere" camcreate [0, 0, 0] s11 = "mysphere" camcreate [0, 0, 0] ; Vectors in ENU. _v1 = [0, 0, 0] _v2 = [0, 1, 0] _v3 = [1, 0, 0] _v4 = [0, -1, 0] _v5 = [-1, 0, 0] _v6 = [0, 0, 1] _v7 = [0, 0, -1] _v8 = [0.325, 1, 0] _v9 = [-0.325, 1, 0] _v10 = [0, 1, 0.325] _v11 = [0, -3, 0] _vs = [_v1, _v2, _v3, _v4, _v5, _v6, _v7, _v8, _v9, _v10, _v11] _nvs = count _vs _n = 0 #lp1 _ip = Output ; Position in ENU (remember that the z value represents absolute height here). _x = _ip select 4 _y = _ip select 3 _z = -(_ip select 5) ; The Direction Cosine Matrix. Note that this is the NED DCM. We don't want to have to rebuild it from scratch for ENU. Instead, when we come to apply it to the ENU vectors, we'll perform the necessary conversion as we go. _m11 = _ip select 9 _m12 = _ip select 10 _m13 = _ip select 11 _m21 = _ip select 12 _m22 = _ip select 13 _m23 = _ip select 14 _m31 = _ip select 15 _m32 = _ip select 16 _m33 = _ip select 17 ; Transform the vectors. _tvs = [] #lp2 _v = _vs select _n _x1 = _v select 0 _y1 = _v select 1 _z1 = _v select 2 ; Apply the inverse (or transpose) of the DCM. Also swap the x and y terms and change the sign of the z term as we go. _x2 = _y1 * _m11 + _x1 * _m21 - _z1 * _m31 _y2 = _y1 * _m12 + _x1 * _m22 - _z1 * _m32 _z2 = _y1 * _m13 + _x1 * _m23 - _z1 * _m33 base setpos [_y2 + _x, _x2 + _y, 0] _v = [_y2 + _x, _x2 + _y, -_z2 + _z + (getpos base select 2)] _tvs = _tvs + [_v] _n = _n + 1 ?(_n < _nvs): goto "lp2" _n = 0 ; The array named tvs now contains all the vectors transformed appropriately for ENU, including an offset to account for the (quite probably differing) height of the ground under each position. So all that remains is to place the objects into OFP world-space in the normal way. s1 setpos (_tvs select 0) s2 setpos (_tvs select 1) s3 setpos (_tvs select 2) s4 setpos (_tvs select 3) s5 setpos (_tvs select 4) s6 setpos (_tvs select 5) s7 setpos (_tvs select 6) s8 setpos (_tvs select 7) s9 setpos (_tvs select 8) s10 setpos (_tvs select 9) s11 setpos (_tvs select 10) ; In ENU, remember that a positive yaw is nose left, NOT right. It is effectively minus (compass) heading. hint format ["yaw %1\npitch %2\nroll %3\n", -deg(_ip select 8), deg(_ip select 7), deg(_ip select 6)] ~0.001 goto "lp1" ---------- Prospero
  4. More or less as Spitfire says. I'd add that since the object to be controlled is neither the player nor the actual vehicle in which the player is, but rather a totally independent missile / rocket / bomb, there shouldn't be a problem with control conflicts depending on how elegantly you need to lock out "normal" player control while you're steering the missile (usually via setPos or setVelocity, or a combination thereof), and where you want to place the camera. I hope that makes sense. Basically, if you can get away from the "normal" control lockout method using cameraEffect used in the ProSphere scripts, you'll be fine. You'll probably be able to just switchCamera to the missile. Prospero
  5. Prospero

    Calculating air resistance

    OFP's weather-wind - remember the "rubbing" argument in the drop function, for example - may also influence the motion of the sim-types used for shells / rockets. I haven't done any experiments, so I'm simply mentioning it as it occurs. It could be one factor responsible for the "inconsistencies" mentioned. It is measurable though (at least, I'm sure a reasonably accurate estimation is possible) via script. Prospero
  6. Prospero

    Calculating air resistance

    I s'pose this may be helpful. Then again, it may not be. I wrote it as a framework for this sort of stuff a while back. Knock yourselves out. ---------- ; Assume that we've already created a reference object called "referenceobject" on the map in the Mission Editor to define the start position for the simulation. The following line of code will place this reference object 3 metres off the ground - for purely arbitrary reasons. Also assume that we've already created a trigger called "base" to aid in the determination of absolute height. referenceobject setpos [(getpos referenceobject select 0), (getpos referenceobject select 1), (getpos referenceobject select 2) + 3] ; This is the global array we'll use to output the required simulation variables. Output = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] ; Physical constants. At least, we'll treat them as constants. Note that rho (it's a letter in the greek alphabet) is the air density. Gravity can be "switched off" by making g equal to zero (very useful for test purposes). ;_g = 9.806650 _g = 0 _rho = 1.225 ; We convert initial x, y and z position states from ENU to NED since this flight-model adheres to normal aeronautical convention for body-axes and (flat Earth) Earth-axes coordinate systems, namely NED. To transform ENU coordinates (the OFP world coordinate system is ENU) to NED coordinates (or the reverse process), simply swap over x and y, and change the sign of z. The following four lines read, "Initial x coordinate in NED equals initial y coordinate in ENU... etc." Also note that the NED z value we'll be feeding into the sim is absolute height, rather than height AGL. _x = getpos referenceobject select 1 _y = getpos referenceobject select 0 base setpos [_y, _x, 0] _z = -((getpos referenceobject select 2) - (getpos base select 2)) ; Initialize various other aircraft states. For convenience, I've used OFP's rad function here so that one can plug in values in degrees per second for the body-axes angular rates and degrees for the Euler angles. The rad function simply converts the values to radians for use in the flight-model. _u = 0 _v = 0 _w = 0 _p = rad(0) _q = rad(0) _r = rad(0) _phi = rad(0) _theta = rad(0) _psi = rad(0) _oudot = 0 _ovdot = 0 _owdot = 0 _opdot = 0 _oqdot = 0 _ordot = 0 _oxdot = 0 _oydot = 0 _ozdot = 0 _Vt = 0 _alpha = 0 _beta = 0 _lVt = 0 _lalpha = 0 _lbeta = 0 ; Build the initial attitude quaternion from the initial Euler angles. The Euler angles are phi (roll), theta (pitch) and psi (yaw). We will also initialize the quaternion parameters' rates. Incidentally, we often talk about and list the Euler angles in this order, but when it comes to applying rotations, we will always perfom yaw first (around the z-axis), pitch second (around the y-axis), and roll third (around the x-axis). This is the so-called 3-2-1 order prescribed by normal aeronautical convention. I should also mention that I will never refer to a quaternion's four parameters by numbered indices, like 0, 1, 2 and 3. Of course, this would be the natural thing to do if the quaternion was stored in an array, but it can quickly lead to confusion because some people put the "angle" parameter first, whilst other people put it last. Therefore, for the sake of clarity, I will ensure that the variable for the "angle" parameter always contains a letter "w", and the variables for the three parameters that make up the Euler "axis vector" always contain either a letter "x", "y" or "z" as appropriate. This way, there should be no confusion. _q1w = cos(deg(_phi / 2)) * cos(deg(_theta / 2)) * cos(deg(_psi / 2)) + sin(deg(_phi / 2)) * sin(deg(_theta / 2)) * sin(deg(_psi / 2)) _q1x = sin(deg(_phi / 2)) * cos(deg(_theta / 2)) * cos(deg(_psi / 2)) - cos(deg(_phi / 2)) * sin(deg(_theta / 2)) * sin(deg(_psi / 2)) _q1y = sin(deg(_phi / 2)) * cos(deg(_theta / 2)) * sin(deg(_psi / 2)) + cos(deg(_phi / 2)) * sin(deg(_theta / 2)) * cos(deg(_psi / 2)) _q1z = cos(deg(_phi / 2)) * cos(deg(_theta / 2)) * sin(deg(_psi / 2)) - sin(deg(_phi / 2)) * sin(deg(_theta / 2)) * cos(deg(_psi / 2)) _odq1w = 0 _odq1x = 0 _odq1y = 0 _odq1z = 0 ; Normalize the initial attitude quaternion. Note that we will always normalize a quaternion before building a rotation matrix from its parameters. _e = sqrt(_q1w^2 + _q1x^2 + _q1y^2 + _q1z^2) _q1w = _q1w / _e _q1x = _q1x / _e _q1y = _q1y / _e _q1z = _q1z / _e ; Build the initial Direction Cosine Matrix (also known as the DCM) from the initial attitude quaternion. _m11 = _q1w^2 + _q1x^2 - _q1y^2 - _q1z^2 _m12 = 2 * (_q1x * _q1y + _q1w * _q1z) _m13 = 2 * (_q1x * _q1z - _q1w * _q1y) _m21 = 2 * (_q1x * _q1y - _q1w * _q1z) _m22 = _q1w^2 - _q1x^2 + _q1y^2 - _q1z^2 _m23 = 2 * (_q1y * _q1z + _q1w * _q1x) _m31 = 2 * (_q1x * _q1z + _q1w * _q1y) _m32 = 2 * (_q1y * _q1z - _q1w * _q1x) _m33 = _q1w^2 - _q1x^2 - _q1y^2 + _q1z^2 ; Initialize the control inputs. Conveniently, up to six axes can be handled by my ProSphere Controller software - one for each degree of freedom. ProSphere Controller consists of a set of OFP scripts I wrote which a) read the "main" game movement keys, b) process the keystrokes to provide six simultaneous pseudo-analog axes of real-time control input, and c) load the axes data into a globally accessible array called PS. _ps = PS ; Initialize variables related to what I always refer to as the "weather-wind". Bear in mind that its xwnd, ywnd and zwnd vector components are specified in NED Earth-axes. _xwnd = 0 _ywnd = 0 _zwnd = 0 ; Overall aircraft mass, mass moments of inertia and ixz product of inertia. We'll treat them all as constants. Here, we're using typical values for a small helicopter UAV such as the Yamaha R50. The R50 weighs about 45 kg and has a rotor diameter of approximately 3 metres. _m = 45.0 _ix = 2.0 _iy = 6.2 _iz = 6.0 _ixz = 0.0 _id = _ix * _iz - _ixz^2 ; Aircraft component data. ; ...blah blah blah... ; Initialize variables related to the timing scheme. _odt = 0 _ot = Time ; Main loop. #lp ~0.0001 _nt = Time ;_dt = _nt - _ot _dt = 0.01 ; Get the control inputs from ProSphere Controller. _ps = PS ; The weather-wind velocity vector's components in NED Earth-axes (in metres per second). For now, we'll specify constant zero wind. _xwnd = 0 _ywnd = 0 _zwnd = 0 ; Transform from Earth-axes to body-axes using the Direction Cosine Matrix. _uwnd = _xwnd * _m11 + _ywnd * _m12 + _zwnd * _m13 _vwnd = _xwnd * _m21 + _ywnd * _m22 + _zwnd * _m23 _wwnd = _xwnd * _m31 + _ywnd * _m32 + _zwnd * _m33 ; Subtract these values from u, v, and w. We'll then be using the resulting relative values (ur, vr and wr) to calculate Vt, alpha and beta, and hence the lift, drag and sideforce forces in wind-axes for each of the aircraft's components. _ur = _u - _uwnd _vr = _v - _vwnd _wr = _w - _wwnd ; Determine aircraft CG's velocity, alpha and beta. I've put in some logic to truncate the value returned by the atan2 function to a range of +/- pi/2 radians for both alpha and beta values. The logic should be removed if you want to use a range of +/- pi radians instead. I've also put in a trap for when the atan2 function is passed two zero-value arguments. _Vt = sqrt(_ur^2 + _vr^2 + _wr^2) ?(_wr == 0) && (_ur == 0): goto "sp1" _alpha = rad(_wr atan2 _ur) ?(_alpha > pi / 2) && (_alpha <= pi): _alpha = pi - _alpha ?(_alpha > -pi) && (_alpha <= -pi / 2): _alpha = abs(_alpha) - pi #sp1 ?(_vr == 0) && (_ur == 0): goto "sp2" _beta = rad(_vr atan2 _ur) ?(_beta > pi / 2) && (_beta <= pi): _beta = pi - _beta ?(_beta > -pi) && (_beta <= -pi / 2): _beta = abs(_beta) - pi #sp2 ; /----------------------------------------------/ ; /**********************************************/ ; / The aircraft component build-up starts here. / ; /**********************************************/ ; /----------------------------------------------/ ; Now we determine the local velocity, local alpha and local beta at the position of the structural component's aerodynamic centre (often refered to as its ac). We will call the local velocity components lur, lvr and lwr. The cross product of the aircraft's angular rate vector and the structural component's position vector is used to calculate them. _lur = _ur + (_q * _htdz -_r * _htdy) _lvr = _vr + (_r * _htdx - _p * _htdz) _lwr = _wr + (_p * _htdy -_q * _htdx) ; Local velocity, local alpha and local beta. _lVt = sqrt(_lur^2 + _lvr^2 + _lwr^2) ?(_lwr == 0) && (_lur == 0): goto "sp3" _lalpha = rad(_lwr atan2 _lur) ?(_lalpha > pi / 2) && (_lalpha <= pi): _lalpha = pi - _lalpha ?(_lalpha > -pi) && (_lalpha <= -pi / 2): _lalpha = abs(_lalpha) - pi #sp3 ?(_lvr == 0) && (_lur == 0): goto "sp4" _lbeta = rad(_lvr atan2 _lur) ?(_lbeta > pi / 2) && (_lbeta <= pi): _lbeta = pi - _lbeta ?(_lbeta > -pi) && (_lbeta <= -pi / 2): _lbeta = abs(_lbeta) - pi #sp4 ; ...blah blah blah... ; Relate the signs of these forces to wind-axes convention. _x1 = -_drag _y1 = _sideforce _z1 = -_lift ; We now need to express the wind-axes force vector in aircraft body-axes. I find it helpful to think of the process as follows. Rather than rotate the wind-axes vector, we'll leave it where it is, and instead imagine that we're rotating the wind-axes coordinate frame so that it is aligned with the body-axes frame. Then we'll express the original vector in this new coordinate frame. The wind-axes coordinate system can be aligned with the body-axes coordinate system by two consecutive rotations. First, a rotation of -lbeta (minus lbeta) around the z-axis, followed by a rotation of lalpha around the updated y-axis. The idea then, is to build an "overall" rotation matrix that will accomplish this. In fact, the process turns out to be identical to that which we'd use for rotating a vector - and indeed, it appears that this is precisely what we're doing - but in the final stage of the process we don't apply the rotation matrix to the coordinate frame, but rather we apply its inverse (or transpose) to the vector. What we end up with is an expression for the original vector in the new coordinate system, rather than an expression for the rotated vector in the original coordinate system. For clarity, I have reproduced the process in full here, rather than take any shortcuts. ; Build the primitive quaternion representing a rotation by -lbeta around the z-axis. _q2wa = cos(deg(-_lbeta/2)) _q2xa = 0 _q2ya = 0 _q2za = sin(deg(-_lbeta/2)) ; Build the primitive quaternion representing a rotation by lalpha around the y-axis. _q2wb = cos(deg(_lalpha/2)) _q2xb = 0 _q2yb = sin(deg(_lalpha/2)) _q2zb = 0 ; Combine the two quarternions by quaternion multiplication in the order of z-axis rotation first, y-axis rotation second. The result is a single quaternion describing both rotations in one shot - as if they were "consecutive". _q2w = -_q2xa * _q2xb - _q2ya * _q2yb - _q2za * _q2zb + _q2wa * _q2wb _q2x = _q2wa * _q2xb - _q2za * _q2yb + _q2ya * _q2zb + _q2xa * _q2wb _q2y = _q2za * _q2xb + _q2wa * _q2yb - _q2xa * _q2zb + _q2ya * _q2wb _q2z = -_q2ya * _q2xb + _q2xa * _q2yb + _q2wa * _q2zb + _q2za * _q2wb ; Normalize the resulting quaternion. _e = sqrt(_q2w^2 + _q2x^2 + _q2y^2 + _q2z^2) _q2w = _q2w / _e _q2x = _q2x / _e _q2y = _q2y / _e _q2z = _q2z / _e ; Build the rotation matrix from the quaternion parameters. _r11 = _q2w^2 + _q2x^2 - _q2y^2 - _q2z^2 _r12 = 2 * (_q2x * _q2y - _q2z * _q2w) _r13 = 2 * (_q2x * _q2z + _q2y * _q2w) _r21 = 2 * (_q2x * _q2y + _q2z * _q2w) _r22 = _q2w^2 - _q2x^2 + _q2y^2 - _q2z^2 _r23 = 2 * (_q2y * _q2z - _q2x * _q2w) _r31 = 2 * (_q2x * _q2z - _q2y * _q2w) _r32 = 2 * (_q2y * _q2z + _q2x * _q2w) _r33 = _q2w^2 - _q2x^2 - _q2y^2 + _q2z^2 ; Now, as stated previously, the inverse (or transpose) of this rotation matrix can be used to express any wind-axes vector in body-axes. By the way, the inverse and transpose are exactly the same thing since we are dealing with an orthogonal rotation matrix. Think transpose - it's a lot easier, not to mention faster to calculate - we simply swap the rows and columns of the matrix. ;_x2 = _x1 * _r11 + _y1 * _r21 + _z1 * _r31 ;_y2 = _x1 * _r12 + _y1 * _r22 + _z1 * _r32 ;_z2 = _x1 * _r13 + _y1 * _r23 + _z1 * _r33 ; Alternatively, one can work out aerodynamic forces in body-axes directly. This is a useful shortcut, especially if we just want to approximate aerodynamic forces acting on simple shapes. _x2 = -1.0 * abs(_lur) * _lur * _rho / 2 _y2 = -1.0 * abs(_lvr) * _lvr * _rho / 2 _z2 = -1.0 * abs(_lwr) * _lwr * _rho / 2 ; The forces on, and moments around the aircraft CG position arising from the forces just calculated for the structural component in question. We can use the cross product of the structure's position vector and the structure's force vector to work out the moments around the CG. _htx = _x2 _hty = _y2 _htz = _z2 _htl = _htdy * _z2 - _htdz * _y2 _htm = _htdz * _x2 - _htdx * _z2 _htn = _htdx * _y2 - _htdy * _x2 ; /--------------------------------------------/ ; /********************************************/ ; / The aircraft component build-up ends here. / ; /********************************************/ ; /--------------------------------------------/ ; Determine total forces and moments acting on the aircraft. Note that I am also feeding in some control input directly - for test purposes. _Xtot = (_ps select 4) * 10000 + _htx _Ytot = (_ps select 3) * 10000 + _hty _Ztot = -(_ps select 2) * 10000 + _htz _Ltot = (_ps select 5) * 500 + _htl _Mtot = -(_ps select 1) * 500 + _htm _Ntot = (_ps select 0) * 500 + _htn ; Now we'll deal with the rigid body Equations Of Motion (the EOM). ; First, determine the required sines and cosines of the Euler angles calculated in the previous cycle, bearing in mind that the Euler angles, body angular rates and body angular accelerations are expressed in radians within this maths flight-model. Operation Flashpoint trig functions, however, expect to be passed angles expressed in degrees, so we'll take care of this by converting them appropriately as we go (using the deg function). Note, by the way, that C/C++, FORTRAN, Visual BASIC and MATLAB differ in that their trig functions deal in radians, so no conversion would be necessary if one was to implement this code using these languages. _sph = sin(deg(_phi)) _cph = cos(deg(_phi)) _sth = sin(deg(_theta)) _cth = cos(deg(_theta)) ; Determine the body-axes acceleration components, taking gravity into account. _udot = (_r * _v) - (_q * _w) + (_Xtot / _m) - (_g * _sth) _vdot = (_p * _w) - (_r * _u) + (_Ytot / _m) + (_g * _sph * _cth) _wdot = (_q * _u) - (_p * _v) + (_Ztot / _m) + (_g * _cph * _cth) _pdot = (_iz / _id) * (_Ltot + _ixz * _p * _q - (_iz - _iy) * _q * _r) + (_ixz / _id) * (_Ntot - _ixz * _q * _r - (_iy - _ix) * _p * _q) _qdot = (_Mtot - (_ix - _iz) * _p * _r - _ixz * (_p^2 - _r^2)) / _iy _rdot = (_ixz / _id) * (_Ltot + _ixz * _p * _q - (_iz - _iy) * _q * _r) + (_ix / _id) * (_Ntot - _ixz * _q * _r - (_iy - _ix) * _p * _q) ; Integrate trapezoidally to determine body-axes velocity components. _u = _u + 0.5 * (_udot * _dt + _oudot * _odt) _v = _v + 0.5 * (_vdot * _dt + _ovdot * _odt) _w = _w + 0.5 * (_wdot * _dt + _owdot * _odt) _p = _p + 0.5 * (_pdot * _dt + _opdot * _odt) _q = _q + 0.5 * (_qdot * _dt + _oqdot * _odt) _r = _r + 0.5 * (_rdot * _dt + _ordot * _odt) ; Update the attitude quarternion. First, we calculate the rates of change of its parameters based on p, q and r (the body-axes angular velocity components). _dq1w = -0.5 * (_q1x * _p + _q1y * _q + _q1z * _r) _dq1x = 0.5 * (_q1w * _p - _q1z * _q + _q1y * _r) _dq1y = 0.5 * (_q1z * _p + _q1w * _q - _q1x * _r) _dq1z = 0.5 * (-_q1y * _p + _q1x * _q + _q1w * _r) ; Integrate trapezoidally. _q1w = _q1w + 0.5 * (_dq1w * _dt + _odq1w * _odt) _q1x = _q1x + 0.5 * (_dq1x * _dt + _odq1x * _odt) _q1y = _q1y + 0.5 * (_dq1y * _dt + _odq1y * _odt) _q1z = _q1z + 0.5 * (_dq1z * _dt + _odq1z * _odt) ; Normalize the updated quaternion. I have not put in a catch for the possibility of a zero divisor since a quaternion with all four parameters equal to zero would not represent a valid rotation in the first place. _e = sqrt(_q1w^2 + _q1x^2 + _q1y^2 + _q1z^2) _q1w = _q1w / _e _q1x = _q1x / _e _q1y = _q1y / _e _q1z = _q1z / _e ; We now build the Direction Cosine Matrix from the updated quaternion parameters. Converting the attitude to this form is useful for a number of reasons. First, it will allow us to extract the Euler angles. Second, it will prove useful for expressing the weather-wind vector, originally described in Earth-axes, in body-axes. Third, by using its transpose, we can express the linear velocity vector, originally calculated in body-axes, in Earth-axes. Incidentally, the flight-model wants to deal with Euler angles as input and output, but we perform the attitude calculation with quaternion maths internally to alleviate problems such as numerical drift and gimbal lock. _m11 = _q1w^2 + _q1x^2 - _q1y^2 - _q1z^2 _m12 = 2 * (_q1x * _q1y + _q1w * _q1z) _m13 = 2 * (_q1x * _q1z - _q1w * _q1y) _m21 = 2 * (_q1x * _q1y - _q1w * _q1z) _m22 = _q1w^2 - _q1x^2 + _q1y^2 - _q1z^2 _m23 = 2 * (_q1y * _q1z + _q1w * _q1x) _m31 = 2 * (_q1x * _q1z + _q1w * _q1y) _m32 = 2 * (_q1y * _q1z - _q1w * _q1x) _m33 = _q1w^2 - _q1x^2 - _q1y^2 + _q1z^2 ; Now we extract the Euler angles from the Direction Cosine Matrix, which, according to aeronautical convention, are applied in the order yaw first (around the z-axis), pitch second (around the y-axis), and roll third (around the x-axis). Remember, Operation Flashpoint's arc trig functions return degrees rather than radians, so we convert to radians as we go. I've put in a catch for when the atan2 function is passed two zero-value arguments (which occurs when theta is +90 or -90 degrees) - we just skip the calculations for phi and psi entirely - and have also ensured that the asin function isn't fed with a value of magnitude greater than unity due to rounding error. I realise that the atan2 catch needs to be improved. We should be using Jack D. Crenshaw's way of determining phi and psi. His method supposedly obviates the singularity that normally prevents calculating them when theta is +/- 90 degrees. ?(abs(_m13) > 1): goto "sb1" _theta = rad(asin(-_m13)) #rn1 ?(_m23 == 0) && (_m33 == 0): goto "sp5" _phi = rad(_m23 atan2 _m33) #sp5 ?(_m12 == 0) && (_m11 == 0): goto "sp6" _psi = rad(_m12 atan2 _m11) #sp6 ; Transform body-axes linear velocity components to determine Earth-axes linear velocity components in NED. We can use the inverse (or transpose) of the Direction Cosine Matrix to do this. _xdot = _u * _m11 + _v * _m21 + _w * _m31 _ydot = _u * _m12 + _v * _m22 + _w * _m32 _zdot = _u * _m13 + _v * _m23 + _w * _m33 ; Integrate trapezoidally to determine Earth-axes position in NED. _x = _x + 0.5 * (_xdot * _dt + _oxdot * _odt) _y = _y + 0.5 * (_ydot * _dt + _oydot * _odt) _z = _z + 0.5 * (_zdot * _dt + _ozdot * _odt) ; Print useful data such as the state variables to the screen. ;hint format ["X  %1\nY  %2\nZ  %3\nL  %4\nM  %5\nN  %6\n\nudot  %7\nvdot  %8\nwdot  %9\npdot  %10\nqdot  %11\nrdot  %12\n\nu  %13\nv  %14\nw  %15\np  %16\nq  %17\nr  %18\n\nVt  %19\nalpha  %20\nbeta  %21\n\nxdot  %22\nydot  %23\nzdot  %24\n\nx  %25\ny  %26\nz  %27\nphi  %28\ntheta  %29\npsi  %30\n\ndt  %31\n\nlVt  %32\n", _Xtot, _Ytot, _Ztot, _Ltot, _Mtot, _Ntot, _udot, _vdot, _wdot, _pdot, _qdot, _rdot, _u, _v, _w, _p, _q, _r, _Vt, deg(_alpha), deg(_beta), _xdot, _ydot, _zdot, _x, _y, _z, deg(_phi), deg(_theta), deg(_psi), _dt, _lVt] ; Store values which will be used in the trapezoidal integration scheme next cycle. _oudot = _udot _ovdot = _vdot _owdot = _wdot _opdot = _pdot _oqdot = _qdot _ordot = _rdot _oxdot = _xdot _oydot = _ydot _ozdot = _zdot _odq1w = _dq1w _odq1x = _dq1x _odq1y = _dq1y _odq1z = _dq1z _odt = _dt ; Bear in mind that the elements of the output array are, variously, in units of metres per second, metres and radians. Also note that they are all in NED. If, for example, one has an external display script that works in ENU (the OFP world coordinate system), one would need to transform the elements from NED Earth-axes to ENU Earth-axes - typically by swapping over the x and y terms, and changing the sign of the z term when applying setPos or setVelocity commands. The Direction Cosine Matrix (again in NED) is also included as part of the output. Output = [_xdot, _ydot, _zdot, _x, _y, _z, _phi, _theta, _psi, _m11, _m12, _m13, _m21, _m22, _m23, _m31, _m32, _m33] _ot = _nt goto "lp" #sb1 ?(_m13 > 0): _m13 = 1; goto "rn1" _m13 = -1 goto "rn1" ---------- Prospero
  7. </span><table border="0" align="center" width="95%" cellpadding="3" cellspacing="1"><tr><td>Quote (Liquid_Silence @ Feb. 08 2003,07:18)</td></tr><tr><td id="QUOTE">a) Distance seems to be perfectly accurate at reasonably short distances...I haven't tested it though, so you may be right...<span id='postcolor'> No. It doesn't matter if the distance between your objects is small. What matters is where they are on the map. The distance command uses the absolute locations of objects to determine the distance between them. Hence, if they're both top right corner (like about 10,000m for y and 10,000m for x), you're going to get significant rounding error, regardless of how close they are to each other. This problem is internal to the function. I came up with that trigger method for determining height ASL (i.e. without using the distance command AT ALL) especially to get round this problem. Prospero
  8. I know there are various ways of going "round the houses" to do this, but I've always wondered if there's a simple, straightforward, "direct" script method for determining which (official) map is loaded. We're talking from the script's POV. I can't think of one. I suspect, however, I'm being terribly stupid. Prospero
  9. </span><table border="0" align="center" width="95%" cellpadding="3" cellspacing="1"><tr><td>Quote (Liquid_Silence @ Feb. 07 2003,01:22)</td></tr><tr><td id="QUOTE">EDIT: Prospero: I'm having a look at the script. From the look of it, it's gonna be helpful Um, its the one from the thread titled "floor enumerator demo" or something like that, right?<span id='postcolor'> yup Edit: Looking back, that script could be made considerably faster but, hey, I wasn't that concerned with speed at the time. My excuse and I'm sticking to it.
  10. I posted a "floor enumerator" script which may help you - the whole point of the script was to attempt to get round such problems. Best I can offer, anyway. You may need more than one gamelogic. At least, that was my conclusion at the time. Try a search on "floor enumerator" in this forum. Should work. Prospero Edit: As for tilting with the terrain, you're stuck with it (unless you loop your setDir) depending on the simulation-type of the vehicle in question. I once tried camcreating an invisible dummy object with a "level" roadway LOD directly underneath the vehicle in question to prevent it from following the attitude of the terrain. No go. Actually this was part of my "let's try to get this object to pitch and roll" schtick. Needless to say, it didn't work.
  11. </span><table border="0" align="center" width="95%" cellpadding="3" cellspacing="1"><tr><td>Quote (Dschulle @ Jan. 24 2003,12:32)</td></tr><tr><td id="QUOTE"></span><table border="0" align="center" width="95%" cellpadding="3" cellspacing="1"><tr><td>Code Sample </td></tr><tr><td id="CODE">_trigger setpos [_unitpos select 0, _unitpos select 1, 0] _distance = _trigger distance _unit<span id='postcolor'><span id='postcolor'> You are still using the distance command. To avoid substantial rounding errors, I would use: _aslheightofobject = (getpos theobject select 2) - (getpos trigger select 2) In place of: _distance = _trigger distance _unit Prospero
  12. Hi all, Wouldn't it be nice to have an atan2 function that can handle zero as the second argument (i.e. as the divisor). Most other languages seem to handle this, so I just thought I'd mention it. Would save on all those messy traps one has to put in. Prospero
  13. </span><table border="0" align="center" width="95%" cellpadding="3" cellspacing="1"><tr><td>Quote (Suma @ Jan. 22 2003,08:15)</td></tr><tr><td id="QUOTE"></span><table border="0" align="center" width="95%" cellpadding="3" cellspacing="1"><tr><td>Quote (Prospero @ Jan. 21 2003,06:49)</td></tr><tr><td id="QUOTE">Wouldn't it be nice to have an atan2 function that can handle zero as the second argument (i.e. as the divisor). Most other languages seem to handle this, so I just thought I'd mention it.<span id='postcolor'> Function atan2 can handle zero as second argument. What it cannot handle is zero as both arguments - which is consider undefined and returns error.<span id='postcolor'> Yup, sorry, I'm wrong. I was sticking a zero in as the numerator. Sorry Suma. :tail between legs: Prospero
  14. Hey, all I'm asking for is an atan2 function that works "properly". Indeed, atan2 was originally conceived to solve two probs - namely quadrants and the divide-by-zero condition. What we have at the mo in OFP is a 50% solution. I'm not being critical. I love OFP. But believe me, this has been pissing me off 'cos I've been trying to make a "framework" 6DOF non-linear flight-model script for chaps who want the VTOL thing. All the rotation maths is done with quaternions - and when you convert from quats to Euler angles, it really helps to have a "fully" working atan2 func. Prospero Edit: Also, can we pls have doubles too?
  15. </span><table border="0" align="center" width="95%" cellpadding="3" cellspacing="1"><tr><td>Quote (Dschulle @ Jan. 21 2003,08:41)</td></tr><tr><td id="QUOTE">Camcreate a Trigger: </span><table border="0" align="center" width="95%" cellpadding="3" cellspacing="1"><tr><td>Code Sample </td></tr><tr><td id="CODE">Trigger = "EmptyDetector" createVehicle [_x, _y, _z]<span id='postcolor'> Test if variable is initialized: </span><table border="0" align="center" width="95%" cellpadding="3" cellspacing="1"><tr><td>Code Sample </td></tr><tr><td id="CODE">?format["%1", MEINLOGIC] == "scalar bool array string 0xfcffffef": MEINLOGIC = "Logic" createVehicle [_x, _y, _z]<span id='postcolor'> Thanx to vektorboson. <span id='postcolor'> Oooh good show. I didn't know that! Prospero
  16. ... I used an almost identical method in my ProSphere scripts. I haven't read through yours completely yet, but two things occur: 1) You'll lose accuracy as you move towards the upper/right corner of the map (you're using the distance command, not triggers to determine absolute height). 2) If you've made your drop functions atomic, then this point doesn't apply. I had to put a catch in here to normalize the vector components of the dropped particles - particular when the vehicle is speeding along. Otherwise the arc trig functions will error out when asked to chew on something greater than unity. Prospero
  17. Bear in mind that if you're using setVelocity *in a loop* to set the trajectory of your shell in real-time, you need to take loop execution rate into account, and adjust for it. Failing to do so could lead to the "inconsistency" you mentioned. Prospero
  18. Thanks Suma. Prospero
  19. Hi all, What's the resolution of variables in OFP scripts? From my playing around, I suspect they'll all floats. Could you confirm this, Suma? If so, is there a way to construct a variable with higher resolution - say, that of a double, using multiple float variables? Prospero
  20. Hi all, The setVelocity command can be used on objects like a table or a pallette after all. It's just a little different from when applying it to cars and planes etc. Say, for example, I was to execute this (where I have a table called "mytable"): _o = mytable _v = [0, 0, 15] _o setvelocity _v exit You can think of this code as "priming" the table. It won't move, it'll just sit there. But, now shoot the table or drop a grenade on or near it - i.e. give it a bit of a bump. It will now react according to the velocity you gave it via setVelocity in the snippet above. Some very nice scripted explosion effects could be created this way. Prospero
  21. A massive scripting niggle is the difficulty in determining absolute height (or height above *mean* sea level) - without jitter - and regardless of whether an object is over the ground, sea or a surface defined in a Roadway LOD. An associated problem is placing an object into the OFP world at an absolute height in the same circumstances. If there was one single addition to OFP scripting commands, I would choose a (relatively simple) function to solve this problem. I have written about this before, I know. But I really think this would be a very good feature, so I just want to say it here again (in isolation, as it were). Prospero
  22. Hi all, At the moment, when I want to create an explosion at any location (including - crucially - in mid-air), I camCreate one shell73 at the desired location, and then immediately camCreate another shell73 on top of it to detonate it (I read of this method in these forums). Whether one gets an explosion of twice the power of one shell73 by this method, I'm not sure. Anyone know? Is anyone aware of a method to camCreate a satchel-type charge and detonate it in a similar fashion? - and note - I need something that works in mid-air as well as on the ground. Or any other ideas for creating mid-air explosions of different scales? Additionally, is anyone aware of how to camCreate a working grenade which one can then use setVelocity to "throw"? Prospero
  23. </span><table border="0" align="center" width="95%" cellpadding="3" cellspacing="1"><tr><td>Quote (Prospero @ Dec. 08 2002,12:18)</td></tr><tr><td id="QUOTE">But for Suma, if he's out there: there seems to be a problem if getPos is used on an object which is above a slanted Roadway LOD on uneven ground. The z value returned does not seem to be accurate. Or at least, it's something like this. I've had enough of trying to figure it out.<span id='postcolor'> Further: What seems to be happening is something like this: When one performs a getPos (i.e. getPos myobject select 2) on an object (myobject) that is positioned vertically above a sloping surface (let's say 0.5m above it) defined in the Roadway LOD, the height reported is not correct. The height is not the height of the object above the Roadway LOD, as it should be, but rather is the distance to the nearest part of the surface - i.e. the length of the normal to the suface which would intersect the position of myobject. Something like this, anyway. Please can we have a way of placing objects into the world at an absolute height (above mean sea level - or similar static reference plane), and also a way of determining an object's absolute height. Pretty, pretty please. It would really simplify a lot of scripts... artillery for example, not to mention all the stuff I seem to be interested in Prospero
  24. Prospero

    Scripting command request

    Prospero <--------------- punk.
×