Kayl
Members-
Posts
70 -
Joined
-
Last visited
Recent Profile Visitors
The recent visitors block is disabled and is not being shown to other users.
Kayl's Achievements
Transformer (11/54)
0
Reputation
-
I think it's pretty well coded, as it would let you have both a modded object and don't mess the GTA world with the same object ID You can't really say that you are left with the freedom to have both a modded object and an untouched GTA world object because the moment your MTA object is streamed in, the GTA world object changes as well. When you replace an "ID", you expect all instances to be replaced. If it was that well coded, it would either replace also GTA world objects without the need for a hack, or provide us with an option to say if we want only MTA objects affected. Right now we have absolutely no control over when the GTA object is replaced (since it depends on the MTA objects as we discussed). To have a real freedom to leave GTA objects untouched, it all goes down to the problem of having only access to "replacement" functions. If we could create new IDs and load new models without having to replace GTA ones, that would also be great. There are lots of improvements to be done on those engine functions (no control over the real replacement of GTA objects, no way to create new models, collision model failing on streamin > need scripting hack to maintain the col in a working state, random texture fail). I wouldn't really say it's "well coded". It's a preview of what we could expect but it's left in a weird state.
-
That's indeed the technique used for this DKR sign. A ghost (alpha = 0) MTA object is created at the same location as the GTA object to guarantee the streaming in of the MTA object will force the GTA object to be "updated" accordingly. I wish this was not necessary.
-
<script src="..." type="client" />
-
Thank you very much for sending me your file. Thanks to it, I was able to spot the problem. It was a QtToLua bug introduced when the export of scroll bar was created. Scrollbar of scrollareas where also exported as standalone scrollbars when they shouldn't be. I have therefore released version 0.1.7 of QtToLua http://dkrclan.com/qttolua_data/download/QtToLua.zip which fixes the aforementioned bug. I have tested it with your file and it works fine now. You are free to use layouts or not, the problem was not coming from that. Thank you for your feedback which helped me fix this issue.
-
From what I see you don't use layouts which might explain a mismatch when going from Qt to MTA, also when you create a new window, choose widget or dialog, mainwindow is useless since menus are not part of MTA. Apart from the layout suggestion, I don't see what the problem could be, could you send me your UI file so I can have a look?
-
Vice > The example resource shows how to show/hide the window with a command. In your code for your own window, simply use https://wiki.multitheftauto.com/wiki/BindKey to bind F5 to your command/function showing the GUI. dukenukem > No. Qt with C++, hence "Qt to Lua".
-
As far as I know, the very definition of dimension is that all elements in other dimensions than the current local player's dimension aren't streamed in² for the local player, they are merely element information that will only be transposed to GTA world "objects" if the player is in the right dimension and the surrounding of it. The GTA clientside version of the car is "gone" not "hidden" for the client so there is no way his GTA physic engine could continue syncing it correctly so I fairly doubt MTA would let him be the syncer of it. So for me, forget about the car issue since I think it's a non issue, moving (clientside) all the map objects to the current player's dimension each time it changes seems for me the right solution to get map elements in all dimensions. ²OnClientElementStreamIn : "When this event is triggered, that element is guaranteed to be physically created as a GTA object."
-
That's because of euler rotation order. Cf: viewtopic.php?p=326214#p326214 Objects in MTA are ZXY whereas vehicles are ZYX, meaning (for objects) rotation about world Z, then rotation about resulting X and then rotation about resulting Y (for objects). So the result is different depending on the order. Most people use moveObject to rotate about one axis at a time and on objects that have 0 rotation on X or Y so the problem is not seen for those. The editor uses a patch, similar to the lua code you will find in the topic post above, to make objects rotate with ZYX and not ZXY. It has now been integrated in MTA 1.1 directly via an extra parameter for https://wiki.multitheftauto.com/wiki/SetElementRotation which, as far as I know, hasn't been backported to 1.0.4. However moveObject isn't affected by it yet, and I intend to fix that, hopefully it will be backported. For now, if you do the moveObject clientside, you can work around the issue by doing the moveObject yourself onClientRender and using the lua patch above. If you do it serverside, you have to wait for it to be implemented and backported. Edit: actually now that I think about how moveObject is done (since I'm already working on it for something else) I can tell you that even if it gets implemented, it will create some rotation artefacts on the non updated clients. I will implement it though, but I don't think this part should be backported.
-
Even if you do use an external webserver it shall be configured so as to give access to the resource-cache folder (not the resource one) which only consists of the latest versions of all required clientside files. Only if the webserver was poorly configured would it give access to mta serverside files.
-
XX3 > Indeed the end goal is to be able to attach object like hats or other items to a ped. Both problems need to be resolved to be able to do that correctly (for the orientation one, the hack I currently do in the head only works for some skins, providing the orientation of any bone would allow me to make it work for all). benxamix2 > I don't want to attach the object to the root bone of the ped but to a specific bone which, during animations, changes offset and orientation. But as I mentioned, this topic is more about the positioning than the rotation (which can be hacked for most skins when it comes to the head). I didn't put my rotation hack in the example code since I want to focus on the position issue first.
-
Hi there, I'm trying to play a bit with bones. I'm trying to "attach" an object relatively to a bone. The first problem concerns the position of the bone (here, a bone in the head). It appears that, in order to have the object following the bone smoothly, the getBonePosition + setElementPosition (on the object) must be done onClientPreRender. However, when walking on steep slopes, it seems GTA changes the bone position between onClientPreRender and onClientRender, I guess it's a result of inverse kinematics being performed to make the ped's feet touch the ground and adapt the current animation accordingly. In this case then the valid position is the position given onClientRender but if I set the position of the object onClientRender, the position is fine, but the object is flickering. Whereas if I set it onClientPreRender, the object doesn't flicker but isn't in the right position when it comes to peds walking on non flat ground. Illustration code: addCommandHandler("bone", function() local objectPreRender = createObject(2054, 0, 0, 0) local objectRender = createObject(2054, 0, 0, 0) local objectRender2 = createObject(2054, 0, 0, 0) local bx, by, bz = nil, nil, nil addEventHandler("onClientPreRender", getRootElement(), function() bx, by, bz = objectToBone(objectPreRender, 0, tocolor(255, 0, 0, 255)) end) addEventHandler("onClientRender", getRootElement(), function() objectToBone(objectRender, 0.1, tocolor(0, 255, 0, 255), objectRender2, 0.2, tocolor(0, 0, 255, 255), bx, by, bz) end) end) function objectToBone(object1, offset1, color1, object2, offset2, color2, bx, by, bz) local newbx, newby, newbz = getPedBonePosition(getLocalPlayer(), 5) setElementPosition(object1, newbx, newby, newbz + offset1) _FDP({newbx, newby, newbz + offset1}, color1) if object2 and bx and by and bz then setElementPosition(object2, bx, by, bz + offset2) _FDP({bx, by, bz + offset2}, color2) end return newbx, newby, newbz end function _FDL(sx, sy, sz, ex, ey, ez, color) local realColor = color or tocolor(255, 0, 0, 255) local ssx, ssy = getScreenFromWorldPosition(sx, sy, sz) local esx, esy = getScreenFromWorldPosition(ex, ey, ez) if ssx and ssy and esx and esy then dxDrawLine(ssx, ssy, esx, esy, realColor, 2) end end function _FDP(pos, color) local delta = 0.1 local x, y, z = unpack(pos) _FDL(x +delta, y, z, x-delta, y, z, color) _FDL(x, y+delta, z, x, y-delta, z, color) _FDL(x, y, z+delta, x, y, z-delta, color) end And the result (Z offset is on purpose to differentiate techniques): Is there any way to solve this dilemma? The second issue I have is different and not as important for now and is more a feature request. It's about the orientation of a bone. Looking at MTA's code, it seems there isn't yet the ability to extract (from GTA) the absolute orientation of a bone (only its position). Hence it becomes quite tricky to attach an object to a bone without it. However, luckily for me, the head has several bones that barely move with respect to one another (I guess only for "facial" animation). So I'm able to derive an estimate of the bone orientation I want. If by any chance, at some point in the future, one dude responsible for finding available functions via Reverse Engineering had some time to look at that and provide us with a getPedBoneMatrix() that would be epic (But don't worry I'm aware there are more important things to work on at the moment).
-
Ok, I have posted the new patch that contains also serverside changes: http://dkrclan.com/qttolua_data/eulerPatchFull.patch It was a bit simpler since serverside peds only use Z and don't have this -Z(set) +Z(get) problem. Here is the serverside test code that goes with it. addCommandHandler("stest", function () local spacing = 20 local x, y, z = -1375.1043701172, -25.0885887146, (14.1484375 + 5) local modes = { "default", "ZXY", "ZYX" } local elementsConf = { [70] = {createPed, 2}, [519] = {createVehicle, 0}, [1681] = {createObject, 0}, } local elements = {} for model, elementConf in pairs(elementsConf) do local createElementFcn, offsetZ = unpack(elementConf) for itMode, mode in ipairs(modes) do local offsetX = (itMode-1)*spacing local element1 = createElementFcn(model, x, y, z) setElementCollisionsEnabled(element1, false) setElementAlpha(element1, 150) local element2 = createElementFcn(model, x, y, z) setElementCollisionsEnabled(element2, false) setElementAlpha(element2, 150) table.insert(elements, {element1, element2, offsetX, offsetZ, mode}) end end local rx, ry, rz = 0, 0, 0 local totalTime = 0 local rotationTime = 5 local oldTick = nil setTimer( function(deltaT) local now = getTickCount() if not oldTick then oldTick = now return end local deltaT = now - oldTick oldTick = now totalTime = totalTime + deltaT if totalTime > 5*rotationTime*1000 then totalTime = 0 rx, ry, rz = 0, 0, 0 elseif totalTime > 4*rotationTime*1000 then rx = rx + 360/rotationTime*deltaT/1000 ry = ry + 360/rotationTime*deltaT/1000 rz = rz + 360/rotationTime*deltaT/1000 elseif totalTime > 3*rotationTime*1000 then rz = rz + 360/rotationTime*deltaT/1000 rx, ry = 0, 0 elseif totalTime > 2*rotationTime*1000 then ry = ry + 360/rotationTime*deltaT/1000 rx, rz = 0, 0 elseif totalTime > 1*rotationTime*1000 then rx = rx + 360/rotationTime*deltaT/1000 ry, rz = 0, 0 end local angles = {rx, ry, rz} for i=1,3 do if angles[i] < 0 then angles[i] = angles[i] + 360 elseif angles[i] >= 360 then angles[i] = angles[i] - 360 end end rx, ry, rz = unpack(angles) local text = string.format("desired %f %f %f ", rx, ry, rz) for _, elementData in ipairs(elements) do local element1, element2, offsetX, offsetZ, rotationMode = unpack(elementData) local erx, ery, erz = getElementRotation(element1, rotationMode) setElementPosition(element1, x - offsetX, y, z + offsetZ) setElementRotation(element1, rx, ry, rz, rotationMode) setElementVelocity(element1, 0, 0, 0) setElementPosition(element2, x - offsetX, y, z + offsetZ) setElementRotation(element2, erx, ery, erz, rotationMode) setElementVelocity(element2, 0, 0, 0) end end , 50, 0) end )
-
Thx to our PM discussion, I was able to compile MTA. Here is a first version of the patch: http://dkrclan.com/qttolua_data/eulerPatch.patch I would have liked to upload it on the bug report but I'm getting access denied. The test code for it: addCommandHandler("test", function () local spacing = 20 local x, y, z = -1375.1043701172, -25.0885887146, (14.1484375 + 5) local modes = { "default", "ZXY", "ZYX" } local elementsConf = { [70] = {createPed, 2}, [519] = {createVehicle, 0}, [1681] = {createObject, 0}, } local elements = {} for model, elementConf in pairs(elementsConf) do local createElementFcn, offsetZ = unpack(elementConf) for itMode, mode in ipairs(modes) do local offsetX = (itMode-1)*spacing local element1 = createElementFcn(model, x, y, z) setElementCollisionsEnabled(element1, false) setElementAlpha(element1, 150) local element2 = createElementFcn(model, x, y, z) setElementCollisionsEnabled(element2, false) setElementAlpha(element2, 150) table.insert(elements, {element1, element2, offsetX, offsetZ, mode}) end end local rx, ry, rz = 0, 0, 0 local totalTime = 0 local rotationTime = 5 addEventHandler("onClientPreRender", getRootElement(), function(deltaT) if not getKeyState("space") then totalTime = totalTime + deltaT if totalTime > 5*rotationTime*1000 then totalTime = 0 rx, ry, rz = 0, 0, 0 elseif totalTime > 4*rotationTime*1000 then rx = rx + 360/rotationTime*deltaT/1000 ry = ry + 360/rotationTime*deltaT/1000 rz = rz + 360/rotationTime*deltaT/1000 elseif totalTime > 3*rotationTime*1000 then rz = rz + 360/rotationTime*deltaT/1000 rx, ry = 0, 0 elseif totalTime > 2*rotationTime*1000 then ry = ry + 360/rotationTime*deltaT/1000 rx, rz = 0, 0 elseif totalTime > 1*rotationTime*1000 then rx = rx + 360/rotationTime*deltaT/1000 ry, rz = 0, 0 end end local angles = {rx, ry, rz} for i=1,3 do if angles[i] < 0 then angles[i] = angles[i] + 360 elseif angles[i] >= 360 then angles[i] = angles[i] - 360 end end rx, ry, rz = unpack(angles) local text = string.format("desired %f %f %f ", rx, ry, rz) for _, elementData in ipairs(elements) do local element1, element2, offsetX, offsetZ, rotationMode = unpack(elementData) local erx, ery, erz = getElementRotation(element1, rotationMode) setElementPosition(element1, x - offsetX, y, z + offsetZ) setElementRotation(element1, rx, ry, rz, rotationMode) setElementVelocity(element1, 0, 0, 0) setElementPosition(element2, x - offsetX, y, z + offsetZ) setElementRotation(element2, erx, ery, erz, rotationMode) setElementVelocity(element2, 0, 0, 0) end local width, height = guiGetScreenSize() local nbLines = #(text:split("\n")) dxDrawRectangle(0, 0, width, 10+nbLines*dxGetFontHeight(1.5, "clear"), tocolor(0, 0, 0, 128)) dxDrawText(text, 0, 0, width, height, tocolor(255, 255, 255, 255), 1.5, "clear", "center", "top") end ) end ) function string.split(str, delim) local startPos = 1 local endPos = string.find(str, delim, 1, true) local result = {} while endPos do table.insert(result, string.sub(str, startPos, endPos-1)) startPos = endPos + 1 endPos = string.find(str, delim, startPos, true) end table.insert(result, string.sub(str, startPos)) return result end setElementRotation and getElementRotation now take an extra optional string parameter that can be "default", "ZXY" or "ZYX" (cf example code) I have only changed the clientside version. Do you want this also on the serverside? If so, can you tell me if I should do the exact thing on the server side or if by any chance some of the files I modified are shared (didn't look like it when compiling).
-
In order not to brake compatibility, an extra parameter to get and set could be used to specify the rotation order. By default, this order would be what it is now depending on the element type. setElementRotation(object, rx, ry, rz) would be same as setElementRotation(object, rx, ry, rz, "ZXY") getElementRotation(object) would be same as getElementRotation(object, "ZXY") same goes for vehicle and ped/player with their respective current rotation order. Like that, old scripts keep working, but at least those desiring to use unified versions could do so by passing the same order to all their set/gets. (Only a subset could be implemented, no need to go through all http://en.wikipedia.org/wiki/Euler_angl ... _rotations) However it's just a thought, because I'm not sure the standard MTA scripter cares that much or knows really what "euler" and "rotation order" mean
-
I know I'm answering to myself, however if I were to continue on the same post it would start to be way too long. So, since yesterday I have figured out what was wrong, or actually, why the code provided worked when it shouldn't have. The "patch" I based my fix on labeled the rotations wrongly. After intensive testing and headache, I can make the following statement with confidence: 'When calling setElementRotation(rx, ry, rz), MTA applies the rotations in the following order : - for objects: ZXY - for vehicles: ZYX - for peds: -Z-Y-X When doing getElementRotation, peds are bugged and return coordinates in Z-Y-X instead of -Z-Y-X (cf video)". I have changed the test case to illustrate several things: - I show the difference between default MTA setElementRotation/getElementRotation and euler based set/get both with ZXY and ZYX meaning - I create 2 instances of each element, to set the rotation of the 2nd of each, I get the rotation of the first and set it to the second (it's used to show if both function work properly). As the video shows, it help me notice that MTA getElementRotation is bugged for peds. - Now the object is also a plane and is placed at the same location/orientation as the vehicle. It helps seeing the differences (which only happen in default MTA functions). The code for the fix, with math details in the comments: Euler conversion functions -- RX(theta) -- | 1 0 0 | -- | 0 c(theta) -s(theta) | -- | 0 s(theta) c(theta) | -- RY(theta) -- | c(theta) 0 s(theta) | -- | 0 1 0 | -- | -s(theta) 0 c(theta) | -- RZ(theta) -- | c(theta) -s(theta) 0 | -- | s(theta) c(theta) 0 | -- | 0 0 1 | -- ZXY = RZ(z).RX(x).RY(y) -- | c(y)*c(z)-s(x)*s(y)*s(z) -c(x)*s(z) s(x)*c(y)*s(z)+s(y)*c(z) | -- | c(y)*s(z)+s(x)*s(y)*c(z) c(x)*c(z) s(y)*s(z)-s(x)*c(y)*c(z) | -- | -c(x)*s(y) s(x) c(x)*c(y) | -- ZYX = RZ(z).RY(y).RX(x) -- | c(y)*c(z) s(x)*s(y)*c(z)-c(x)*s(z) s(x)*s(z)+c(x)*s(y)*c(z) | -- | c(y)*s(z) s(x)*s(y)*s(z)+c(x)*c(z) c(x)*s(y)*s(z)-s(x)*c(z) | -- | -s(y) s(x)*c(y) c(x)*c(y) | function euler_ZXY_to_ZYX(ZXY_x, ZXY_y, ZXY_z) ZXY_x = math.rad(ZXY_x) ZXY_y = math.rad(ZXY_y) ZXY_z = math.rad(ZXY_z) local cx = math.cos(ZXY_x) local sx = math.sin(ZXY_x) local cy = math.cos(ZXY_y) local sy = math.sin(ZXY_y) local cz = math.cos(ZXY_z) local sz = math.sin(ZXY_z) --ZYX (unknown) => A = s(x)*c(y) / c(x)*c(y) = t(x) --ZXY (known) => A = s(x) / c(x)*c(y) local ZYX_x = math.atan2(sx, cx*cy) --ZYX (unknown) => B = c(y)*s(z) / c(y)*c(z) = t(z) --ZXY (known) => B = c(y)*s(z)+s(x)*s(y)*c(z) / c(y)*c(z)-s(x)*s(y)*s(z) local ZYX_z = math.atan2(cy*sz+sx*sy*cz, cy*cz-sx*sy*sz) --ZYX (unknown) => C = -s(y) --ZXY (known) => C = -c(x)*s(y) --Isn't asin not as good as atan2 ? solution tried with atan2 doesn't work that well though local ZYX_y = math.asin(cx*sy) return math.deg(ZYX_x), math.deg(ZYX_y), math.deg(ZYX_z) end function euler_ZYX_to_ZXY(ZYX_x, ZYX_y, ZYX_z) ZYX_x = math.rad(ZYX_x) ZYX_y = math.rad(ZYX_y) ZYX_z = math.rad(ZYX_z) local cx = math.cos(ZYX_x) local sx = math.sin(ZYX_x) local cy = math.cos(ZYX_y) local sy = math.sin(ZYX_y) local cz = math.cos(ZYX_z) local sz = math.sin(ZYX_z) --ZXY (unknown) => A = -c(x)*s(z) / c(x)*c(z) => t(z) = -A --ZYX (known) => A = s(x)*s(y)*c(z)-c(x)*s(z) / s(x)*s(y)*s(z)+c(x)*c(z) local ZXY_z = math.atan2(-(sx*sy*cz-cx*sz), sx*sy*sz+cx*cz) --ZXY (unknown) => B = -c(x)*s(y) / c(x)*c(y) => t(y) = -B --ZYX (known) => B = -s(y) / c(x)*c(y) local ZXY_y = math.atan2(sy, cx*cy) --ZXY (unknown) => C = s(x) --ZYX (known) => C = s(x)*c(y) --Isn't asin not as good as atan2 ? solution tried with atan2 doesn't work that well though local ZXY_x = math.asin(sx*cy) return math.deg(ZXY_x), math.deg(ZXY_y), math.deg(ZXY_z) end New setElementRotation_Euler??? and getElementRotation_Euler??? (both for ZXY and ZYX) function setElementRotation_EulerZXY(element, rx, ry, rz, bZeroOnZisEast) if not element or not isElement(element) then return false end if bZeroOnZisEast then rz = rz - 90 rx, ry = -ry, rx end local mta_rx, mta_ry, mta_rz = rx, ry, rz local eType = getElementType(element) if eType == "ped" or eType == "player" then mta_rx, mta_ry, mta_rz = euler_ZXY_to_ZYX(rx, ry, rz) mta_rx, mta_ry, mta_rz = -mta_rx, -mta_ry, -mta_rz --In set, ped is -Z-Y-X elseif eType == "vehicle" then mta_rx, mta_ry, mta_rz = euler_ZXY_to_ZYX(rx, ry, rz) end return setElementRotation(element, mta_rx, mta_ry, mta_rz) end function setElementRotation_EulerZYX(element, rx, ry, rz, bZeroOnZisEast) if not element or not isElement(element) then return false end if bZeroOnZisEast then rz = rz - 90 rx, ry = -ry, rx end local mta_rx, mta_ry, mta_rz = rx, ry, rz local eType = getElementType(element) if eType == "ped" or eType == "player" then mta_rx, mta_ry, mta_rz = -mta_rx, -mta_ry, -mta_rz --In set, ped is -Z-Y-X elseif eType == "object" then mta_rx, mta_ry, mta_rz = euler_ZYX_to_ZXY(rx, ry, rz) end return setElementRotation(element, mta_rx, mta_ry, mta_rz) end function getElementRotation_EulerZXY(element, bZeroOnZisEast) if not element or not isElement(element) then return false end local rx, ry, rz = getElementRotation(element) local eType = getElementType(element) if eType == "ped" or eType == "player" then rx, ry, rz = -rx, -ry, rz --In get, ped is Z-Y-X rx, ry, rz = euler_ZYX_to_ZXY(rx, ry, rz) elseif eType == "vehicle" then rx, ry, rz = euler_ZYX_to_ZXY(rx, ry, rz) end if bZeroOnZisEast then rz = rz + 90 rx, ry = ry, -rx end return rx, ry, rz end function getElementRotation_EulerZYX(element, bZeroOnZisEast) if not element or not isElement(element) then return false end local rx, ry, rz = getElementRotation(element) local eType = getElementType(element) if eType == "ped" or eType == "player" then rx, ry, rz = -rx, -ry, rz --In get, ped is Z-Y-X elseif eType == "object" then rx, ry, rz = euler_ZXY_to_ZYX(rx, ry, rz) end if bZeroOnZisEast then rz = rz + 90 rx, ry = ry, -rx end return rx, ry, rz end And the updated test case addCommandHandler("test", function () local spacing = 20 local x, y, z = -1375.1043701172, -25.0885887146, (14.1484375 + 5) local modes = { {setElementRotation, getElementRotation}, {setElementRotation_EulerZXY, getElementRotation_EulerZXY}, {setElementRotation_EulerZYX, getElementRotation_EulerZYX} } local elementsConf = { [70] = {createPed, 2}, [519] = {createVehicle, 0}, [1681] = {createObject, 0}, } local elements = {} for model, elementConf in pairs(elementsConf) do local createElementFcn, offsetZ = unpack(elementConf) for itMode, mode in ipairs(modes) do local offsetX = (itMode-1)*spacing local element1 = createElementFcn(model, x, y, z) setElementCollisionsEnabled(element1, false) setElementAlpha(element1, 150) local element2 = createElementFcn(model, x, y, z) setElementCollisionsEnabled(element2, false) setElementAlpha(element2, 150) table.insert(elements, {element1, element2, offsetX, offsetZ, mode}) end end local bZeroOnZIsEast = false local rx, ry, rz = 0, 0, 0 local totalTime = 0 local rotationTime = 5 addEventHandler("onClientPreRender", getRootElement(), function(deltaT) if not getKeyState("space") then totalTime = totalTime + deltaT if totalTime > 5*rotationTime*1000 then totalTime = 0 bZeroOnZIsEast = not bZeroOnZIsEast rx, ry, rz = 0, 0, 0 elseif totalTime > 4*rotationTime*1000 then rx = rx + 360/rotationTime*deltaT/1000 ry = ry + 360/rotationTime*deltaT/1000 rz = rz + 360/rotationTime*deltaT/1000 elseif totalTime > 3*rotationTime*1000 then rz = rz + 360/rotationTime*deltaT/1000 rx, ry = 0, 0 elseif totalTime > 2*rotationTime*1000 then ry = ry + 360/rotationTime*deltaT/1000 rx, rz = 0, 0 elseif totalTime > 1*rotationTime*1000 then rx = rx + 360/rotationTime*deltaT/1000 ry, rz = 0, 0 end end local angles = {rx, ry, rz} for i=1,3 do if angles[i] < 0 then angles[i] = angles[i] + 360 elseif angles[i] >= 360 then angles[i] = angles[i] - 360 end end rx, ry, rz = unpack(angles) local text = nil text = string.format("bZeroOnZIsEast: %s\ndesired %f %f %f ", tostring(bZeroOnZIsEast), rx, ry, rz) for _, elementData in ipairs(elements) do local element1, element2, offsetX, offsetZ, mode = unpack(elementData) local setRotationFcn, getRotationFcn = unpack(mode) local erx, ery, erz = getRotationFcn(element1, bZeroOnZIsEast) setElementPosition(element1, x - offsetX, y, z + offsetZ) setRotationFcn(element1, rx, ry, rz, bZeroOnZIsEast) setElementVelocity(element1, 0, 0, 0) setElementPosition(element2, x - offsetX, y, z + offsetZ) setRotationFcn(element2, erx, ery, erz, bZeroOnZIsEast) setElementVelocity(element2, 0, 0, 0) end local width, height = guiGetScreenSize() local nbLines = #(text:split("\n")) dxDrawRectangle(0, 0, width, 10+nbLines*dxGetFontHeight(1.5, "clear"), tocolor(0, 0, 0, 128)) dxDrawText(text, 0, 0, width, height, tocolor(255, 255, 255, 255), 1.5, "clear", "center", "top") end ) end ) I hope the countless hours I spent figuring out the underlying problem and the real rotation orders will be of some use to some of you.