Face: facing towards the eye point or not?

I need to determine for a given face and the current eye point
whether the front or the back of the face is visible (or if I’m viewing the face on edge).

I could use any vertex and add 2 point3d’s, one offset in the direction of the face normal,
and the other in the direction of the reverse normal,
then calculate which is closest to the eye point.

But maybe there’s a more elegant method somewhere in the API?

How about:

   angle=the_face.normal.angle_between(Sketchup.active_model.active_view.camera.direction)

If the result is less than pi/2 (1.570796326794897 radians = 90 degrees), the back is visible. If greater than pi/2, the front is visible (the value will never be greater than pi). If exactly pi/2 the camera is viewing the face edge-on.

Thanks,
I’ll take your word for it, but I don’t really understand what it means to compute the angle between 2 vectors in 3D space that don’t intersect(?) I can do trig and plane geometry but vector algebra escapes me. Where can I learn more about it: vector calculus for dummies?

This is a list that should keep you busy for awhile:

1 Like

Yes I had seen Euclidean vector - Wikipedia
but I wouldn’t say that it is in the written in the “for dummies” format.
I’ll have to pretend I’m a genius and read it anyway.

Vectors will always have angles with other vectors - vectors ‘float’ in space and are not ‘anchored’ to any particular ‘point’.
But a ‘Line’ - which is a vector anchored at a given point [and which is NOT an “Edge” à la SketchUp] - is another thing entirely… it might/might-not intersect with another ‘Line’.

