Skip to content

Commit ced470e

Browse files
authored
editor: Rotation improvements (#400)
* Rotate using quaternions * lint warning
1 parent 9a5b8c8 commit ced470e

File tree

17 files changed

+435
-169
lines changed

17 files changed

+435
-169
lines changed

[editor]/edf/edf.lua

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,7 @@ function edfCreateElement(elementType, creatorClient, fromResource, parametersTa
618618
if dataField == "position" then
619619
edfSetElementPosition(newElement, dataValue[1], dataValue[2], dataValue[3])
620620
elseif dataField == "rotation" then
621-
edfSetElementRotation(newElement, dataValue[1], dataValue[2], dataValue[3])
621+
edfSetElementRotation(newElement, dataValue[1], dataValue[2], dataValue[3], dataValue[4])
622622
elseif dataField == "interior" then
623623
setElementInterior(newElement, dataValue)
624624
setElementData(newElement, dataField, dataValue)
@@ -899,25 +899,24 @@ function edfSetElementPosition(element, px, py, pz)
899899
end
900900

901901
--Sets an element's rotation, or its rotX/Y/Z element data
902-
function edfSetElementRotation(element, rx, ry, rz)
902+
function edfSetElementRotation(element, rx, ry, rz, rotOrder)
903903
local ancestor = edfGetAncestor(element) or element
904-
setElementData(ancestor, "rotation", {rx, ry, rz})
905-
904+
setElementData(ancestor, "rotation", {rx, ry, rz, rotOrder})
906905
local etype = getElementType(element)
907906
if etype == "object" or etype == "vehicle" then
908-
if rx and ry and rz and setElementRotation(element, rx, ry, rz) then
907+
if rx and ry and rz and setElementRotation(element, rx, ry, rz, rotOrder) then
909908
triggerEvent ( "onElementPropertyChanged", ancestor, "rotation" )
910909
return true
911910
end
912911
elseif etype == "player" or etype == "ped" then
913-
if setElementRotation(element, 0, 0, rz) then
912+
if setElementRotation(element, 0, 0, rz, rotOrder) then
914913
triggerEvent ( "onElementPropertyChanged", ancestor, "rotation" )
915914
return true
916915
end
917916
else
918917
local handle = edfGetHandle(element)
919918
if handle then
920-
if setElementRotation(handle, rx, ry, rz) then
919+
if setElementRotation(handle, rx, ry, rz, rotOrder) then
921920
triggerEvent ( "onElementPropertyChanged", ancestor, "rotation" )
922921
return true
923922
end

[editor]/edf/edf_client.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,9 @@ function edfSetElementRotation(element, rx, ry, rz)
147147
if rx and ry and rz then
148148
local etype = getElementType(element)
149149
if etype == "object" or etype == "vehicle" or etype == "player" or etype == "ped" then
150+
-- Clear the quat rotation when set manually
151+
exports.editor_main:clearElementQuat(element)
152+
150153
return setElementRotation(element, rx, ry, rz)
151154
else
152155
local handle = edfGetHandle(element)

[editor]/editor_gui/client/options_action.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ function optionsActions.enableColPatch(value)
4444
end
4545
end
4646

47+
function optionsActions.enableRotPatch(value)
48+
optionsData.enableRotPatch = value
49+
end
50+
4751
function optionsActions.smoothCamMove (value)
4852
local loaded = freecam.setFreecamOption ( "smoothMovement", value )
4953
if ( loaded ) then

[editor]/editor_gui/client/options_backend.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ local xmlVariants = {
2727
["enablePrecisionSnap"]="enableprecisionsnap",
2828
["enablePrecisionRotation"]="enableprecisionrotation",
2929
["enableColPatch"]="enablecolpatch",
30+
["enableRotPatch"]="enablerotpatch",
3031
["fov"]="fov",
3132
}
3233
local nodeTypes = {
@@ -57,6 +58,7 @@ local nodeTypes = {
5758
["enablePrecisionRotation"]="bool",
5859
["enableXYZlines"]="bool",
5960
["enableColPatch"]="bool",
61+
["enableRotPatch"]="bool",
6062
["fov"]="progress",
6163
}
6264
local defaults = {
@@ -86,6 +88,7 @@ local defaults = {
8688
["enablePrecisionRotation"]=false,
8789
["enableXYZlines"]=true,
8890
["enableColPatch"]=false,
91+
["enableRotPatch"]=true,
8992
["fov"]=dxGetStatus()["SettingFOV"],
9093
}
9194

[editor]/editor_gui/client/options_gui.lua

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,17 @@ function createOptionsDialog()
3232

3333
---------------------------------
3434
dialog.enableBox = editingControl.boolean:create{["x"]=0.60,["y"]=0.02,["width"]=1,["height"]=0.1,["relative"]=true,["parent"]=dialog.generalTab,["label"]="Enable Bounding Box"}
35-
dialog.enableXYZlines = editingControl.boolean:create{["x"]=0.60,["y"]=0.1,["width"]=1,["height"]=0.1,["relative"]=true,["parent"]=dialog.generalTab,["label"]="Enable XYZ Lines"}
35+
dialog.enableXYZlines = editingControl.boolean:create{["x"]=0.60,["y"]=0.12,["width"]=1,["height"]=0.1,["relative"]=true,["parent"]=dialog.generalTab,["label"]="Enable XYZ Lines"}
3636
---------------------------------
37-
dialog.enablePrecisionSnap = editingControl.boolean:create{["x"]=0.60,["y"]=0.2,["width"]=1,["height"]=0.1,["relative"]=true,["parent"]=dialog.generalTab,["label"]="Precise Position"}
38-
guiCreateLabel ( 0.55, 0.3, 70, 17, "Snap Level:", true, dialog.generalTab )
39-
dialog.precisionLevel = editingControl.dropdown:create{["x"]=0.68,["y"]=0.3,["width"]=0.3,["height"]=0.07,["dropWidth"]=0.30,["dropHeight"]=0.55,["relative"]=true,["rows"]={"10","5","2","1","0.1","0.01","0.001","0.0001"},["parent"]=dialog.generalTab}
40-
guiCreateLabel ( 0.47, 0.4, 70, 17, "Rotation Precision:", true, dialog.generalTab )
41-
dialog.precisionRotLevel = editingControl.dropdown:create{["x"]=0.68,["y"]=0.4,["width"]=0.3,["height"]=0.07,["dropWidth"]=0.30,["dropHeight"]=0.55,["relative"]=true,["rows"]={"180","90","45","30","20","10","5","1"},["parent"]=dialog.generalTab}
42-
dialog.enablePrecisionRotation = editingControl.boolean:create{["x"]=0.60,["y"]=0.5,["width"]=1,["height"]=0.1,["relative"]=true,["parent"]=dialog.generalTab,["label"]="Precise Rotation"}
37+
dialog.enablePrecisionSnap = editingControl.boolean:create{["x"]=0.60,["y"]=0.22,["width"]=1,["height"]=0.1,["relative"]=true,["parent"]=dialog.generalTab,["label"]="Enable Snap - Precise Position"}
38+
guiCreateLabel ( 0.47, 0.34, 70, 17, "Position Snap Level:", true, dialog.generalTab )
39+
dialog.precisionLevel = editingControl.dropdown:create{["x"]=0.68,["y"]=0.34,["width"]=0.3,["height"]=0.07,["dropWidth"]=0.30,["dropHeight"]=0.55,["relative"]=true,["rows"]={"10","5","2","1","0.1","0.01","0.001","0.0001"},["parent"]=dialog.generalTab}
40+
guiCreateLabel ( 0.47, 0.44, 70, 17, "Rotation Snap Level:", true, dialog.generalTab )
41+
dialog.precisionRotLevel = editingControl.dropdown:create{["x"]=0.68,["y"]=0.44,["width"]=0.3,["height"]=0.07,["dropWidth"]=0.30,["dropHeight"]=0.55,["relative"]=true,["rows"]={"180","90","45","30","20","10","5","1"},["parent"]=dialog.generalTab}
42+
dialog.enablePrecisionRotation = editingControl.boolean:create{["x"]=0.60,["y"]=0.52,["width"]=1,["height"]=0.1,["relative"]=true,["parent"]=dialog.generalTab,["label"]="Enable Snap - Precise Rotation"}
4343
---------------------------------
44-
dialog.enableColPatch = editingControl.boolean:create{["x"]=0.60,["y"]=0.6,["width"]=1,["height"]=0.1,["relative"]=true,["parent"]=dialog.generalTab,["label"]="Enable collision patches"}
44+
dialog.enableColPatch = editingControl.boolean:create{["x"]=0.60,["y"]=0.62,["width"]=1,["height"]=0.1,["relative"]=true,["parent"]=dialog.generalTab,["label"]="Enable collision patches"}
45+
dialog.enableRotPatch = editingControl.boolean:create{["x"]=0.60,["y"]=0.72,["width"]=1,["height"]=0.1,["relative"]=true,["parent"]=dialog.generalTab,["label"]="Enable rotation patches"}
4546
---------------------------------
4647
--camera settings
4748
guiCreateLabel ( 0.02, 0.02, 1, 0.1, "Normal camera move speed:", true, dialog.cameraTab )

[editor]/editor_gui/client/tutorialdefinition.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ tutorial = {
141141
addEventHandler ("onClientElementDrop",root,tutorialAction.dropped2)
142142
removeEventHandler ("onClientElementSelect",root,tutorialAction.selected1)
143143
end },
144-
{ message = "A tip for selecting objects with poor collisions is to enable 'High sensitivity mode'. Press "..cc.high_sensitivity_mode.." to toggle this. It will detect objects with poor collisions at the expense of accuracy.",
144+
{ message = "A tip for selecting objects with poor collisions is to enable 'High sensitivity mode'. Press "..cc.high_sensitivity_mode.." to toggle this. It will detect objects with poor collisions at the expense of accuracy. If you still cannot select the object, try 'Enable collision patches' in options.",
145145
initiate = function()
146146
setTimer ( tutorialNext, 15000, 1 )
147147
end },
@@ -158,7 +158,7 @@ tutorial = {
158158
setTimer(tutorialNext,17000,1)
159159
end },
160160
--14
161-
{ message = "You can use the "..cc.mod_rotate.." modifier to switch into rotation mode. While holding down the "..cc.mod_rotate.." key, press the "..cc.element_move_forward..", "..cc.element_move_backward..", "..cc.element_move_left..", "..cc.element_move_right..", "..cc.element_move_upwards.." and "..cc.element_move_downwards.." keys to rotate the element.",
161+
{ message = "You can use the "..cc.mod_rotate.." and "..cc.mod_rotate_local.." modifiers to switch into rotation mode. While holding down the "..cc.mod_rotate.." or "..cc.mod_rotate_local.." key, press the "..cc.element_move_forward..", "..cc.element_move_backward..", "..cc.element_move_left..", "..cc.element_move_right..", "..cc.element_move_upwards.." and "..cc.element_move_downwards.." keys to rotate the element in world ("..cc.mod_rotate..") or local ("..cc.mod_rotate_local..") space.",
162162
initiate = function()
163163
setTimer(tutorialNext,16000,1)
164164
end },

[editor]/editor_main/client/controls.lua

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,21 @@ local defaultControls = {
2323
{ name="toggle_cursor", key ="f", friendlyName="Toggle Cursor" },
2424
{ name="select_target_keyboard", key ="mouse1", friendlyName="Select (Keyboard Mode)" },
2525
{ name="select_target_mouse", key ="mouse2", friendlyName="Select (Mouse Mode)" },
26-
{ name="quick_rotate_increase", key ="mouse_wheel_up", friendlyName="+Z Rotate (Rotate Modifier)" },
27-
{ name="quick_rotate_decrease", key ="mouse_wheel_down",friendlyName="-Z Rotate (Rotate Modifier)" },
26+
{ name="quick_rotate_increase", key ="mouse_wheel_up", friendlyName="+Z / Yaw (Rotate Modifier)" },
27+
{ name="quick_rotate_decrease", key ="mouse_wheel_down",friendlyName="-Z / Yaw (Rotate Modifier)" },
2828
{ name="zoom_in", key ="mouse_wheel_down",friendlyName="Increase Select Distance" },
2929
{ name="zoom_out", key ="mouse_wheel_up", friendlyName="Decrease Select Distance" },
3030
{ name="mod_fast_speed", key ="lshift", friendlyName="Fast speed Modifier" },
3131
{ name="mod_slow_speed", key ="lalt", friendlyName="Slow speed Modifier" },
32-
{ name="mod_rotate", key ="lctrl", friendlyName="Rotate Modifier" },
32+
{ name="mod_rotate", key ="lctrl", friendlyName="Rotate Modifier World Space" },
33+
{ name="mod_rotate_local", key ="rctrl", friendlyName="Rotate Modifier Local Space" },
3334
{ name="high_sensitivity_mode", key ="e", friendlyName="High Sensivity Mode" },
34-
{ name="element_move_right", key ="arrow_r", friendlyName="Move Element Right" },
35-
{ name="element_move_left", key ="arrow_l", friendlyName="Move Element Left" },
36-
{ name="element_move_forward", key ="arrow_u", friendlyName="Move Element Forward" },
37-
{ name="element_move_backward", key ="arrow_d", friendlyName="Move Element Backward" },
38-
{ name="element_move_upwards", key ="pgup", friendlyName="Move Element Upwards" },
39-
{ name="element_move_downwards", key ="pgdn", friendlyName="Move Element Downwards" },
35+
{ name="element_move_right", key ="arrow_r", friendlyName="Move Element Right / Yaw" },
36+
{ name="element_move_left", key ="arrow_l", friendlyName="Move Element Left / Yaw" },
37+
{ name="element_move_forward", key ="arrow_u", friendlyName="Move Element Forward / Pitch" },
38+
{ name="element_move_backward", key ="arrow_d", friendlyName="Move Element Backward / Pitch" },
39+
{ name="element_move_upwards", key ="pgup", friendlyName="Move Element Upwards / Roll" },
40+
{ name="element_move_downwards", key ="pgdn", friendlyName="Move Element Downwards / Roll" },
4041
{ name="camera_move_forwards", key ="w", friendlyName="Camera forwards" },
4142
{ name="camera_move_backwards", key ="s", friendlyName="Camera backwards" },
4243
{ name="camera_move_left", key ="a", friendlyName="Camera strafe left" },

[editor]/editor_main/client/main.lua

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ function startWhenLoaded()
151151
end
152152
if isInterfaceLoaded() then
153153
removeEventHandler("onClientResourceStart", root, startWhenLoaded)
154+
loadRotationFixXML()
154155
startEditor()
155156
end
156157
end
@@ -877,6 +878,9 @@ function dropElement(releaseLock,clonedrop)
877878

878879
-- trigger server selection events
879880
triggerServerEvent("onElementDrop", g_selectedElement)
881+
882+
-- Clear rotation as it can be rotated by other players
883+
clearElementQuat(g_selectedElement)
880884

881885
local droppedElement = g_selectedElement
882886
g_selectedElement = false
@@ -1281,8 +1285,10 @@ function setMovementType( movementType )
12811285
if g_arrowMarker then
12821286
if movementType == "move" then
12831287
setMarkerColor(g_arrowMarker, 255, 255, 0)
1284-
elseif movementType == "rotate" then
1288+
elseif movementType == "rotate" or movementType == "rotate_world" then
12851289
setMarkerColor(g_arrowMarker, 0, 255, 0)
1290+
elseif movementType == "rotate_local" then
1291+
setMarkerColor(g_arrowMarker, 0, 255, 255)
12861292
end
12871293
end
12881294
end
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
local rotationFixes = {}
2+
local elementQuat = {}
3+
4+
function loadRotationFixXML()
5+
local xmlRoot = xmlLoadFile("client/rotation_fix.xml")
6+
if not xmlRoot then
7+
outputDebugString("Cannot load rotation_fix.xml")
8+
return false
9+
end
10+
11+
rotationFixes = {}
12+
13+
local models = xmlNodeGetChildren(xmlRoot)
14+
for k,model in ipairs(models) do
15+
local id = tonumber(xmlNodeGetAttribute(model, "id"))
16+
local x = tonumber(xmlNodeGetAttribute(model, "x"))
17+
local y = tonumber(xmlNodeGetAttribute(model, "y"))
18+
local z = tonumber(xmlNodeGetAttribute(model, "z"))
19+
local w = tonumber(xmlNodeGetAttribute(model, "w"))
20+
21+
if id and x and y and z and w then
22+
rotationFixes[id] = {z, y, x, w}
23+
else
24+
outputDebugString("Incorrect entry in rotation_fix.xml")
25+
end
26+
end
27+
28+
xmlUnloadFile(xmlRoot)
29+
30+
return true
31+
end
32+
33+
function clearElementQuat(element)
34+
elementQuat[element] = nil
35+
end
36+
37+
addEventHandler("onClientElementDestroy", root, function()
38+
clearElementQuat(source)
39+
end)
40+
41+
function applyIncrementalRotation(element, axis, angle, world_space)
42+
-- Create the offset angle/axis quaternion
43+
local offset_quat -- Normalized
44+
local arad = math.rad(angle)
45+
local sina = math.sin(arad / 2)
46+
47+
if axis == "yaw" then
48+
offset_quat = {
49+
sina, 0, 0
50+
}
51+
elseif axis == "pitch" then
52+
offset_quat = {
53+
0, sina, 0
54+
}
55+
elseif axis == "roll" then
56+
offset_quat = {
57+
0, 0, sina
58+
}
59+
else
60+
return false
61+
end
62+
offset_quat[4] = math.cos(arad / 2)
63+
64+
-- Get current rotation
65+
local cur_quat
66+
if elementQuat[element] then
67+
cur_quat = elementQuat[element]
68+
else
69+
local euler_rot = {getElementRotation(element, "ZYX")}
70+
-- Is it not rotated ingame
71+
if euler_rot[1] == 0 and euler_rot[2] == 0 and euler_rot[3] == 0 then
72+
-- Is there a fix and are rotation patches enabled
73+
local id = getElementModel(element)
74+
if rotationFixes[id] and exports["editor_gui"]:sx_getOptionData("enableRotPatch") then
75+
-- Rotate from the fix
76+
cur_quat = {unpack(rotationFixes[id])}
77+
else
78+
-- Rotate from 0
79+
cur_quat = {1, 0, 0, 0}
80+
end
81+
else
82+
-- Convert the current euler rotation to quaternion
83+
cur_quat = getQuatFromEuler(euler_rot)
84+
end
85+
end
86+
87+
-- Rotate by the offset quaternion
88+
-- Right or left multiplication for world or local space
89+
if world_space then
90+
cur_quat = quatMul(cur_quat, offset_quat)
91+
else
92+
cur_quat = quatMul(offset_quat, cur_quat)
93+
end
94+
elementQuat[element] = cur_quat
95+
96+
-- Convert to euler and apply
97+
local cur_euler = getEulerFromQuat(cur_quat)
98+
setElementRotation(element, cur_euler[1], cur_euler[2], cur_euler[3], "ZYX")
99+
100+
return unpack(cur_euler)
101+
end
102+
103+
-- https://paroj.github.io/gltut/Positioning/Tut08%20Quaternions.html
104+
function quatMul(a, b)
105+
local result = {}
106+
result[1] = a[4]*b[1] + a[1]*b[4] + a[2]*b[3] - a[3]*b[2]
107+
result[2] = a[4]*b[2] + a[2]*b[4] + a[3]*b[1] - a[1]*b[3]
108+
result[3] = a[4]*b[3] + a[3]*b[4] + a[1]*b[2] - a[2]*b[1]
109+
result[4] = a[4]*b[4] - a[1]*b[1] - a[2]*b[2] - a[3]*b[3]
110+
return result
111+
end
112+
113+
-- http://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles
114+
function getQuatFromEuler(euler)
115+
local result = {}
116+
local tcos = {}
117+
local tsin = {}
118+
for i=1,3 do
119+
tcos[i] = math.cos(math.rad(euler[i]/2))
120+
tsin[i] = math.sin(math.rad(euler[i]/2))
121+
end
122+
result[1] = tcos[1]*tcos[2]*tcos[3] + tsin[1]*tsin[2]*tsin[3]
123+
result[2] = tsin[1]*tcos[2]*tcos[3] - tcos[1]*tsin[2]*tsin[3]
124+
result[3] = tcos[1]*tsin[2]*tcos[3] + tsin[1]*tcos[2]*tsin[3]
125+
result[4] = tcos[1]*tcos[2]*tsin[3] - tsin[1]*tsin[2]*tcos[3]
126+
return result
127+
end
128+
129+
function getEulerFromQuat(quat)
130+
local result = {}
131+
local q0 = quat[1]
132+
local q1 = quat[2]
133+
local q2 = quat[3]
134+
local q3 = quat[4]
135+
result[1] = math.deg(math.atan2(2*(q0*q1 + q2*q3), 1-2*(q1^2 + q2^2)))
136+
result[2] = math.deg(math.asin(2*(q0*q2 - q3*q1)))
137+
result[3] = math.deg(math.atan2(2*(q0*q3 + q1*q2), 1-2*(q2^2 + q3^2)))
138+
return result
139+
end

0 commit comments

Comments
 (0)