Converting 3D Points to Screen Space X&Y

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

    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:  
      activeBD = doc.GetActiveBaseDraw()  
      if not activeBD:  
      points =  op.GetAllPoints()  
      if not points:  
      for point in points:  
          print "point: [",point,"] / [", activeBD.WS(point),"]"  
    if __name__=='__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),"]"


    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:  
      # retrieve the BaseDraw of the view set to "Use as Render View"  
      renderBD = doc.GetRenderBaseDraw()  
      if not renderBD:  
      # 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:  
      # get render data  
      rDataBC = rData.GetDataInstance()  
      if not rDataBC:  
      # 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) :  
      # get the points or return instance it's not valid  
      points =  op.GetAllPoints()  
      if not points:  
      # 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



    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

Log in to reply