This code will work for determining if a player is looking directly at any arbitrary 3d2d rectangle.
local isec = IntersectRayWithPlane( EyePos( ply ), Forward( EyeAngles( ply ) ), pos, trace.HitNormal )
if isec then
local ap = pos - isec
local ab = pos - tr
local ad = pos - bl
local ap_ab = Dot( ap, ab )
local ab_ab = Dot( ab, ab )
local ap_ad = Dot( ap, ad )
local ad_ad = Dot( ad, ad )
if 0 < ap_ab and ap_ab < ab_ab and 0 < ap_ad and ap_ad < ad_ad
and IsLineOfSightClear( ply, isec ) then --and if they have line of sight of the panel
--Do whatever you'd like, they're looking directly at the panel
where trace.HitNormal is the vector of the direction your panel is facing. You could also use ang:Up() if you’re not using a trace to determine the position of your panel.
pos is the vector of the top left of the rectangle, isec is the intersection of the players view with the plane of the panel, tr is the vector of the top right of the rectangle, and bl is the vector of the bottom left of the rectangle.
I name my variables in accordance with the line that the resulting vector represents.
With a given point ‘P’, and a rectangle defined by the points A(top left), B(top right), C(bottom right), and D(bottom left), a the resulting line from vector P to vector A would be called ‘ap’. Fairly simple.
When getting the dot-product of two vectors, I name the result vector1_vector2 so the dot product of AP and AD gives you ‘ap_ad’ again, fairly simple.
Finally, when evaluating, you just do the comparison that I did. (My brother helped me with this part).
Check that the dot product of AP and AB is between 0 and the dot product of AB and AB. Then check that the dot product of AP and AD is between 0 and the dot product of AD and AD.
If that statement evaluates to true, then your plane intersection vector is inside the rectangle! I also added a check to make sure that the player has view of the intersection point, so that you can’t ‘look’ at a panel that is behind a wall or some such.