Jump to content

Element Rotation


Kayl

Recommended Posts

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

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

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

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

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

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

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

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

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 account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...