DefineClass.MoveGizmoTool = { __parents = { "Mesh" }, mesh_x = pstr(""), mesh_y = pstr(""), mesh_z = pstr(""), mesh_xy = pstr(""), mesh_xz = pstr(""), mesh_yz = pstr(""), b_over_axis_x = false, b_over_axis_y = false, b_over_axis_z = false, b_over_plane_xy = false, b_over_plane_xz = false, b_over_plane_yz = false, v_axis_x = axis_x, v_axis_y = axis_y, v_axis_z = axis_z, opacity = 110, r_color = RGB(192, 0, 0), g_color = RGB(0, 192, 0), b_color = RGB(0, 0, 192), -- rotation arrows, clockwise (cw) and counter-clockwise (ccw) arrow_xcw = pstr(""), arrow_xccw = pstr(""), arrow_ycw = pstr(""), arrow_yccw = pstr(""), arrow_zcw = pstr(""), arrow_zccw = pstr(""), -- settings rotation_arrows_on = false, rotation_arrows_z_only = false, } function MoveGizmoTool:ApplyOpacity(color, opacity) local r, g, b = GetRGB(color) return RGBA(r, g, b, self.opacity) end function MoveGizmoTool:RenderGizmo() local vpstr = pstr("") self.mesh_x = self:RenderAxis(nil, axis_y, self.b_over_axis_x, false) self.mesh_y = self:RenderAxis(nil, -axis_x, self.b_over_axis_y, false) self.mesh_z = self:RenderAxis(nil, axis_z, self.b_over_axis_z, false) self.mesh_xy = self:RenderPlane(nil, axis_z) self.mesh_xz = self:RenderPlane(nil, axis_x) self.mesh_yz = self:RenderPlane(nil, -axis_y) local r = self:ApplyOpacity(self.r_color) local g = self:ApplyOpacity(self.g_color) local b = self:ApplyOpacity(self.b_color) self.arrow_xcw = self:RenderRotationArrow(nil, axis_x, g, false) self.arrow_xccw = self:RenderRotationArrow(nil, axis_x, g, true) self.arrow_ycw = self:RenderRotationArrow(nil, -axis_y, r, false) self.arrow_yccw = self:RenderRotationArrow(nil, -axis_y, r, true) self.arrow_zcw = self:RenderRotationArrow(nil, axis_z, b, false) self.arrow_zccw = self:RenderRotationArrow(nil, axis_z, b, true) vpstr = self:RenderAxis(vpstr, axis_y, self.b_over_axis_x, true, r) vpstr = self:RenderAxis(vpstr, -axis_x, self.b_over_axis_y, true, g) vpstr = self:RenderAxis(vpstr, axis_z, self.b_over_axis_z, true, b) vpstr = self:RenderPlaneOutlines(vpstr) if self.b_over_plane_xy then vpstr = self:RenderPlane(vpstr, axis_z) end if self.b_over_plane_xz then vpstr = self:RenderPlane(vpstr, axis_x) end if self.b_over_plane_yz then vpstr = self:RenderPlane(vpstr, -axis_y) end if self.rotation_arrows_on then if not self.rotation_arrows_z_only then self:RenderRotationArrow(vpstr, axis_x, g, false) self:RenderRotationArrow(vpstr, axis_x, g, true) self:RenderRotationArrow(vpstr, -axis_y, r, false) self:RenderRotationArrow(vpstr, -axis_y, r, true) end self:RenderRotationArrow(vpstr, axis_z, b, false) self:RenderRotationArrow(vpstr, axis_z, b, true) end return vpstr end function MoveGizmoTool:ChangeScale() 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 MoveGizmoTool:CursorIntersection(mouse_pos) local pt1 = camera.GetEye() local precision = 128 -- should not lead to overflow for maps up to 20x20 km (with guim == 1000) local dir = ScreenToGame(mouse_pos, precision) - pt1 * precision local pt2 = pt1 + dir local pos = self:GetVisualPos() if self.b_over_axis_x or self.b_over_axis_y or self.b_over_axis_z then local axis if self.b_over_axis_x then axis = self.v_axis_x elseif self.b_over_axis_y then axis = self.v_axis_y elseif self.b_over_axis_z then axis = self.v_axis_z 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 local ptB = ptA + axis local intersection = IntersectRayPlane(pt1, pt2, pos, planeB, planeC) intersection = ProjectPointOnLine(ptA, ptB, intersection) return intersection elseif self.b_over_plane_xy or self.b_over_plane_xz or self.b_over_plane_yz then local normal, planeB, planeC if self.b_over_plane_xy then normal = self.v_axis_z planeB = pos + self.v_axis_x planeC = pos + self.v_axis_y elseif self.b_over_plane_xz then normal = self.v_axis_y planeB = pos + self.v_axis_x planeC = pos + self.v_axis_z elseif self.b_over_plane_yz then normal = self.v_axis_x planeB = pos + self.v_axis_y planeC = pos + self.v_axis_z end local intersection = IntersectRayPlane(pt1, pt2, pos, planeB, planeC) intersection = ProjectPointOnPlane(pos, normal, intersection) return intersection end end function MoveGizmoTool:IntersectRay(pt1, pt2) self.b_over_plane_xy = false self.b_over_plane_xz = false self.b_over_plane_yz = false self.b_over_axis_x = IntersectRayMesh(self, pt1, pt2, self.mesh_x) self.b_over_axis_y = IntersectRayMesh(self, pt1, pt2, self.mesh_y) self.b_over_axis_z = IntersectRayMesh(self, pt1, pt2, self.mesh_z) local hit, axis, ccw if IntersectRayMesh(self, pt1, pt2, self.arrow_xcw ) then hit, axis, ccw = true, axis_x, false elseif IntersectRayMesh(self, pt1, pt2, self.arrow_xccw) then hit, axis, ccw = true, axis_x, true elseif IntersectRayMesh(self, pt1, pt2, self.arrow_ycw ) then hit, axis, ccw = true, -axis_y, false elseif IntersectRayMesh(self, pt1, pt2, self.arrow_yccw) then hit, axis, ccw = true, -axis_y, true elseif IntersectRayMesh(self, pt1, pt2, self.arrow_zcw ) then hit, axis, ccw = true, axis_z, false elseif IntersectRayMesh(self, pt1, pt2, self.arrow_zccw) then hit, axis, ccw = true, axis_z, true end self.b_over_rotation_arrow = hit self.arrow_axis = axis self.arrow_ccw = ccw if self.b_over_axis_x then self.b_over_axis_y = false self.b_over_axis_z = false return true elseif self.b_over_axis_y then self.b_over_axis_x = false self.b_over_axis_z = false return true elseif self.b_over_axis_z then self.b_over_axis_x = false self.b_over_axis_y = false return true else local overPlaneXY, lenXY = IntersectRayMesh(self, pt1, pt2, self.mesh_xy) local overPlaneXZ, lenXZ = IntersectRayMesh(self, pt1, pt2, self.mesh_xz) local overPlaneYZ, lenYZ = IntersectRayMesh(self, pt1, pt2, self.mesh_yz) if not (overPlaneXY or overPlaneXZ or overPlaneYZ) then return end if overPlaneXY then self.b_over_plane_xy = true return true elseif lenXZ and lenYZ then if lenXZ < lenYZ then self.b_over_plane_xz = overPlaneXZ return overPlaneXZ else self.b_over_plane_yz = overPlaneYZ return overPlaneYZ end elseif overPlaneXZ then self.b_over_plane_xz = true return true elseif overPlaneYZ then self.b_over_plane_yz = true return true end end end function MoveGizmoTool:RenderAxis(vpstr, axis, selected, visual, color) vpstr = vpstr or pstr("") local cylinderRadius = visual and 0.1 * self.scale * self.thickness / 100 or 0.1 * self.scale local cylinderHeight = 4.0 * self.scale local coneRadius = visual and 0.45 * self.scale * self.thickness / 100 or 0.45 * self.scale local coneHeight = 1.0 * self.scale color = selected and RGBA(255, 255, 0, self.opacity) or color vpstr = AppendConeVertices(vpstr, nil, point(0, 0, cylinderHeight), cylinderRadius, cylinderRadius, axis, 90, color) vpstr = AppendConeVertices(vpstr, point(0, 0, cylinderHeight), point(0, 0, coneHeight), coneRadius, 0, axis, 90, color) return vpstr end function MoveGizmoTool:RenderPlaneOutlines(vpstr) local height = 2.5 * self.scale local radius = 0.05 * self.scale * self.thickness / 100 local r = self:ApplyOpacity(self.r_color) local g = self:ApplyOpacity(self.g_color) local b = self:ApplyOpacity(self.b_color) vpstr = AppendConeVertices(vpstr, nil, point(0, 0, height), radius, radius, axis_z, 90, r, point(height, 0, 0)) vpstr = AppendConeVertices(vpstr, nil, point(0, 0, height), radius, radius, -axis_x, 90, r, point(height, 0, 0)) vpstr = AppendConeVertices(vpstr, nil, point(0, 0, height), radius, radius, axis_z, 90, g, point(0, height, 0)) vpstr = AppendConeVertices(vpstr, nil, point(0, 0, height), radius, radius, axis_y, 90, g, point(0, height, 0)) vpstr = AppendConeVertices(vpstr, nil, point(0, 0, height), radius, radius, axis_y, 90, b, point(0, 0, height)) vpstr = AppendConeVertices(vpstr, nil, point(0, 0, height), radius, radius, -axis_x, 90, b, point(0, 0, height)) return vpstr end function MoveGizmoTool:TransformPointsToAxisPlane(axis, ...) local angle = (axis == -axis_y or axis == axis_x) and 90 * 60 or 0 local pts = table.pack(...) for i, pt in ipairs(pts) do pts[i] = RotateAxis(pt, axis, angle) end return pts end function MoveGizmoTool:RenderPlane(vpstr, axis) vpstr = vpstr or pstr("") local color = RGBA(255, 255, 0, self.opacity * 200 / 255) local dist = 2.5 * self.scale local pts = self:TransformPointsToAxisPlane(axis, point30, point(dist, 0, 0), point(dist, dist, 0), point(0, dist, 0)) vpstr:AppendVertex(pts[1], color) vpstr:AppendVertex(pts[2]) vpstr:AppendVertex(pts[4]) vpstr:AppendVertex(pts[3]) vpstr:AppendVertex(pts[2]) vpstr:AppendVertex(pts[4]) return vpstr end function MoveGizmoTool:RenderRotationArrow(vpstr, axis, color, ccw) local size = 0.7 * self.scale local dist = 2.1 * self.scale local offs = 0.1 * self.scale local corner = point(dist, dist, 0) local offset = point(offs, offs, 0) local a1, a2, a3 = point(size, 0, 0), point(0, size, 0), point(size, size, 0) a1 = Rotate(a1 + offset, (ccw and 45 or -45) * 60) + corner a2 = Rotate(a2 + offset, (ccw and 45 or -45) * 60) + corner a3 = Rotate(a3 + offset, (ccw and 45 or -45) * 60) + corner if self.b_over_rotation_arrow and axis == self.arrow_axis and self.arrow_ccw == ccw then color = RGBA(255, 255, 0, self.opacity * 200 / 255) end vpstr = vpstr or pstr("") local pts = self:TransformPointsToAxisPlane(axis, a1, a2, a3) vpstr:AppendVertex(pts[1], color) vpstr:AppendVertex(pts[2]) vpstr:AppendVertex(pts[3]) return vpstr end DefineClass.MoveGizmo = { __parents = { "XEditorGizmo", "MoveGizmoTool" }, HasLocalCSSetting = true, HasSnapSetting = true, Title = "Move gizmo (W)", Description = false, ActionSortKey = "1", ActionIcon = "CommonAssets/UI/Editor/Tools/MoveGizmo.tga", ActionShortcut = "W", UndoOpName = "Moved %d object(s)", rotation_arrows_on = true, operation_started = false, initial_positions = false, initial_pos = false, initial_gizmo_pos = false, move_by_slabs = false, } function MoveGizmo:CheckStartOperation(pt, btn_pressed) local objs = editor.GetSel() if #objs == 0 then return end -- check move operations first local ret = self:IntersectRay(camera.GetEye(), ScreenToGame(pt)) if ret then return ret end if self.b_over_rotation_arrow and btn_pressed then XEditorUndo:BeginOp{ objects = objs, name = string.format("Rotated %d objects", #objs) } local center = self:GetVisualPos() local axis = self.arrow_axis if axis ~= axis_z then axis = SetLen(Cross(self.arrow_axis, axis_z), 4096) end local angle = self.arrow_ccw and 90*60 or -90*60 if self.local_cs then axis = self:GetRelativePoint(axis) - self:GetVisualPos() end local rotate_logic = XEditorRotateLogic:new() SuspendPassEditsForEditOp() rotate_logic:InitRotation(objs, center, 0) rotate_logic:Rotate(objs, "group_rotation", center, axis, angle) ResumePassEditsForEditOp() XEditorUndo:EndOp(objs) return "break" end end function MoveGizmo:StartOperation(pt) self.initial_positions = {} local has_aligned, move_by_z = false, self.b_over_axis_z or self.b_over_plane_xz or self.b_over_plane_yz for _, obj in ipairs(editor.GetSel()) do local pos = obj:GetVisualPos() if obj:IsKindOf("AlignedObj") then pos = move_by_z and pos:AddZ((const.SlabSizeZ or 0) / 2) or obj:GetPos() has_aligned = true end self.initial_positions[obj] = pos end self.move_by_slabs = has_aligned self.initial_pos = self:CursorIntersection(pt) self.initial_gizmo_pos = self:GetVisualPos() self.operation_started = true end function MoveGizmo:PerformOperation(pt) local intersection = self:CursorIntersection(pt) if intersection then local objs = {} local vMove = intersection - self.initial_pos for obj, pos in pairs(self.initial_positions) do XEditorSnapPos(obj, pos, vMove, self.move_by_slabs) objs[#objs + 1] = obj end self:SetPos(self.initial_gizmo_pos + vMove) Msg("EditorCallback", "EditorCallbackMove", objs) end end function MoveGizmo:EndOperation() self.initial_positions = false self.initial_pos = false self.operation_started = false self.initial_gizmo_pos = false end local saneBox = box(-const.SanePosMaxXY, -const.SanePosMaxXY, const.SanePosMaxXY - 1, const.SanePosMaxXY - 1) local saneZ = const.SanePosMaxZ function MoveGizmo: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) self:SetAxisAngle(obj:GetAxis(), obj:GetAngle()) 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 if not self.operation_started then local pos = CenterOfMasses(editor.GetSel()) local clamped_pos = ClampPoint(pos, saneBox):SetZ(Clamp(pos:z(), -saneZ, saneZ - 1)) self:SetPos(clamped_pos, 0) end self:ChangeScale() self:SetMesh(self:RenderGizmo()) else self:SetMesh(pstr("")) end end