class Raycaster(Entity): def __init__(self): super().__init__(name='raycaster', eternal=True) self._picker = CollisionTraverser() # Make a traverser self._pq = CollisionHandlerQueue() # Make a handler self._pickerNode = CollisionNode('raycaster') self._pickerNode.set_into_collide_mask(0) self._pickerNP = self.attach_new_node(self._pickerNode) self._picker.addCollider(self._pickerNP, self._pq) self._pickerNP.show() def distance(self, a, b): return math.sqrt(sum((a - b)**2 for a, b in zip(a, b))) def raycast(self, origin, direction=(0, 0, 1), distance=math.inf, traverse_target=scene, ignore=list(), debug=False): self.position = origin self.look_at(self.position + direction) self._pickerNode.clearSolids() # if thickness == (0,0): if distance == math.inf: ray = CollisionRay() ray.setOrigin(Vec3(0, 0, 0)) ray.setDirection(Vec3(0, 1, 0)) else: ray = CollisionSegment(Vec3(0, 0, 0), Vec3(0, distance, 0)) self._pickerNode.addSolid(ray) if debug: self._pickerNP.show() else: self._pickerNP.hide() self._picker.traverse(traverse_target) if self._pq.get_num_entries() == 0: self.hit = Hit(hit=False) return self.hit ignore += tuple([e for e in scene.entities if not e.collision]) self._pq.sort_entries() self.entries = [ # filter out ignored entities e for e in self._pq.getEntries() if e.get_into_node_path().parent not in ignore ] if len(self.entries) == 0: self.hit = Hit(hit=False) return self.hit self.collision = self.entries[0] nP = self.collision.get_into_node_path().parent point = self.collision.get_surface_point(nP) point = Vec3(point[0], point[2], point[1]) world_point = self.collision.get_surface_point(render) world_point = Vec3(world_point[0], world_point[2], world_point[1]) hit_dist = self.distance(self.world_position, world_point) if nP.name.endswith('.egg'): nP = nP.parent self.hit = Hit(hit=True) for e in scene.entities: if e == nP: # print('cast nP to Entity') self.hit.entity = e self.hit.point = point self.hit.world_point = world_point self.hit.distance = hit_dist normal = self.collision.get_surface_normal( self.collision.get_into_node_path().parent) self.hit.normal = (normal[0], normal[2], normal[1]) normal = self.collision.get_surface_normal(render) self.hit.world_normal = (normal[0], normal[2], normal[1]) return self.hit self.hit = Hit(hit=False) return self.hit def boxcast(self, origin, direction=(0, 0, 1), distance=math.inf, thickness=(1, 1), traverse_target=scene, ignore=list(), debug=False): if isinstance(thickness, (int, float, complex)): thickness = (thickness, thickness) resolution = 3 rays = list() debugs = list() for y in range(3): for x in range(3): pos = origin + Vec3( lerp(-(thickness[0] / 2), thickness[0] / 2, x / (3 - 1)), lerp(-(thickness[1] / 2), thickness[1] / 2, y / (3 - 1)), 0) ray = self.raycast(pos, direction, distance, traverse_target, ignore, False) rays.append(ray) if debug and ray.hit: d = Entity(model='cube', origin_z=-.5, position=pos, scale=(.02, .02, distance), ignore=True) d.look_at(pos + Vec3(direction)) debugs.append(d) # print(pos, hit.point) if ray.hit and ray.distance > 0: d.scale_z = ray.distance d.color = color.green from ursina import destroy # [destroy(e, 1/60) for e in debugs] rays.sort(key=lambda x: x.distance) closest = rays[0] return Hit( hit=sum([int(e.hit) for e in rays]) > 0, entity=closest.entity, point=closest.point, world_point=closest.world_point, distance=closest.distance, normal=closest.normal, world_normal=closest.world_normal, hits=[e.hit for e in rays], entities=list(set([e.entity for e in rays])), # get unique entities hit )
class Raycaster(Entity): line_model = Mesh(vertices=[Vec3(0, 0, 0), Vec3(0, 0, 1)], mode='line') _boxcast_box = Entity(model='cube', origin_z=-.5, collider='box', color=color.white33, enabled=False) def __init__(self): super().__init__(name='raycaster', eternal=True) self._picker = CollisionTraverser() # Make a traverser self._pq = CollisionHandlerQueue() # Make a handler self._pickerNode = CollisionNode('raycaster') self._pickerNode.set_into_collide_mask(0) self._pickerNP = self.attach_new_node(self._pickerNode) self._picker.addCollider(self._pickerNP, self._pq) def distance(self, a, b): return sqrt(sum((a - b)**2 for a, b in zip(a, b))) def raycast(self, origin, direction=(0, 0, 1), distance=inf, traverse_target=scene, ignore=list(), debug=False): self.position = origin self.look_at(self.position + direction) self._pickerNode.clearSolids() ray = CollisionRay() ray.setOrigin(Vec3(0, 0, 0)) ray.setDirection(Vec3(0, 0, 1)) self._pickerNode.addSolid(ray) if debug: temp = Entity(position=origin, model=Raycaster.line_model, scale=Vec3(1, 1, min(distance, 9999)), add_to_scene_entities=False) temp.look_at(self.position + direction) destroy(temp, 1 / 30) self._picker.traverse(traverse_target) if self._pq.get_num_entries() == 0: self.hit = HitInfo(hit=False, distance=distance) return self.hit ignore += tuple([e for e in scene.entities if not e.collision]) self._pq.sort_entries() self.entries = [ # filter out ignored entities e for e in self._pq.getEntries() if e.get_into_node_path().parent not in ignore and self.distance( self.world_position, Vec3( *e.get_surface_point(render))) <= distance ] if len(self.entries) == 0: self.hit = HitInfo(hit=False, distance=distance) return self.hit self.collision = self.entries[0] nP = self.collision.get_into_node_path().parent point = Vec3(*self.collision.get_surface_point(nP)) world_point = Vec3(*self.collision.get_surface_point(render)) hit_dist = self.distance(self.world_position, world_point) self.hit = HitInfo(hit=True, distance=distance) for e in scene.entities: if e == nP: self.hit.entity = e nPs = [e.get_into_node_path().parent for e in self.entries] self.hit.entities = [e for e in scene.entities if e in nPs] self.hit.point = point self.hit.world_point = world_point self.hit.distance = hit_dist self.hit.normal = Vec3(*self.collision.get_surface_normal( self.collision.get_into_node_path().parent).normalized()) self.hit.world_normal = Vec3( *self.collision.get_surface_normal(render).normalized()) return self.hit self.hit = HitInfo(hit=False, distance=distance) return self.hit def boxcast(self, origin, direction=(0, 0, 1), distance=9999, thickness=(1, 1), traverse_target=scene, ignore=list(), debug=False): # similar to raycast, but with width and height if isinstance(thickness, (int, float, complex)): thickness = (thickness, thickness) Raycaster._boxcast_box.enabled = True Raycaster._boxcast_box.collision = True Raycaster._boxcast_box.position = origin Raycaster._boxcast_box.scale = Vec3(abs(thickness[0]), abs(thickness[1]), abs(distance)) Raycaster._boxcast_box.always_on_top = debug Raycaster._boxcast_box.visible = debug Raycaster._boxcast_box.look_at(origin + direction) hit_info = Raycaster._boxcast_box.intersects( traverse_target=traverse_target, ignore=ignore) if hit_info.world_point: hit_info.distance = ursinamath.distance(origin, hit_info.world_point) else: hit_info.distance = distance if debug: Raycaster._boxcast_box.collision = False Raycaster._boxcast_box.scale_z = hit_info.distance invoke(setattr, Raycaster._boxcast_box, 'enabled', False, delay=.2) else: Raycaster._boxcast_box.enabled = False return hit_info
class Raycaster(Entity): def __init__(self): super().__init__(name='raycaster', eternal=True) self._picker = CollisionTraverser() # Make a traverser self._pq = CollisionHandlerQueue() # Make a handler self._pickerNode = CollisionNode('raycaster') self._pickerNode.set_into_collide_mask(0) self._pickerNP = self.attach_new_node(self._pickerNode) self._picker.addCollider(self._pickerNP, self._pq) self._pickerNP.show() def distance(self, a, b): return sqrt(sum((a - b)**2 for a, b in zip(a, b))) def raycast(self, origin, direction=(0, 0, 1), distance=inf, traverse_target=scene, ignore=list(), debug=False): self.position = origin self.look_at(self.position + direction) self._pickerNode.clearSolids() # if thickness == (0,0): if distance == inf: ray = CollisionRay() ray.setOrigin(Vec3(0, 0, 0)) # ray.setDirection(Vec3(0,1,0)) ray.setDirection(Vec3(0, 0, 1)) else: # ray = CollisionSegment(Vec3(0,0,0), Vec3(0,distance,0)) ray = CollisionSegment(Vec3(0, 0, 0), Vec3(0, 0, distance)) self._pickerNode.addSolid(ray) if debug: self._pickerNP.show() else: self._pickerNP.hide() self._picker.traverse(traverse_target) if self._pq.get_num_entries() == 0: self.hit = HitInfo(hit=False) return self.hit ignore += tuple([e for e in scene.entities if not e.collision]) self._pq.sort_entries() self.entries = [ # filter out ignored entities e for e in self._pq.getEntries() if e.get_into_node_path().parent not in ignore ] if len(self.entries) == 0: self.hit = HitInfo(hit=False) return self.hit self.collision = self.entries[0] nP = self.collision.get_into_node_path().parent point = self.collision.get_surface_point(nP) # point = Vec3(point[0], point[2], point[1]) point = Vec3(point[0], point[1], point[2]) world_point = self.collision.get_surface_point(render) # world_point = Vec3(world_point[0], world_point[2], world_point[1]) world_point = Vec3(world_point[0], world_point[1], world_point[2]) hit_dist = self.distance(self.world_position, world_point) if nP.name.endswith('.egg'): nP = nP.parent self.hit = HitInfo(hit=True) for e in scene.entities: if e == nP: # print('cast nP to Entity') self.hit.entity = e self.hit.point = point self.hit.world_point = world_point self.hit.distance = hit_dist self.hit.normal = Vec3(*self.collision.get_surface_normal( self.collision.get_into_node_path().parent).normalized()) self.hit.world_normal = Vec3( *self.collision.get_surface_normal(render).normalized()) return self.hit self.hit = HitInfo(hit=False) return self.hit def boxcast(self, origin, direction=(0, 0, 1), distance=9999, thickness=(1, 1), traverse_target=scene, ignore=list(), debug=False): if isinstance(thickness, (int, float, complex)): thickness = (thickness, thickness) temp = Entity(position=origin, model='cube', origin_z=-.5, scale=Vec3(abs(thickness[0]), abs(thickness[1]), abs(distance)), collider='box', color=color.white33, always_on_top=debug, visible=debug) temp.look_at(origin + direction) hit_info = temp.intersects(traverse_target=traverse_target, ignore=ignore) if debug: temp.collision = False destroy(temp, delay=.1) else: destroy(temp) return hit_info