Context: I have a model that has multiple faces parallel to (but below) the XY plane (constant negative z-values). The faces may have materials. Similarly, the face edges may have (different) materials as well. All coplanar faces are grouped (one group for each z-value). Next, I also have edges without faces connected. These edges may be connected as curves, and each curve (or set of connected edges) will also be in a plane parallel to the XY-plane. Each curve or set of co-planar edges is also grouped. All these groups (either faces with edges, or just ‘edges’) may be further grouped into other groups (with a multilevel group structure). Ultimately, they all roll-up to a single top level group. At initial creation time, each of these groups may have transformations applied.
Problem: For a given point in the Z=0 plane (x, y, 0), I need to know the material (if any) of the point with the same x and y coordinates of the face (or face edge) with the highest value of z (thus the least negative value).
My solution so far: I think the problem could/should be solved with a combination of the raytest and classify_point methods in the Ruby API. So, I cast a ray downwards from the origin at (x, y, 0), and take the point hit by the ray to derive the material at that particular x/y coordinate. This seems to work ok most of the time (when the hit point is either inside a face or when the hitpoint is on a curve/edge set without faces). However, there seems to be ‘random’ behavior if the hitpoint is on the edge of a group that contains a face. In this case, it doesn’t seem to follow a systematic pattern whether the path of the raytest (i.e. the second return value - array - of the model.raytest) returns the face or the edge. Sometimes, it seems to return the face, sometimes it seems to return the edge… In such cases, I need to know the material of the edge (not the face). ..
I try to handle this (if a face is returned) with the face.classify_point method. This method returns whether the hitpoint is ‘inside’ the face, at the ‘edge’ of a face, at a vertex of a face, or outside the face… So, for my requirement, this should allow to retrieve the edge (if the hitpoint is on the edge, but the raytest has returned the face entity).
My problem is that this doesn’t seem to work. The face.classify method systematically returns that the hitpoint is outside the face (return value 32). This is despite the fact that the raytest has found the hitpoint as the intersection of the ray with the face… From online research on this forum, I understand that there is a difference in coordinate systems used by the raytest and classify_point methods. The solution should be to apply an inverse transformation of the group to the hitpoint. I’ve done that, but the issue remains (classify_point keeps returning 32…). One thing that’s not clear for me is: which group (of the multilevel group hierarchy returned by raytest) should be taken for the transformation inverse. I tried with the different levels of the hierarchy, but it seems to make no difference…
Can somebody with more experience than me have a look at this, and let me know what I’m doing wrong? Especially, the ‘inverse transformation’ step is something that I’m not very sure about…
My (simplified version of the) code is shown below:
pnt_data.each do |e, data| # Run the downward raytest for point
ray_origin = data[:point].offset(Geom::Vector3d.new(0, 0, -1), Z)# Apply small offset in z-direction to point (avoid self-hitting or hitting the canvas)
while true
ray = [ray_origin, Geom::Vector3d.new(0, 0, -1)] # Ray points downwards in Z direction
hit = model.raytest(ray, true) # Hidden geometry is not considered
if hit
hit_point = hit[0] # The hit_point is the intersection of the ray with the faces or (curve)/edges
hit_entity = hit[1].last
ray_origin = hit_point.offset(Geom::Vector3d.new(0, 0, -1), Z) # Re-initialize the ray_origin to a point just below the current hit_point to search for the next hit
hit_mtl = hit_entity.material
alpha = hit_mtl.nil? ? 1.0 : hit_mtl.alpha # Determine the alpha value of the hit entity material
if hit_entity.is_a?(Sketchup::Face) # If the hit_entity is a face, 2 scenario's are in principle possible: either the edge is a face boundary or not (i.e. inside). Due to design vertices are not possible...
tr = hit[1][-2].transformation
check_pnt = hit_point.transform (tr.inverse)
pnt_face = hit_entity.classify_point(check_pnt)
case pnt_face
when Sketchup::Face::PointInside # The hit_point is strictly inside the face area
when Sketchup::Face::PointOnEdge # The hit_point is on one of the face edges
hit_entity = hit_entity.edges.first # Replace the 'face' as hit_entity by the 'first face edge' as hit entity
hit_mtl = hit_entity.material # Replace the hit_mtl with the edge material! First edge: all face edges have identical materials
alpha = hit_mtl.nil? ? 1.0 : hit_mtl.alpha # Determine the alpha value of the edge material
else # Should normally never happen, but happens all the time....
break
end
else # The ray hit is with an edge in ref_obj. This branch is valid for lineart edges
end
else # No furter hits are found: break
break # Quit the while loop --> process next point
end
end
end