Hello coders,
since this is a question about algorithms I figured I'd post this here. Also this is mostly me hoping that someone will drop some wisdom on me in terms of 3D programming.
Problem
So I got two different positions in 3D space, Start
and Target
. When I look at them in the C4D viewport I need to calculate a new point which is between the two points, but 30 pixels before the target.
Take a look at this example where the distance in pixels on the screen is 298, so I need a new point which is 268 pixels away from Start
(298px - 30px), blue X marks the desired spot.
![af599930-9005-43c7-a0e6-19532e6b5b02-image.png](https://developers.maxon.net/forum/assets/uploads/files/1704571245308-af599930-9005-43c7-a0e6-19532e6b5b02-image.png)
However this point must exist in world space.
So my thought process was this:
- Get screen coordinates of both points using
BaseView.WS
.
- Calculate how far away the new point is from
Start
in percent relative to the screen distance to Target
.
- Create a new point in screen space using this calculated ratio by doing
Start + Direction * Distance * Ratio
.
- Transform to world coordinates using
BaseView.SW
.
The new point will be in the correct screen coordinates, so x and y are fine. However z is messed up which I can only assume is because of the camera perspective.
![1ed14989-5288-47c1-8dd0-f41c3f3ab471-image.png](https://developers.maxon.net/forum/assets/uploads/files/1704571920406-1ed14989-5288-47c1-8dd0-f41c3f3ab471-image.png)
Note in the following demo how both the target and the new point are always the same distance apart from each other in Perspective View while the z coordinate in Top View prevents the point from being on the desired pink line between the two points:
![52d7d72d-d3aa-4632-8644-ddff9f0bba7f-Cinema_4D_VliCT7fk6O.gif](https://developers.maxon.net/forum/assets/uploads/files/1704572423775-52d7d72d-d3aa-4632-8644-ddff9f0bba7f-cinema_4d_vlict7fk6o.gif)
This gets more apparent when moving the target closer to the camera.
Using a parallel camera (instead of perspective) of course does not have this issue.
![778887e1-a768-47c1-8b5b-e29779e56232-Cinema_4D_RryWEthfdm.gif](https://developers.maxon.net/forum/assets/uploads/files/1704573056568-778887e1-a768-47c1-8b5b-e29779e56232-cinema_4d_rrywethfdm.gif)
See how it's always nicely on the pink line?
And that's where I'm stuck. How do I fix this? Can anyone give me a hint?
Here's my code and a demo scene.
import c4d
import math
doc: c4d.documents.BaseDocument # The document the object `op` is contained in.
op: c4d.BaseObject # The Python generator object holding this code.
hh: "PyCapsule" # A HierarchyHelp object, only defined when main is executed.
def main() -> c4d.BaseObject:
bd = doc.GetRenderBaseDraw()
if bd is None:
return c4d.BaseObject(c4d.Onull)
# Note for variable names postix:
# > _w = World
# > _s = Screen
# > _s_flat = Screen without z coordinate
# World positions of start and target.
start_pos_w = doc.SearchObject('Start').GetAbsPos()
target_pos_w = doc.SearchObject('Target').GetAbsPos()
# The distance in pixels the new point should be away from point 2.
distance_from_target = 30.0
# Calculate the screen coordinates of the points.
start_pos_s = bd.WS(start_pos_w)
target_pos_s = bd.WS(target_pos_w)
# Calling WS creates a z coordinate to describe the distance of the
# point to the camera. To correctly calculate the 2D distance the
# z axis must be ignored.
start_pos_s_flat = c4d.Vector(start_pos_s.x, start_pos_s.y, 0)
target_pos_s_flat = c4d.Vector(target_pos_s.x, target_pos_s.y, 0)
# Get the direction and distance of both points in flat screen space.
direction_s_flat = (target_pos_s_flat - start_pos_s_flat).GetNormalized()
length_s_flat = (target_pos_s_flat - start_pos_s_flat).GetLength()
# Calculate the position of the new point in screen space with respect
# to the z coordinate:
# 1. Calculate the ratio how far away the new point is from the
# target in percent.
# 1.a Subtract the distance_from_target from the flat length.
length_s_flat_delta = length_s_flat - distance_from_target
# 1.b Divide delta by the flat length to get the ratio.
ratio = length_s_flat_delta / length_s_flat
# 2. Calculate the new point in "deep" screen space.
# 2.a Get direction and length of the screen space positions.
direction_s = (target_pos_s - start_pos_s).GetNormalized()
length_s = (target_pos_s - start_pos_s).GetLength()
# 2.b Use the ratio to calculate the new point in "deep" screen space.
new_point_s = start_pos_s + (direction_s * length_s * ratio)
# Transform the new point to world coordinates.
new_point_w = bd.SW(new_point_s)
# Create some output to visualize the result.
points = [start_pos_w, new_point_w, target_pos_w]
poly_obj = c4d.BaseObject(c4d.Opolygon)
poly_obj.ResizeObject(len(points), 0)
for i, point in enumerate(points):
poly_obj.SetPoint(i, point)
return poly_obj
Link to demo scene (2024.2.0) on my OneDrive:
https://1drv.ms/u/s!At78FKXjEGEomLMOaoZcJaA2TSwblg?e=8wfev1
Cheers,
Daniel