File size: 12,362 Bytes
b6a38d7 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 |
DefineClass.RotateGizmo = {
__parents = { "XEditorGizmo" },
HasLocalCSSetting = true,
HasSnapSetting = false,
Title = "Rotate gizmo (E)",
Description = false,
ActionSortKey = "2",
ActionIcon = "CommonAssets/UI/Editor/Tools/RotateGizmo.tga",
ActionShortcut = "E",
UndoOpName = "Rotated %d object(s)",
mesh_x = pstr(""),
mesh_y = pstr(""),
mesh_z = pstr(""),
mesh_big = pstr(""),
mesh_sphere = pstr(""),
b_over_x = false,
b_over_y = false,
b_over_z = false,
b_over_big = false,
b_over_sphere = false,
v_axis_x = axis_x,
v_axis_y = axis_y,
v_axis_z = axis_z,
scale = 100,
thickness = 100,
opacity = 255,
sensitivity = 100,
operation_started = false,
tangent_vector = false,
tangent_offset = false,
tangent_axis = false,
tangent_angle = false,
initial_orientations = false,
init_intersect = false,
init_pos = false,
rotation_center = false,
rotation_axis = false,
rotation_angle = 0,
rotation_snap = false,
text = false,
}
function RotateGizmo:Done()
self:DeleteText()
end
function RotateGizmo:DeleteText()
if self.text then
self.text:delete()
self.text = nil
end
end
function RotateGizmo:CheckStartOperation(pt)
return #editor.GetSel() > 0 and self:IntersectRay(camera.GetEye(), ScreenToGame(pt))
end
function RotateGizmo:StartOperation(pt)
if not self.b_over_sphere then
self.text = XTemplateSpawn("XFloatingText")
self.text:SetTextStyle("GizmoText")
self.text:AddDynamicPosModifier({id = "attached_ui", target = self:GetPos()})
self.text.TextColor = RGB(255, 255, 255)
self.text.ShadowType = "outline"
self.text.ShadowSize = 1
self.text.ShadowColor = RGB(64, 64, 64)
self.text.Translate = false
end
-- with Slabs in the selection, rotate around a half-grid aligned point (if rotating around the Z axis)
-- to ensure Slab positions are aligned after the rotation
local center = self:GetPos()
local objs = editor.GetSel()
self:CursorIntersection(pt) -- initialize rotation_axis
if not self.b_over_sphere and self.rotation_axis == axis_z and HasAlignedObjs(objs) then
local snap = const.SlabSizeX
center = point(center:x() / snap * snap, center:y() / snap * snap, center:z()) or center
end
self.init_intersect = self:CursorIntersection(pt)
self.init_pos = self:GetPos()
self.rotation_center = center
self.initial_orientations = {}
for i, obj in ipairs(objs) do
self.initial_orientations[obj] = { axis = obj:GetVisualAxis(), angle = obj:GetVisualAngle(), offset = obj:GetVisualPos() - center }
end
self.operation_started = true
end
function RotateGizmo:PerformOperation(pt)
local intersection = self:CursorIntersection(pt)
if not intersection then return end
self.rotation_angle = 0
local axis, angle
local offset = MulDivRound(intersection - self.init_intersect, 9 * self.sensitivity, self.scale) -- 9 is the magic number that gives +/-45 degree range
if self.b_over_sphere then
local normal = Normalize(self:GetVisualPos() - camera.GetEye())
local axisX = Normalize(Cross(normal, axis_z))
local axisY = Normalize(Cross(normal, axisX))
local angleX = Dot(offset, axisY) / 4096
local angleY = -Dot(offset, axisX) / 4096
axis, angle = ComposeRotation(axisX, angleX, axisY, angleY)
else
axis, angle = self.rotation_axis, Dot(offset, self.tangent_vector) / 4096
if XEditorSettings:GetGizmoRotateSnapping() then
local new_angle = self:SnapAngle(angle)
angle, self.rotation_snap = new_angle, new_angle ~= angle
end
self.rotation_angle = angle / 60.0
end
local center = self.rotation_center
for obj, data in pairs(self.initial_orientations) do
local newPos = center + RotateAxis(data.offset, axis, angle)
if not obj:IsValidZ() then
newPos = newPos:SetInvalidZ()
end
XEditorSetPosAxisAngle(obj, newPos, ComposeRotation(data.axis, data.angle, axis, angle))
end
Msg("EditorCallback", "EditorCallbackRotate", table.keys(self.initial_orientations))
end
function RotateGizmo:EndOperation()
self.tangent_vector = false
self.tangent_offset = false
self.tangent_axis = false
self.tangent_angle = false
self.rotation_axis = false
self.initial_orientations = false
self.init_intersect = false
self.init_pos = false
self.operation_started = false
self:DeleteText()
end
function RotateGizmo:SnapAngle(angle)
local snapAngle = 15 * 60
local snapAngleTollerance = 120
if abs(angle) > snapAngleTollerance then -- don't snap at the 0 degree rotation, allowing for small adjustments
if abs(angle % snapAngle) < snapAngleTollerance or abs(angle % snapAngle) > (snapAngle - snapAngleTollerance) then
angle = (angle + snapAngleTollerance) / snapAngle * snapAngle
end
end
return angle
end
function RotateGizmo:Render()
local obj = not XEditorIsContextMenuOpen() and selo()
if obj then
if self.local_cs then
self.v_axis_x, self.v_axis_y, self.v_axis_z = GetAxisVectors(obj)
else
self.v_axis_x = axis_x
self.v_axis_y = axis_y
self.v_axis_z = axis_z
self:SetOrientation(axis_z, 0)
end
self:SetPos(self.init_pos or CenterOfMasses(editor.GetSel()))
self:CalculateScale()
self:SetMesh(self:RenderGizmo())
else self:SetMesh(pstr("")) end
end
function RotateGizmo:RenderGizmo()
local vpstr = pstr("")
local center = point(0, 0, 0)
local pos = selo() and selo():GetVisualPos() or GetTerrainCursor()
local normal = pos - camera.GetEye()
normal = Normalize(normal)
local bigTorusAxis, bigTorusAngle = GetAxisAngle(axis_z, normal)
bigTorusAxis = Normalize(camera.GetEye() - self:GetPos())
bigTorusAngle = bigTorusAngle / 60
self.mesh_big = self:RenderBigTorus(nil, bigTorusAxis)
self.mesh_sphere = self:RenderCircle(nil, bigTorusAxis, bigTorusAngle)
self.mesh_x = self:RenderTorusAndAxis(nil, self.v_axis_x, self.b_over_x, normal)
self.mesh_y = self:RenderTorusAndAxis(nil, self.v_axis_y, self.b_over_y, normal)
self.mesh_z = self:RenderTorusAndAxis(nil, self.v_axis_z, self.b_over_z, normal)
if self.text then
self.text:SetText((self.rotation_snap and "<color 255 235 64>" or "") .. string.format("%.2f°", self.rotation_angle))
end
vpstr = self:RenderBigTorus(vpstr, bigTorusAxis, self.b_over_big, true)
vpstr = self:RenderOutlineTorus(vpstr, bigTorusAxis)
vpstr = self:RenderCircle(vpstr, bigTorusAxis, bigTorusAngle, self.b_over_sphere)
vpstr = self:RenderTorusAndAxis(vpstr, self.v_axis_x, self.b_over_x, normal, RGBA(192, 0, 0, self.opacity), true)
vpstr = self:RenderTorusAndAxis(vpstr, self.v_axis_y, self.b_over_y, normal, RGBA(0, 192, 0, self.opacity), true)
vpstr = self:RenderTorusAndAxis(vpstr, self.v_axis_z, self.b_over_z, normal, RGBA(0, 0, 192, self.opacity), true)
return self:RenderTangent(vpstr)
end
function RotateGizmo:CalculateScale()
local eye = camera.GetEye()
local dir = self:GetVisualPos()
local ray = dir - eye
local cameraDistanceSquared = ray:x() * ray:x() + ray:y() * ray:y() + ray:z() * ray:z()
local cameraDistance = 0
if cameraDistanceSquared >= 0 then cameraDistance = sqrt(cameraDistanceSquared) end
self.scale = cameraDistance / 20 * self.scale / 100
end
function RotateGizmo:IntersectRay(pt1, pt2)
self.b_over_z = IntersectRayMesh(self, pt1, pt2, self.mesh_z)
self.b_over_x = IntersectRayMesh(self, pt1, pt2, self.mesh_x)
self.b_over_y = IntersectRayMesh(self, pt1, pt2, self.mesh_y)
self.b_over_big = IntersectRayMesh(self, pt1, pt2, self.mesh_big)
self.b_over_sphere = false
if self.b_over_z then
self.b_over_x = false
self.b_over_y = false
return true
elseif self.b_over_x then
self.b_over_y = false
self.b_over_z = false
return true
elseif self.b_over_y then
self.b_over_z = false
self.b_over_x = false
return true
elseif self.b_over_big then
return true
end
self.b_over_sphere = IntersectRayMesh(self, pt1, pt2, self.mesh_sphere)
return self.b_over_sphere
end
function RotateGizmo:CursorIntersection(mouse_pos)
local pt1 = camera.GetEye()
local pt2 = ScreenToGame(mouse_pos)
local pos = self:GetVisualPos()
local pt_intersection = self.b_over_x or self.b_over_y or self.b_over_z or self.b_over_big
if pt_intersection then
if not self.operation_started then
self.rotation_axis =
self.b_over_x and self.v_axis_x or
self.b_over_y and self.v_axis_y or
self.b_over_z and self.v_axis_z or
self.b_over_big and (camera.GetEye() - pos)
self.rotation_axis = Normalize(self.rotation_axis)
self.tangent_offset = pt_intersection - pos
self.tangent_vector = Cross(self.rotation_axis, Normalize(self.tangent_offset))
self.tangent_vector = Normalize(self.tangent_vector)
self.tangent_axis, self.tangent_angle = GetAxisAngle(axis_z, self.tangent_vector)
self.tangent_axis, self.tangent_angle = Normalize(self.tangent_axis), self.tangent_angle / 60
end
local axis
if self.b_over_x then
axis = self.v_axis_x
elseif self.b_over_y then
axis = self.v_axis_y
elseif self.b_over_z then
axis = self.v_axis_z
elseif self.b_over_big then
axis = camera.GetEye() - pos
end
local camDir = Normalize(camera.GetEye() - pos)
local camX = Normalize(Cross((camDir), axis_z))
local planeB = pos + camX
local planeC = pos + Normalize(Cross((camDir), camX))
local ptA = pos + self.tangent_offset
local ptB = ptA + self.tangent_vector
local intersection = IntersectRayPlane(pt1, pt2, pos, planeB, planeC)
return ProjectPointOnLine(ptA, ptB, intersection)
elseif self.b_over_sphere then
local axis = Normalize(camera.GetEye() - pos)
local screenX = Cross(axis, axis_z)
local screenY = Cross(axis, axis_x)
local planeB = pos + screenX
local planeC = pos + screenY
return IntersectRayPlane(pt1, pt2, pos, planeB, planeC)
end
end
function RotateGizmo:RenderTangent(vpstr)
if self.tangent_vector then
local radius = 0.1 * self.scale * self.thickness / 100
local length = 2.5 * self.scale
local coneHeight = 0.50 * self.scale
local coneRadius = 0.30 * self.scale * self.thickness / 100
local color = RGBA(255, 0, 255, self.opacity)
vpstr = AppendConeVertices(vpstr, point(0, 0, -length), point(0, 0, length * 2), radius, radius, self.tangent_axis, self.tangent_angle, color, self.tangent_offset)
vpstr = AppendConeVertices(vpstr, point(0, 0, -length), point(0, 0, -coneHeight), coneRadius, 0, self.tangent_axis, self.tangent_angle, color, self.tangent_offset)
vpstr = AppendConeVertices(vpstr, point(0, 0, length), point(0, 0, coneHeight), coneRadius, 0, self.tangent_axis, self.tangent_angle, color, self.tangent_offset)
end
return vpstr
end
function RotateGizmo:RenderCircle(vpstr, axis, angle, selected)
vpstr = vpstr or pstr("")
local HSeg = 32
local center = point(0, 0, 0)
local rad = Cross(axis, axis_z)
local radius = 2.3 * self.scale
local color = selected and RGBA(255, 255, 0, 70 * self.opacity / 255) or RGBA(0, 0, 0, 0)
rad = Normalize(rad)
rad = MulDivRound(rad, radius, 4096)
for i = 1, HSeg do
local pt = Rotate(rad, MulDivRound(360 * 60, i, HSeg))
pt = RotateAxis(pt, rad, angle * 60)
local nextPt = Rotate(rad, MulDivRound(360 * 60, i + 1, HSeg))
nextPt = RotateAxis(nextPt, rad, angle * 60)
vpstr:AppendVertex(center, color)
vpstr:AppendVertex(pt)
vpstr:AppendVertex(nextPt)
end
return vpstr
end
function RotateGizmo:RenderBigTorus(vpstr, axis, selected, visual)
local radius1 = 3.5 * self.scale
local radius2 = visual and 0.15 * self.scale * self.thickness / 100 or 0.15 * self.scale
local color = selected and RGBA(255, 255, 0, self.opacity) or RGBA(0, 192, 192, self.opacity)
return AppendTorusVertices(vpstr, radius1, radius2, axis, color)
end
function RotateGizmo:RenderTorusAndAxis(vpstr, axis, selected, normal, color, visual)
local radius1 = 2.3 * self.scale
local radius2 = visual and 0.15 * self.scale * self.thickness / 100 or 0.15 * self.scale
color = selected and RGBA(255, 255, 0, self.opacity) or color
local height = 1.5 * self.scale
local radius = 0.05 * self.scale
vpstr = AppendTorusVertices(vpstr, radius1, radius2, axis, color, normal)
local axis, angle = GetAxisAngle(axis_z, axis)
axis = Normalize(axis)
angle = angle / 60
return AppendConeVertices(vpstr, nil, point(0, 0, height), radius, radius, axis, angle, color)
end
function RotateGizmo:RenderOutlineTorus(vpstr, axis)
local radius1 = 2.3 * self.scale
local radius2 = 0.15 * self.scale * self.thickness / 100
local color = RGBA(128, 128, 128, 192 * self.opacity / 255)
return AppendTorusVertices(vpstr, radius1, radius2, axis, color)
end |