Last active
March 23, 2025 03:37
-
-
Save ZakBlystone/9a139c51015a9870c02eddc958926021 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- FastToScreen (CC0 Public Domain, free to use in whatever you want) | |
-- Created by: Zachary Blystone ( [email protected] ) | |
-- Localized functions | |
local __vunpack = FindMetaTable("Vector").Unpack | |
local __cos = math.cos | |
local __sin = math.sin | |
local __min = math.min | |
local __tan = math.tan | |
local __abs = math.abs | |
local __atan2 = math.atan2 | |
local mt = {} | |
mt.__index = mt | |
-- FastToScreen:SetBorder | |
-- Controls how projected points are clamped to viewport edges. | |
-- Takes one of the following (numbers specified in pixels): | |
-- 1. 'true': sets a 0px border on the projection | |
-- 2. 'false': clears any previously set border | |
-- 3. A float indicating how far inset the border should be | |
-- 4. A table containing the left,right,top,bottom insets for the border | |
-- {<left>, <right>, <top>, <bottom>} (e.g. {16,16,16,32}) | |
function mt:SetBorder(border) | |
if border == false then border = nil end | |
if border == true then border = 0 end | |
if type(border) == "number" then border = {border,border,border,border} end | |
self.t_border = border | |
return self | |
end | |
-- FastToScreen:Setup | |
-- Initializes the internal state needed to project points to the screen. | |
-- All arguments are optional: | |
-- origin: A Vector that defines camera position in world-space | |
-- angles: An Angle that defines the camera rotation in world-space | |
-- fov: The camera's field-of-view in degrees | |
-- width: The width of the screen or rendertarget | |
-- height: The height of the screen or rendertarget | |
function mt:Setup(origin, angles, fov, width, height) | |
-- Defaults if arguments are not provided | |
origin = origin or EyePos() | |
angles = angles or EyeAngles() | |
fov = fov or LocalPlayer():GetFOV() | |
width = width or ScrW() | |
height = height or ScrH() | |
-- Unpack basis and translation vectors | |
local ox,oy,oz = __vunpack(origin) | |
local fx,fy,fz = __vunpack(angles:Forward()) | |
local rx,ry,rz = __vunpack(angles:Right()) | |
local ux,uy,uz = __vunpack(angles:Up()) | |
-- Calculate 3x4 world-to-camera matrix | |
local m = self.m_wtoc | |
m[ 1], m[ 2], m[ 3], m[ 4] = -rx, -ry, -rz, (rx * ox + ry * oy + rz * oz) | |
m[ 5], m[ 6], m[ 7], m[ 8] = ux, uy, uz, -(ux * ox + uy * oy + uz * oz) | |
m[ 9], m[10], m[11], m[12] = fx, fy, fz, -(fx * ox + fy * oy + fz * oz) | |
-- Calculate width, height, and aspect ratio for Source | |
-- (Based on 4:3 'standard' ratio) | |
local aspect = width / height | |
self.f_wscale = 1 / ( __tan( fov * math.pi / 360 ) * aspect * 0.75 ) | |
self.f_hscale = aspect * self.f_wscale | |
self.f_wsize = width | |
self.f_hsize = height | |
return self | |
end | |
-- FastToScreen:ToScreenRaw | |
-- Projectes a given point in world-space to screen-space | |
-- (Point is not visible if z < 0) | |
-- Takes: raw x,y,z coordinates as numbers representing a point in world-space | |
-- Returns: | |
-- Projected x,y,z coordinates as numbers | |
-- Whether or not that point was clamped to an edge | |
-- If clamped; the angle of the point from the center of the screen | |
function mt:ToScreenRaw(x,y,z) | |
local m = self.m_wtoc | |
local b = self.t_border | |
local wsize, hsize = self.f_wsize, self.f_hsize | |
local on_edge, angle = false, 0 | |
-- Transform point into camera-space | |
x, y, z = | |
m[ 1] * x + m[ 2] * y + m[ 3] * z + m[ 4], | |
m[ 5] * x + m[ 6] * y + m[ 7] * z + m[ 8], | |
m[ 9] * x + m[10] * y + m[11] * z + m[12] | |
-- Transform point into NDC-space | |
local w = 1 / -__abs(z) | |
x = self.f_wscale * x * w | |
y = self.f_hscale * y * w | |
-- Clamp point to border if one is set up | |
if b then | |
-- Calculate border edges in NDC-space | |
local cl,cr,ct,cb = | |
-1 + 2 * ( b[1] / wsize ), | |
-1 + 2 * ( (wsize - b[2]) / wsize ), | |
-1 + 2 * ( b[3] / hsize ), | |
-1 + 2 * ( (hsize - b[4]) / hsize ) | |
-- If point lies outside border or is behind the camera, clamp it | |
if z < 0 or x - cl < 0 or x - cr > 0 or y - ct < 0 or y - cb > 0 then | |
-- Compute angle of point from center of view | |
on_edge = true | |
angle = __atan2(y,x) | |
local cos, sin = __cos(angle), __sin(angle) | |
local d = 2 | |
-- Project point onto edges, 'd' will be nearest edge distance | |
if sin > 0 then d = __min(d, (y - cb) / -sin) end | |
if sin < 0 then d = __min(d, (y - ct) / -sin) end | |
if cos > 0 then d = __min(d, (x - cr) / -cos) end | |
if cos < 0 then d = __min(d, (x - cl) / -cos) end | |
-- Move point along angle towards projected distance | |
x = x + cos * d | |
y = y + sin * d | |
end | |
end | |
-- Transform point into screen-space | |
x = (1 + x) * wsize * 0.5 | |
y = (1 + y) * hsize * 0.5 | |
return x, y, z, on_edge, angle | |
end | |
-- FastToScreen:ToScreen | |
-- Projectes a given point in world-space to screen-space | |
-- (Point is not visible if z < 0) | |
-- Takes: A Vector representing a point in world-space | |
-- Returns: | |
-- Projected x,y,z coordinates as numbers | |
-- Whether or not that point was clamped to an edge | |
-- If clamped; the angle of the point from the center of the screen | |
function mt:ToScreen(v) | |
return self:ToScreenRaw( __vunpack(v) ) | |
end | |
-- FastToScreen | |
-- Returns a new 'FastToScreen' object. | |
-- Must be initialized with :Setup before using. | |
function FastToScreen() | |
return setmetatable({ | |
m_wtoc = { | |
1,0,0,0, | |
0,1,0,0, | |
0,0,1,0}, | |
f_wscale = 1, | |
f_hscale = 1, | |
f_aspect = 1, | |
f_width = 0, | |
f_height = 0, | |
t_border = nil, | |
}, mt) | |
end | |
-- Example: Project origin to screen, clamped to a 16-px wide border | |
--[[ | |
local ts = FastToScreen():SetBorder(16) | |
hook.Add("HUDPaint", "marker_test", function() | |
ts:Setup() | |
local x,y,z = ts:ToScreenRaw( 0,0,0 ) | |
surface.SetDrawColor(255,255,255) | |
surface.DrawRect(x-5,y-5,10,10) | |
end) | |
]] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment