Kayl Posted November 5, 2010 Share Posted November 5, 2010 Hi there, I have filled a bug report regarding the inconsistency of result when calling setElementRotation on different element types (vehicle, ped, object): http://bugs.mtasa.com/view.php?id=5631 An illustrated example can be found below: In the current state, I find it a bit useless to have unified functions like setElementRotation and getElementRotation if semantically the outcome of each is different. Even if a fix was released in any coming nightly, I would still need my script to work for older versions. So, I create this topic not that much to complain about the whole issue, but to seek explanations from people that have experience with it (either from scripting or from within MTA dev team). A reason I see, that could explain the differences observed, would be different rotation sequences when interpreting the Euler angles. When doing getElementRotation and getElementMatrix and then converting the matrix to XYZ euler angles, it seems to work fine for vehicles, so it seems vehicles use XYZ. From a video I found on youtube (http://www.youtube.com/watch?v=MuMImJuoeIQ) it seems objects might use YXZ. Can it be confirmed/explained by some MTA folks? And what is the rotation sequence for peds? --------------------------------------------------------- Edit 15:06 UTC+1 Ok so, looking at the other video with the YXZ patch and its bug report, http://bugs.mtasa.com/view.php?id=4609, I managed to find a workaround. With this workaround, setElementRotation and getElementRotation use unified Euler angle meaning (XYZ that is). So calling any of those on a ped, an object, or a vehicle ends up giving the same result. First of all, video time: Since I was in the process of using fixed functions, I introduced an extra parameter to setElementRotation and getElementRotation which allows the user to use angles that make mathematical sense, hence putting angles so that a rotation of 0 on Z means element facing +X (East). So it's now: setElementRotation(element, rx, ry, rz, bZeroOnZIsEast) getElementRotation(element, bZeroOnZIsEast) The video shows the test sequence in both cases (normal case of 0 = North, and logical case of 0 = East). I didn't try to fix any other type as the 3 illustrated in the video. The fix code (to be put before any of your code using setElementRotation or getElementRotation (for instance in a file declared before the others in meta.xml)): --Adapted from [url=http://bugs.mtasa.com/view.php?id=4609]http://bugs.mtasa.com/view.php?id=4609[/url] function Euler_XYZ_to_YXZ(rx, ry, rz) rx = math.rad(rx) ry = math.rad(ry) rz = math.rad(rz) local sinX = math.sin(rx) local cosX = math.cos(rx) local sinY = math.sin(ry) local cosY = math.cos(ry) local sinZ = math.sin(rz) local cosZ = math.cos(rz) local newRx = math.asin(cosY * sinX) local newRy = math.atan2(sinY, cosX * cosY) local newRz = math.atan2(cosX * sinZ - cosZ * sinX * sinY, cosX * cosZ + sinX * sinY * sinZ) return math.deg(newRx), math.deg(newRy), math.deg(newRz) end -- Adapted and fixed from [url=http://bugs.mtasa.com/view.php?id=4609]http://bugs.mtasa.com/view.php?id=4609[/url] function Euler_YXZ_to_XYZ(rx, ry, rz) rx = math.rad(rx) ry = math.rad(ry) rz = math.rad(rz) local sinX = math.sin(rx) local cosX = math.cos(rx) local sinY = math.sin(ry) local cosY = math.cos(ry) local sinZ = math.sin(rz) local cosZ = math.cos(rz) local newRx = math.atan2(sinX, cosX * cosY) local newRy = math.asin(cosX * sinY) local newRz = math.atan2(cosZ * sinX * sinY + cosY * sinZ, cosY * cosZ - sinX * sinY * sinZ) return math.deg(newRx), math.deg(newRy), math.deg(newRz) end local _setElementRotation = setElementRotation function setElementRotation(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 eType = getElementType(element) if eType == "ped" or eType == "player" then return _setElementRotation(element, -rx, -ry, -rz) elseif eType == "object" then return _setElementRotation(element, Euler_XYZ_to_YXZ(rx, ry, rz)) else return _setElementRotation(element, rx, ry, rz) end end local _getElementRotation = getElementRotation function getElementRotation(element, bZeroOnZIsEast) if not element or not isElement(element) then return false end local eType = getElementType(element) local rx, ry, rz = _getElementRotation(element) if bZeroOnZIsEast then rz = rz + 90 rx, ry = ry, -rx end if eType == "ped" or eType == "player" then rx, ry = -rx, -ry elseif eType == "object" then for i=1,3 do rx, ry, rz = Euler_YXZ_to_XYZ(rx, ry, rz) 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 return unpack(angles) end And the new test code: addCommandHandler("test", function () local x, y, z = -1375.1043701172, -25.0885887146, (14.1484375 + 5) local elements = {} elements[1] = {createPed(0, x, y, z ), 0} elements[2] = {createVehicle(520, x, y, z), 10} elements[3] = {createObject(1632, x, y, z), -10} for i=1,3 do setElementCollisionsEnabled(elements[i][1], false) end local bZeroOnZIsEast = false local rx, ry, rz = 0, 0, 0 local totalTime = 0 local rotationTime = 5 local keyPositions = { {0, 0, 0}, {45, 0, 0}, {-45, 0, 0}, {0, 45, 0}, {0, -45, 0}, {0, 0, 45}, {0, 0, -45} } local currentKey = 1 addEventHandler("onClientPreRender", getRootElement(), function(deltaT) if not getKeyState("space") then totalTime = totalTime + deltaT if currentKey <= #keyPositions and totalTime > rotationTime*1000 then currentKey = currentKey + 1 rx, ry, rz = 0, 0, 0 totalTime = 0 end if currentKey <= #keyPositions then rx, ry, rz = unpack(keyPositions[currentKey]) else if totalTime > 5*rotationTime*1000 then totalTime = 0 bZeroOnZIsEast = not bZeroOnZIsEast currentKey = 1 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 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 _, data in ipairs(elements) do local element = data[1] local erx, ery, erz = getElementRotation(element, bZeroOnZIsEast) text = text.."\n"..string.format("%s %f %f %f ", getElementType(element), erx, ery, erz) local deltaX = data[2] local rotMult = data[3] setElementPosition(element, x+deltaX, y, z) setElementRotation(element, rx, ry, rz, bZeroOnZIsEast) setElementVelocity(element, 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 ) Edit: 21:13 UTC+1 the getElementRotation in the case of objects is still not perfect, needs fixing. I'll post fix when I have it -> Indeed, playing with only 2 angles at a time, it turns out in fact objects are XYZ and vehicles are YXZ and peds are -Y-X-Z. Besides, the conversions provided use asin which is not as mathematically as smooth as atan2, so I'll provide clearer functions as soon as I'm sure they work well. Link to comment
Kayl Posted November 6, 2010 Author Share Posted November 6, 2010 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. Link to comment
dzek (varez) Posted November 6, 2010 Share Posted November 6, 2010 thanks, i noticed that there is something wrong with rotations, but worked it around with some help - this can be useful for the future Link to comment
eAi Posted November 6, 2010 Share Posted November 6, 2010 It'd be pretty trivial to fix this in MTA, but obviously this is a fairly major breaking change. Maybe such things could be supported through tags in the meta file, though this isn't ideal. Or we just make everyone fix their scripts for 1.1 Link to comment
Kayl Posted November 6, 2010 Author Share Posted November 6, 2010 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 Link to comment
dzek (varez) Posted November 6, 2010 Share Posted November 6, 2010 standard scripter is scripting gates and/or some mp3 music on servers unfortunately (or sometimes they want also hunter top times, and afk killers) - nothing complicated. nothing that require to get/set rotation at all. advanced ones will be happy to see this implemented. and this extra parameter is very good idea Link to comment
eAi Posted November 7, 2010 Share Posted November 7, 2010 It's not ideal, but it'd be better than what we have now, I agree. Kayl, it'd be great if you could write a patch for the code to implement this, it shouldn't be too complex if you're reasonably good at coding PM me if you need help. Link to comment
Kayl Posted November 8, 2010 Author Share Posted November 8, 2010 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). Link to comment
eAi Posted November 8, 2010 Share Posted November 8, 2010 Yes, it should be server-side too, if possible. It should be pretty similar to implement it, I'd have thought. The patch looks good at a first glance, someone else will give it a test - I hope Link to comment
Kayl Posted November 8, 2010 Author Share Posted November 8, 2010 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 ) Link to comment
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now