Skip to content

Commit

Permalink
raycast
Browse files Browse the repository at this point in the history
  • Loading branch information
strubium committed May 22, 2024
1 parent 9d7cfd1 commit c582843
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 12 deletions.
11 changes: 5 additions & 6 deletions QtPyHammer/ui/viewport.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,17 +130,17 @@ def resizeGL(self, width, height):

# Qt Signals
def do_raycast(self, click_x, click_y):
camera_right = vector.vec3(x=1).rotate(*-self.camera.rotation)
camera_up = vector.vec3(z=1).rotate(*-self.camera.rotation)
camera_forward = vector.vec3(y=1).rotate(*-self.camera.rotation)
camera_right = vector.vec3(x=1).rotate(1)
camera_up = vector.vec3(z=1).rotate(1)
camera_forward = vector.vec3(y=1).rotate(1)
width, height = self.width(), self.height()
x_offset = camera_right * ((click_x * 2 - width) / width)
x_offset *= width / height # aspect ratio
y_offset = camera_up * ((click_y * 2 - height) / height)
fov_scalar = math.tan(math.radians(self.render_manager.field_of_view / 2))
fov_scalar = math.tan(math.radians(90 / 2))
x_offset *= fov_scalar
y_offset *= fov_scalar
ray_origin = self.camera.position
ray_origin = camera.freecam.position
ray_direction = camera_forward + x_offset + y_offset
return ray_origin, ray_direction

Expand Down Expand Up @@ -178,7 +178,6 @@ def mouseReleaseEvent(self, event):
x = self.width() / 2
y = self.height() / 2
ray_origin, ray_direction = self.do_raycast(x, self.height() - y)
self.raycast.emit(ray_origin, ray_direction)
super(MapViewport3D, self).mouseReleaseEvent(event)

def wheelEvent(self, event):
Expand Down
25 changes: 19 additions & 6 deletions QtPyHammer/ui/workspace.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"""QtPyHammer Workspace that holds and manages an open .vmf file"""
import enum

from PyQt5 import QtWidgets

from PyQt5 import QtWidgets, QtCore
from PyQt5.QtGui import QMouseEvent
from . import viewport, popup
from ..utilities import raycast
from ..ops.vmf import VmfInterface


Expand Down Expand Up @@ -37,13 +37,26 @@ def __init__(self, vmf_path, new=True, parent=None):
# ^ define the VmfInterface last so it can connect to everything
# undo & redo is tied directly to the VmfInterface

def mousePressEvent(self, event: QMouseEvent):
"""Override the mouse press event to handle selection"""
if event.button() == QtCore.Qt.LeftButton:
# Convert the mouse position to normalized device coordinates
ndc_x = (2.0 * event.x()) / self.viewport.width() - 1.0
ndc_y = 1.0 - (2.0 * event.y()) / self.viewport.height()

# Use a raycasting method to find the selection
ray_origin, ray_direction = viewport.MapViewport3D.do_raycast(self, ndc_x, ndc_y)
self.select(ray_origin, ray_direction)

def select(self, ray_origin, ray_direction):
"""Get the object hit by ray"""
ray_length = self.viewport.render_manager.draw_distance
ray = raycast.Ray(ray_origin, ray_direction, ray_length)
selection = raycast.raycast(ray, self.map_file)
self.map_file.selection.add(selection)
# TODO: highlight selection in renderer
objects = self.map_file.get_objects() # Access objects from VmfInterface
selection = raycast(ray, objects)
if selection:
self.selection.add(selection)
# TODO: highlight selection in renderer

def save_to_file(self):
print(f"Saving {self.filename}... ", end="")
Expand Down
49 changes: 49 additions & 0 deletions QtPyHammer/utilities/raycast.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import math

class Ray:
def __init__(self, origin, direction, length):
self.origin = origin
self.direction = direction
self.length = length

def ray_aabb_intersection(ray, aabb_min, aabb_max):
tmin = (aabb_min[0] - ray.origin[0]) / ray.direction[0]
tmax = (aabb_max[0] - ray.origin[0]) / ray.direction[0]
if tmin > tmax:
tmin, tmax = tmax, tmin

tymin = (aabb_min[1] - ray.origin[1]) / ray.direction[1]
tymax = (aabb_max[1] - ray.origin[1]) / ray.direction[1]
if tymin > tymax:
tymin, tymax = tymax, tymin

if (tmin > tymax) or (tymin > tmax):
return False

if tymin > tmin:
tmin = tymin
if tymax < tmax:
tmax = tymax

tzmin = (aabb_min[2] - ray.origin[2]) / ray.direction[2]
tzmax = (aabb_max[2] - ray.origin[2]) / ray.direction[2]
if tzmin > tzmax:
tzmin, tzmax = tzmax, tzmin

if (tmin > tzmax) or (tzmin > tmax):
return False

if tzmin > tmin:
tmin = tzmin
if tzmax < tmax:
tmax = tzmax

return (tmin < ray.length) and (tmax > 0)

def raycast(ray, objects):
for obj in objects:
aabb_min = obj.bounding_box_min # Assuming these are lists
aabb_max = obj.bounding_box_max
if ray_aabb_intersection(ray, aabb_min, aabb_max):
return obj # Return the first object hit by the ray
return None

0 comments on commit c582843

Please sign in to comment.