Artificial horizon / horizon indicator

I am working on a GUI that will place a line over the horizon depending on the orientation of a part to which your camera’s cframe is set to.

I’ve looked at a few topics about this already but I haven’t really found any useful information.

1 Like

Please be more specific with your problem

Also there’s many different kinds of artifical horizons and you also need to clarify on that too

1 Like

Yes of course, sorry. I made a sketch of the thing I wish to achieve.

1 Like

Simplest way would be to use the Highlight instance to draw an outline around the part, but you can’t really do much customization.

An alternative way would be make your own Highlighting method. Map the 3D corner position of the part into the 2D viewport screen and then connect them with UI Frames. You would have to use a convex hull algorithm to find the outermost points and then draw a polygon around it, which will draw an outline around the original part. Downside is you can really only use this for cubes, rectangular prisms, and wedges since cylinders, spheres, and custom meshes don’t have definitive “corners”.

Attached is an example. It’s very buggy because of how WorldToViewportPoint works on positions outside of the screen.

outlineParts.rbxl (55.6 KB)

--!strict

local p = workspace:WaitForChild('Part')
local sui = script.Parent

local sizeToCorner: {Vector3} = {
	Vector3.new(1, 1, 1),
	Vector3.new(1, 1, -1),
	Vector3.new(-1, 1, 1),
	Vector3.new(-1, 1, -1),
	Vector3.new(1, -1, 1),
	Vector3.new(1, -1, -1),
	Vector3.new(-1, -1, 1),
	Vector3.new(-1, -1, -1),
}

--this convexHull algorithm was pulled from here: https://love2d.org/forums/viewtopic.php?t=93842
--it was written for vanilla Lua, so Luau optimizations may not apply here!!!
--ccw() was modified to work with roblox Vector2 objects
local ccw = function(a: Vector2, b: Vector2, c: Vector2): boolean
	return (b.X - a.X) * (c.Y - a.Y) > (b.Y - a.Y) * (c.X - a.X)
end
local function convexHull(pl: {Vector2}): {Vector2}
	if #pl == 0 then return {} end

	table.sort(pl, function(left,right)
		return left.X < right.X
	end)

	local h = {}

	-- lower hull
	for i,pt in pairs(pl) do
		while #h >= 2 and not ccw(h[#h-1], h[#h], pt) do
			table.remove(h,#h)
		end
		table.insert(h,pt)
	end

	-- upper hull
	local t = #h + 1
	for i=#pl, 1, -1 do
		local pt = pl[i]
		while #h >= t and not ccw(h[#h-1], h[#h], pt) do
			table.remove(h,#h)
		end
		table.insert(h,pt)
	end

	table.remove(h,#h)

	return h
end

local function drawHorizon(part: Part, outline: {Frame}): {Frame}
	local cam = workspace.CurrentCamera
	local pos3D: CFrame = part.CFrame
	local size3DHalf: Vector3 = part.Size*.5
	local corners2D: {Vector2} = table.create(8)
	for _, v in sizeToCorner do
		local pos2D: Vector3 = cam:WorldToViewportPoint((pos3D*CFrame.new(v*size3DHalf)).Position)
		table.insert(corners2D, Vector2.new(pos2D.X, pos2D.Y))
	end
	
	for _, f in outline do
		f.Visible = false
	end
	
	local hull: {Vector2} = convexHull(corners2D)
	for i, v in hull do
		local to: Vector2 = hull[i+1] or hull[1]
		
		local exists: Frame? = outline[i]
		local f: Frame
		if exists then
			f = exists
		else
			f = Instance.new('Frame')
			f.AnchorPoint = Vector2.one*.5
			f.Parent = sui
			outline[i] = f
		end
		
		local disp: Vector2 = to - v
		local midpoint: Vector2 = to:Lerp(v, .5)
		f.Rotation = math.deg(math.atan2(disp.Y, disp.X))
		f.Position = UDim2.fromOffset(midpoint.X, midpoint.Y)
		f.Size = UDim2.fromOffset(disp.Magnitude, 4)
		
		f.Visible = true
	end
	
	return outline
end

local hull: {Frame} = {}
game:GetService('RunService').RenderStepped:Connect(function()
	hull = drawHorizon(p, hull)
end)
2 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.