An Edge has a Line, it also has two points [obtainable from its vertices positions, and from the Line you can extract a point and a Vector.
The Edge’s Line’s Vector is not anchored to any point in space so it can be intersected with [to get an angle] any other vector…

Thanks TIG. That’s what this dummy needed.
So any 2 vectors will intersect to produce 2 angles which total to 180 degrees.
I’m assuming the angle_between method always returns the lesser of the 2.
But to use that angle in transforms I’m going to need a sign +/-.
How do I get that?

You could also use the cosine of the angle.

cos(angle) < 0.0 => front is visible
cos(angle) = 0.0 => you see it from the side
cos(angle) > 0.0 => back is visible

However I think this is true only if you actually ‘see’ the face as the face could be behind the camera.

Maybe you could first check for intersections with the face from the camera.eye in the camera.direction.

Something like:

ray = camera.eye, camera.direction
item = Sketchup.active_model.raytest(ray, false)
see_face = (item != nil) & (item[1] == face)

This assumes that face is not in a component or group.

True, though the (expensive) trig operations don’t really add any insight beyond testing the value of the angle.

This begs a bigger question: does it matter for Barry’s ultimate use whether the face is visible or not? It could also be non-visible if it is blocked by something else “in front of” it even when it is not behind the camera’s clipping plane. It might be blocked directly on the camera ray, but partially visible elsewhere on the view. The point being that to know whether more elaborate tests are necessary, we would need to know more about exactly what Barry is trying to accomplish and, in particular, how he selects a face to test.

What I’m trying to do involves moving objects glued to the selected face so that they are aligned Left or Right. Such concepts depend of course on your point of view.
(The face would have to be not blocked in order for the user to select it.)
So I don’t have to test for occlusion.

angle = face.normal.angle_between(view.camera.direction)
if angle < 90.degrees
    ### you are looking at the back of the face.
elsif angle > 90.degrees
    ### you can see the front.
else
    ### it's exactly 90.degrees you are looking at the face 'side on'...
end

This assumes you’ve somehow defined face earlier on, and that you have also set up
view = Sketchup.active_model.active_view

No, #angle_between is sensitive to the relative orientation of the two vectors and returns an angle between 0 (same direction) and 180 degrees (opposite direction). Also, strictly speaking vectors don’t intersect. They can’t, since as noted, they don’t have any fixed location in space.

Actually it returns the value in radians, but as TIG illustrated, you can either convert the return to degrees using the #radians method or convert 90 to radians using the #degrees method. These methods can be confusing. For example, #degrees means “this value is given in degrees, convert it to radians”. It might have been better named #degrees_to_radians.

Got it, I figured out that a vector can point only in one direction soon after posting.
So if I do a intersect_plane_plane, it returns a line, which is a point and a vector.
How does it decide which of infinite points on the line to be the returned point?
And which of the 2 possible vectors (one in each direction) does it return?

A line can be parameterized as a reference point and a vector, so that any location on the line is expressed as (assuming the vector has length 1) :

loc = ref_point + distance * vector

I believe (but haven’t re-checked just now) that SketchUp returns the point on the line closest to the origin.

Regarding your final question, I don’t know. So, when it matters I test the vector’s direction against some known reference.

OOPS!

The test I proposed earlier will work when the target face is pierced by the central ray of the camera or the camera is in parallel projection. But it can be fooled in perspective by a face that is away from the center of the viewport and oriented nearly edge-on to the camera’s direction vector. Such a face will present one of its surfaces in the viewport but the previous test will say it is edge on (or might report the wrong surface if it is nearly edge on).

To deal with this issue, when the camera is in perspective mode one needs to use a vector corresponding to a ray from the camera through the face instead of the camera’s direction vector. Such a vector can easily be obtained by subtracting the camera’s eye point from any point on the face (the subtraction operator on a Point3d returns a Vector3d expressing the direction and distance to the first point from the second). The choice of which point to test isn’t important since a face is planar. It shows the same side to the camera all across its surface. So calculate

if(camera.perspective?)
  test_vect = point_on_face - camera.eye
else
  test_vect = camera.direction
end

and use test_vect instead of camera.direction in the logic above.

1 Like

I was aware of the problem.
But for my purposes I believe the simple < or > 90 degrees test will suffice since the plane of the face will never be behind the camera.
If I used your method why wouldn’t I drop just drop a perpendicular from the eyepoint to the face? using intersect_line_plane.

Let’s see if I follow, as you have left out a bunch of details…

Presumably you mean to create the line and plane as

line = [camera.eye, face.normal]
plane = face.plane

(since otherwise I don’t know what line and plane you are talking about). But then

point=Geom::intersect_line_plane

returns a Point3d on the face’s plane where a ray line through the camera eye passes perpendicular through the plane. Would you then use that point for the subtraction I proposed? If so, please note that this point is on the face’s plane but is not necessarily within the face and could actually be behind the camera or otherwise outside the viewable area (technically, the camera’s frustum). If that’s not the idea, what would you do next?

Also, note that because the line was constructed using the face’s normal, the vector between the intersection point and the camera eye is necessarily either in the same or the opposite direction as the normal.

why not just use the face.bounds methods to find the new spot?

john

1 Like

Good question, John. For a Face,

face.bounds.center

returns a Point3d at the geometric center of the Face (since a Face is 2D, so it its bounding box. I quibbled about “geometric center” because depending on the shape of the Face the bounds center might fall in a hole). Even so, you could use that for point_on_face in the snippet I gave previously.

I was was thinking more about the corners…

# if the user has selected the face...
target_face = Sketchup.active_model.selection[0]
for i in 0..7 do
result = target_face.bounds.corner(i)
puts "(left front bottom)" if i == 0
puts" (right front bottom)" if i == 1
puts "(left back bottom)" if i == 2
puts "(right back bottom)" if i == 3
puts "(left front top)" if i == 4
puts "(right front top)" if i == 5
puts "(left back top)" if i == 6
puts "(right back top)" if i == 7
p result
end
=begin
returns....
(left front bottom)
Point3d(122.244, 0, 0)
 (right front bottom)
Point3d(122.244, 0, 0)
(left back bottom)
Point3d(122.244, 163.78, 0)
(right back bottom)
Point3d(122.244, 163.78, 0)
(left front top)
Point3d(122.244, 0, 133.943)
(right front top)
Point3d(122.244, 0, 133.943)
(left back top)
Point3d(122.244, 163.78, 133.943)
(right back top)
Point3d(122.244, 163.78, 133.943)
0..7
=end

you could right or left justify using that info…
john