Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Controllable camera movement with touchpad contolls #7157

Open
3 tasks done
Sevdat opened this issue Feb 3, 2025 · 0 comments
Open
3 tasks done

Added Controllable camera movement with touchpad contolls #7157

Sevdat opened this issue Feb 3, 2025 · 0 comments

Comments

@Sevdat
Copy link

Sevdat commented Feb 3, 2025

Checklist

Proposed new feature or change

Good evening,

I wrote this code (with the help of deepseek) to create a controllable camera with touchpad. The local camera rotation has an issue where if you look down the controls reverse or start stuttering which we fixed by adding a limit. You can add this to documentations to help beginners. If you put the mouse somewhere on the screen it will start rotating towards that direction. If you use scroll up (swiping with two fingers on the touchpad) camera moves forward or scroll left and right for for sideways movement.

import open3d as o3d
import win32api
import win32gui
import win32con
import time
from typing import Optional, Tuple
import numpy as np
import numba

class Scene:
    def __init__(self, point_cloud: Optional[o3d.geometry.PointCloud] = None, points: Optional[int] = None, colors: Optional[int] = None):
        self.pcd: Optional[o3d.geometry.PointCloud] = None
        self.window: Optional[o3d.visualization.VisualizerWithKeyCallback] = None
        self.windowWidth: int = 800
        self.windowHeight: int = 600
        self.camera: Optional[Scene.Camera] = None

        if point_cloud is not None:
            self.pcd = point_cloud
        elif points is not None and colors is not None:
            self.createPointCloud(points, colors)

        self.createWindow()
        self.camera = Scene.Camera(self)

    def createPointCloud(self, points: int, colors: int) -> None:
        self.pcd = o3d.geometry.PointCloud()
        self.pcd.points = o3d.utility.Vector3dVector(np.random.rand(points, 3))
        self.pcd.colors = o3d.utility.Vector3dVector(np.random.rand(colors, 3))

    def createWindow(self) -> None:
        self.window = o3d.visualization.VisualizerWithKeyCallback()
        self.window.create_window(width=self.windowWidth, height=self.windowHeight)

        screenWidth = win32api.GetSystemMetrics(0)
        screenHeight = win32api.GetSystemMetrics(1)
        centerX = (screenWidth - self.windowWidth) // 2
        centerY = (screenHeight - self.windowHeight) // 2

        hwnd = win32gui.FindWindow(None, "Open3D")
        if hwnd:
            win32gui.SetWindowPos(hwnd, win32con.HWND_TOP, centerX, centerY, self.windowWidth, self.windowHeight, win32con.SWP_SHOWWINDOW)

        if self.pcd:
            self.window.add_geometry(self.pcd)

        self.windowHeight -= 48
        self.windowWidth -= 19

    class Camera:
        def __init__(self, scene: 'Scene'):
            self.scene = scene
            self.fps: float = 1 / 60
            self.sensitivity: float = self.fps * 2
            self.forward: float = 0
            self.right: float = 0
            self.up: float = 0
            self.mouseLookX: float = 0
            self.mouseLookY: float = 0
            self.mouseScrollX: float = 0
            self.mouseScrollY: float = 0
            self.ctr = self.scene.window.get_view_control()
            self.keyboard_controls = Scene.Camera.CameraKeyboardControls(self)
            self.touchpad_controls = Scene.Camera.CameraTouchPadControls(self)
            self.setRenderDistance(0.1, 1000)
            self.angleX = 0
            self.angleY = 0
            self.mouseYMoreThan180 = self.getCameraForwardVector()

        def cameraFront(self, vec:np.array) -> None:
            self.ctr.set_front(vec)

        def cameraLookat(self, vec:np.array) -> None:
            self.ctr.set_lookat(vec)

        def cameraSetUp(self, vec:np.array) -> None:
            self.ctr.set_up(vec)

        def cameraSetZoom(self, zoom: float) -> None:
            self.ctr.set_zoom(zoom)

        def setRenderDistance(self, near: float, far: float) -> None:
            self.ctr.set_constant_z_near(near)
            self.ctr.set_constant_z_far(far)

        def getOriginAndDirection(self) -> np.array:
            extrinsicMatrix = self.ctr.convert_to_pinhole_camera_parameters().extrinsic
            rotationMatrix = extrinsicMatrix[:3, :3]
            origin = extrinsicMatrix[:3, 3]
            rightDirection = origin+rotationMatrix[:, 0]
            upDirection = origin+rotationMatrix[:, 1]
            forwardDirection = origin-rotationMatrix[:, 2]
            return np.array([origin,rightDirection,upDirection,forwardDirection])
        
        def getCameraForwardVector(self,):
            extrinsicMatrix = self.ctr.convert_to_pinhole_camera_parameters().extrinsic
            forwardVector = extrinsicMatrix[2, :3]
            return forwardVector / np.linalg.norm(forwardVector)

        def getCameraPitch(self):
            extrinsicMatrix = self.ctr.convert_to_pinhole_camera_parameters().extrinsic
            rotationMatrix = extrinsicMatrix[:3, :3]
            pitch = np.arcsin(-rotationMatrix[2, 1])  # Simplified for this example
            return pitch
        class CameraKeyboardControls:
            def __init__(self, camera: 'Scene.Camera'):
                self.camera = camera
                key_actions = {
                    ord('W'): lambda vis: self.key_callback(vis, ord('W')),
                    ord('S'): lambda vis: self.key_callback(vis, ord('S')),
                    ord('A'): lambda vis: self.key_callback(vis, ord('A')),
                    ord('D'): lambda vis: self.key_callback(vis, ord('D')),
                    ord('E'): lambda vis: self.key_callback(vis, ord('E')),
                    ord('Q'): lambda vis: self.key_callback(vis, ord('Q')),
                }
                for key, action in key_actions.items():
                    self.camera.scene.window.register_key_callback(key, action)

            def key_callback(self, vis, key_code: int) -> bool:
                if key_code == ord('W'):

                    print(self.camera.getOriginAndDirection())
                elif key_code == ord('S'):
                    self.camera.forward = -3
                elif key_code == ord('D'):
                    self.camera.right = 3
                elif key_code == ord('A'):
                    self.camera.right = -3
                elif key_code == ord('E'):
                    self.camera.up = 3
                elif key_code == ord('Q'):
                    self.camera.up = -3

                self.camera.ctr.camera_local_translate(self.camera.forward, self.camera.right, self.camera.up)
                self.camera.right = 0
                self.camera.forward = 0
                self.camera.up = 0
                return False

        class CameraTouchPadControls:
            def __init__(self, camera: 'Scene.Camera'):
                self.camera = camera
                self.camera.scene.window.register_mouse_move_callback(self.on_mouse_move)
                self.camera.scene.window.register_mouse_scroll_callback(self.on_mouse_scroll)
            def on_mouse_move(self, obj, x: int, y: int) -> None:
                if x > self.camera.scene.windowWidth / 2:
                    self.camera.mouseLookX = (x-(self.camera.scene.windowWidth/2))*self.camera.sensitivity
                else:
                    self.camera.mouseLookX = -((self.camera.scene.windowWidth/2)-x)*self.camera.sensitivity

                if y > self.camera.scene.windowHeight / 2:
                    self.camera.mouseLookY = (y-(self.camera.scene.windowHeight/2))*self.camera.sensitivity 
                else:
                    self.camera.mouseLookY = -((self.camera.scene.windowHeight/2)-y)*self.camera.sensitivity 

            def on_mouse_scroll(self, obj, x: int, y: int) -> None:
                self.camera.mouseScrollX += x*self.camera.sensitivity/10
                self.camera.mouseScrollY -= y*self.camera.sensitivity/10

scene2 = Scene(o3d.geometry.PointCloud(o3d.io.read_point_cloud(o3d.data.PLYPointCloud().path)))
PITCH_LIMIT_MIN = np.radians(-60)  # -70 degrees
PITCH_LIMIT_MAX = np.radians(60)   # 70 degrees
while True:
    scene2.window.poll_events()
    scene2.window.update_renderer()
    current = scene2.camera.getCameraPitch()
    if current < PITCH_LIMIT_MIN and scene2.camera.mouseLookY<0: scene2.camera.mouseLookY = 0
    if current > PITCH_LIMIT_MAX  and scene2.camera.mouseLookY>0:  scene2.camera.mouseLookY = 0
    scene2.camera.ctr.camera_local_translate(scene2.camera.mouseScrollY, scene2.camera.mouseScrollX, 0)
    scene2.camera.ctr.camera_local_rotate(scene2.camera.mouseLookX,scene2.camera.mouseLookY)
    # print(scene2.camera.getCameraPitch()*180/np.pi)
    time.sleep(scene2.camera.fps)

References

No response

Additional information

No response

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant