Converting 3D Points to Screen Space X&Y

On 28/07/2018 at 16:27, xxxxxxxx wrote:

Hello,
I'm trying to get the X&Y pixel coordinates of my polygon plane's vertices (as well as their depth). The output dimensions are 4096x2160 pixels (attached). These are the plane's 3D vectors followed by the pixel coordinates I'm looking to get:

# Top Left Corner: (-200,200,0) -> 1493,505
# Top Right Corner: (200,200,0) -> 2458,471
# Bottom Left Corner: (-200,-200,0) -> 1524,1712
# Bottom Right Corner: (200,-200,0) -> 2438,1509

How can I do this in Python? I've tried to use BaseDraw's WC, WC_V, WS, CS methods, but I'm not sure what to do with their results as they are not in pixels.

WC: Vector(-174.873, 181.224, 1288.966) # Top Left Corner
WC then CS: Vector(472.901, 423.506, 1288.966) # Top Left Corner
WS: Vector(472.901, 423.506, 1288.966) # Top Left Corner
WC_V: Vector(-163.448, 174.118, -151.55) # Top Left Corner
CS: Vector(-25959351, -25959394, 0),Vector(25960649, -25959394, 0),Vector(25960649, 25960606, 0),Vector(-25959351, 25960606, 0) # All four vertices

Thank you.

On 30/07/2018 at 08:32, xxxxxxxx wrote:

Hi blastframe, welcome to our community and thanks for writing here.

With regard to your question, there are a couple of point missing:

  1. what's the context? Rendering or Viewport?
  2. The output dimensions you're reporting is the size of the rendered image or is it the size of the viewport?

As a rule of thumb, in case you're evaluating the viewport in a rendering context it's recommended to use BaseDocument::GetRenderBaseDraw() whilst if in viewport feel free to use BaseDocument::GetActiveBaseDraw().

Aside fro this also note that the origin of the screen space is the top/left corner - where the "Perspective" label is - and ends on the bottom/right  - where the "Grid spacing" label is.

A brief test in viewport provides the correct results with this code

def main() :  
  if not doc or not op:  
      return  
    
  activeBD = doc.GetActiveBaseDraw()  
  if not activeBD:  
      return  
    
  points =  op.GetAllPoints()  
  if not points:  
      return  
    
  for point in points:  
      print "point: [",point,"] / [", activeBD.WS(point),"]"  
    
if __name__=='__main__':  
  main()  

Hoping it helps, give best.

On 02/08/2018 at 08:41, xxxxxxxx wrote:

Hi Riccardo,
Thank you again for the reply. To answer your questions:

  1. the context is Rendering.
  2. The output dimensions are the size of the rendered image

I used the code you provided with GetRenderBaseDraw(). It still is giving me numbers that do not equate to the X & Y coordinates in pixels. For example, if the rendered image is 1920x1080 and I have a point at 0,200,-200, the pixel coordinates in the image are 657 x 144. The script's output is:

point: [ Vector(0, 200, -200) ] / [ Vector(443.885, 338.496, 880.435) ]

If I use the Camera to screen conversion method, I get closer, but the Y is a crazy value:

    for point in points:
        print "point: [",point,"] / [", activeBD.CS(point, True),"]"

Output:

point: [ Vector(0, 200, -200) ] / [ Vector(649, -25959394, -500000000) ]

Can you help me get the values that I'm seeking? Thanks again!

On 03/08/2018 at 06:23, xxxxxxxx wrote:

Hi Blastframe thanks for following up.

I've prepared the following script hoping it works nicely for you.

