What are the necessary maths to achieve the camera panning effect that\'s used in 3ds max?
In 3ds max the distance between the cursor and the mesh will always remain
[...] but there's still one case where his algorithm won't work properly. It doesn't handle properly the case where you panning is started from empty space [...]
In the solution the depth of the object is taken from the depth buffer, at that position, where the mouse click occurs. If this is the "empty space", a position where no object was drawn, the depth is the maximum of the depth range (in common 1). This leads to a rapid paining.
A solution or workaround would be use the depth of an representative position of the scene. e.g. the origin of the world:
pt_drag = glm.vec3(0, 0, 0)
Of course this may not lead to a proper result in each case. If the objects of the scene are not around the origin of the world, this approach will fail. I recommend to calculate the center of the axis aligned bounding box of the scene. Use this point for the representative "depth":
box_min = ... # glm.vec3
box_max = ... # glm.vec3
pt_drag = (box_min + box_max) / 2
The depth of a point can computed by the transformation with the view and projection matrix and a final perspective divide:
o_clip = self.proj * self.view * glm.vec4(pt_drag, 1)
o_ndc = glm.vec3(o_clip) / o_clip.w
This can be applied to the function glut_mouse
:
def glut_mouse(self, button, state, x, y):
self.drag = state == GLUT_DOWN
self.last_mouse_pos = glm.vec2(x, self.height-y)
self.mouse_down_pos = glm.vec2(x, self.height-y)
if self.drag:
depth_buffer = glReadPixels(x, self.height-y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)
self.last_depth = depth_buffer[0][0]
if self.last_depth == 1:
pt_drag = glm.vec3(0, 0, 0)
o_clip = self.proj * self.view * glm.vec4(pt_drag, 1)
o_ndc = glm.vec3(o_clip) / o_clip.w
if o_ndc.z > -1 and o_ndc.z < 1:
self.last_depth = o_ndc.z * 0.5 + 0.5
Preview:
The key to a well feeling solution is to find the "correct" depth. At perspective projection the dragging, where the mouse movement effects the object in a 1:1 motion, projected on the viewport, only works correctly for a well defined depth. Objects with different depths are displaced by a different scale when they projected on the viewport, that's the "nature" of perspective.
To find the "correct" depth, there are different possibilities, which depend on your needs:
depth_buffer = glReadPixels(x, self.height-y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)
self.last_depth = depth_buffer[0][0]
d_buf = glReadPixels(0, 0, self.width, self.height, GL_DEPTH_COMPONENT, GL_FLOAT)
d_vals = [float(d_buf[i][j]) for i in range(self.width) for j in range(self.height) if d_buf[i][j] != 1]
if len(d_vals) > 0:
self.last_depth = (min(d_vals) + max(d_vals)) / 2
pt_drag = glm.vec3(0, 0, 0)
o_clip = self.proj * self.view * glm.vec4(pt_drag, 1)
o_ndc = glm.vec3(o_clip) / o_clip.w
if o_ndc.z > -1 and o_ndc.z < 1:
self.last_depth = o_ndc.z * 0.5 + 0.5
Calculating the center of the bounding box of the scene.
Implement a raycasting, which identifies an object by a ray, which starts at the point of view a runs trough the cursor (mouse) position. This algorithm can be advanced by identifying the object which is "closest" to the ray, when no object is hit.