def main() :  
  # check for doc being valid  
  if not doc:  
      return  
    
  # retrieve the BaseDraw of the view set to "Use as Render View"  
  renderBD = doc.GetRenderBaseDraw()  
  if not renderBD:  
      return  
    
  # retrieve the safe-frame information  
  renderSafeFrame = renderBD.GetSafeFrame()  
  renderSafeFrameRes = (renderSafeFrame['cr'] - renderSafeFrame['cl'], renderSafeFrame['cb'] - renderSafeFrame['ct'])  
    
    
  # retrieve active RenderData  
  rData = doc.GetActiveRenderData()  
  if not rData:  
      return  
    
  # get render data  
  rDataBC = rData.GetDataInstance()  
  if not rDataBC:  
      return   
    
  # store frame resolution  
  frameRes = (rDataBC.GetInt32(c4d.RDATA_XRES), rDataBC.GetInt32(c4d.RDATA_YRES))  
  
    
  # check there's a valid active object  
  if not op or not op.IsInstanceOf(c4d.Opolygon) :  
      return      
    
  # get the points or return instance it's not valid  
  points =  op.GetAllPoints()  
  if not points:  
      return  
    
  # loop through points  
  for point in points:  
      # transform point from World to Screen  
      pointToScreen = renderBD.WS(point)  
      # notify about points outside the render frame  
      if pointToScreen.x > renderSafeFrame['cr'] or pointToScreen.x < renderSafeFrame['cl']:  
          print " -- x-coordinate out of frame area --- "  
      if pointToScreen.y > renderSafeFrame['cb'] or pointToScreen.y < renderSafeFrame['ct']:  
          print " -- y-coordinate out of frame area --- "  
      # compensate the x/y offset              
      pointsR_XY = (pointToScreen.x - renderSafeFrame['cl'], pointToScreen.y -renderSafeFrame['ct'])          
      # scale with regard of the final frame size  
      pointsR_XY_2 = (pointsR_XY[0] * frameRes[0] / renderSafeFrameRes[0], pointsR_XY[1] * frameRes[1] / renderSafeFrameRes[1])  
      # just print  
      print "point: [", point ,"] / [", pointsR_XY_2, "]"  

Let me know if something is unclear or wrong.
Cheers, Riccardo

On 03/08/2018 at 07:33, xxxxxxxx wrote:

WOW! You did it, Riccardo! Thank you very much 😄 I'm very impressed and grateful.

On 03/08/2018 at 07:57, xxxxxxxx wrote:

My apologies, I do have one follow-up question. I see the line with # notify about points outside the render frame

How could I tell if the point is being obscured by other geometry? Would this be

renderBD.TestPointZ(point)

?

When I use this with the plane highlighted in green, it returns False for two of the points which are visible.

point: [ Vector(0, 200, -200) ] / [ (576.2718396467545, 166.23981331428323) ]
Visible: False
  
point: [ Vector(0, -200, -200) ] / [ (589.7969294792699, 878.0441715337967) ]
Visible: False
  
point: [ Vector(0, 200, 200) ] / [ (1066.6762020149142, 205.85938605460527) ]
Visible: True
  
point: [ Vector(0, -200, 200) ] / [ (1063.7210058999553, 771.3600524945704) ]
Visible: True

On 06/08/2018 at 00:23, xxxxxxxx wrote:

Hi Blastframe, thanks for following up.

With regard to your last question, consider that the point being passed to the BaseView::TestPointZ() needs to be expressed in camera space and not in world space. Then I warmly suggest to use BaseView::WC() before passing your point to the TestPointZ function.

Best, Riccardo

On 06/08/2018 at 12:09, xxxxxxxx wrote:

Originally posted by xxxxxxxx

Hi Blastframe, thanks for following up.

With regard to your last question, consider that the point being passed to the BaseView::TestPointZ()needs to be expressed in camera space and not in world space. Then I warmly suggest to use BaseView::WC() before passing your point to the TestPointZ function.

Best, Riccardo

Hi Riccardo,
Thank you for all of your help with my issue. Per your advice, I tried converting to camera space, but the results for all points were still true, even if they were blocked by self geometry or another object.

        pointToCS = renderBD.WC(point)
        print renderBD.TestPointZ(pointToCS)

Maybe this is not what TestPointZ is checking? I am wanting to skip vertices that are not visible. Thank you.

On 07/08/2018 at 03:05, xxxxxxxx wrote:

Hi Blastframe, thanks for following up.

With regard to your last post, I need to apologize cause I provided a misleading information being myself confused by the documentation. Actually the BaseView::TestPointZ() verify if a given point is within the camera near/far clipping range and not if the point is occluded by any other object.

In your case instead, you should think about a more complex design where each of your point is tested against any other geometry found in the scene by using a GeRayCollider() and then evaluate the result according to the intersection distance.

Unfortunately there's no other turn-key solution to achieve your desired functionality.

Best,  Riccardo