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: # 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, distance=distance) 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 HitInfo( 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 )
def find_collision(self): self.collisions = list() self.collision = None if not self.raycast or self._pq.get_num_entries() == 0: self.unhover_everything_not_hit() return False self._pq.sortEntries() for entry in self._pq.getEntries(): for entity in scene.entities: if entry.getIntoNodePath( ).parent == entity and entity.collision: if entity.collision: hit = HitInfo( hit=entry.collided(), entity=entity, distance=distance(entry.getSurfacePoint(scene), camera.getPos()), point=entry.getSurfacePoint(entity), world_point=entry.getSurfacePoint(scene), normal=entry.getSurfaceNormal(entity), world_normal=entry.getSurfaceNormal(scene), ) self.collisions.append(hit) break if self.collisions: self.collision = self.collisions[0] self.hovered_entity = self.collision.entity if not self.hovered_entity.hovered: self.hovered_entity.hovered = True if hasattr(self.hovered_entity, 'on_mouse_enter'): self.hovered_entity.on_mouse_enter() for s in self.hovered_entity.scripts: if hasattr(s, 'on_mouse_enter'): s.on_mouse_enter() self.unhover_everything_not_hit()
def intersects(self, traverse_target=scene, ignore=(), debug=False): from ursina.hit_info import HitInfo if not self.collision or not self.collider: self.hit = HitInfo(hit=False) return self.hit from ursina import distance if not hasattr(self, '_picker'): from panda3d.core import CollisionTraverser, CollisionNode, CollisionHandlerQueue from panda3d.core import CollisionRay, CollisionSegment, CollisionBox 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() self._pickerNode.addSolid(self._collider.shape) 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 += (self, ) 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, distance=0) return self.hit collision = self.entries[0] nP = collision.get_into_node_path().parent point = collision.get_surface_point(nP) point = Vec3(*point) world_point = collision.get_surface_point(render) world_point = Vec3(*world_point) hit_dist = distance(self.world_position, world_point) self.hit = HitInfo(hit=True) self.hit.entity = next(e for e in scene.entities if e == nP) self.hit.point = point self.hit.world_point = world_point self.hit.distance = hit_dist normal = collision.get_surface_normal(collision.get_into_node_path().parent).normalized() self.hit.normal = Vec3(*normal) normal = collision.get_surface_normal(render).normalized() self.hit.world_normal = Vec3(*normal) self.hit.entities = [] for collision in self.entries: self.hit.entities.append(next(e for e in scene.entities if e == collision.get_into_node_path().parent)) return self.hit
def terraincast(origin, terrain): # mainly used for getting the y position on a terrain. returns a HitInfo like raycast() origin = Vec3(*origin) height_values = terrain.model.height_values width = terrain.model.width depth = terrain.model.depth height_multiplier = terrain.world_scale_y if terrain.world_rotation[0] != 0 or terrain.world_rotation[2] != 0: print("terraincaster does not work when the terrain is rotated to not face upwards") return None #stores x,z to reduce unecessary calculations later point_x = origin[0] point_z = origin[2] #transformations processed for origin to align with height_values angle = - radians(terrain.world_rotation_y) origin_vector = origin - terrain.world_position store = origin_vector[0] origin_vector[0] = origin_vector[0] * cos( - angle) - origin_vector[2] * sin( - angle) origin_vector[2] = store * sin( - angle) + origin_vector[2] * cos( - angle) origin = terrain.world_position + origin_vector origin = (origin - terrain.world_position + terrain.origin * terrain.world_scale) origin[0] = (origin[0] / terrain.world_scale_x + .5) * width origin[2] = (origin[2] / terrain.world_scale_z + .5) * width if origin[0] >=0 and origin[0] < len(height_values) and origin[2] >=0 and origin[2] < len(height_values[0]): if floor(origin[2]) == len(height_values[0]) - 1 and floor(origin[0]) == len(height_values) - 1: start = Vec3(floor(origin[0]),height_values[int(floor(origin[0]))][int(floor(origin[2]))],floor(origin[2])) normal = Vec3(0,1,0) else: if floor(origin[2]) == len(height_values[0]) - 1: start = Vec3(floor(origin[0]),height_values[int(floor(origin[0]))][int(floor(origin[2]))],floor(origin[2])) right = Vec3(floor(origin[0]),height_values[int(floor(origin[0]))][int(floor(origin[2]))],floor(origin[2] + 1)) left = Vec3(floor(origin[0] + 1),height_values[int(floor(origin[0]) + 1)][int(floor(origin[2]))],floor(origin[2] + 1)) elif floor(origin[0]) == len(height_values) - 1: start = Vec3(floor(origin[0]),height_values[int(floor(origin[0]))][int(floor(origin[2]))],floor(origin[2])) right = Vec3(floor(origin[0]),height_values[int(floor(origin[0]))][int(floor(origin[2]) + 1)],floor(origin[2]) + 1) left = Vec3(floor(origin[0] + 1),height_values[int(floor(origin[0]))][int(floor(origin[2]) + 1)],floor(origin[2] + 1)) #determines which triangle of the current square the player is standing on and gets vectors for each corner elif origin[0] % 1 < origin[2] % 1: start = Vec3(floor(origin[0]),height_values[int(floor(origin[0]))][int(floor(origin[2]))],floor(origin[2])) right = Vec3(floor(origin[0]),height_values[int(floor(origin[0]))][int(floor(origin[2]) + 1)],floor(origin[2]) + 1) left = Vec3(floor(origin[0] + 1),height_values[int(floor(origin[0]) + 1)][int(floor(origin[2]) + 1)],floor(origin[2] + 1)) else: start = Vec3(floor(origin[0]),height_values[int(floor(origin[0]))][int(floor(origin[2]))],floor(origin[2])) right = Vec3(floor(origin[0]) + 1,height_values[int(floor(origin[0]) + 1)][int(floor(origin[2]))],floor(origin[2])) left = Vec3(floor(origin[0] + 1),height_values[int(floor(origin[0]) + 1)][int(floor(origin[2]) + 1)],floor(origin[2] + 1)) #get normal to face and make sure it's facing up and is 1 unit in length normal = cross(left - start,right - start) if normal[1] <0: normal = - normal normal = normal / sqrt(normal[0] ** 2 + normal[1] ** 2 + normal[2] ** 2) hit = HitInfo(hit=True) #finds point where verticle line from origin and face plane intersect based on the calculated normal this uses the maths that is here for anyone interested https: / / en.wikipedia.org / wiki / Line - plane_intersection hit.world_point = Vec3(point_x, (dot(start,normal) - origin[0] * normal[0] - origin[2] * normal[2]) / normal[1] * terrain.world_scale_y + terrain.world_position[1] - terrain.origin[1] * terrain.world_scale_y, point_z ) #finishes up setting hit info object if hasattr(terrain.parent, 'position'): hit.point = hit.world_point - terrain.parent.world_position else: hit.point = hit.world_point #parented to scene or something so reverts to world coordinates hit.normal = Vec3(*normal) hit.world_normal = hit.normal hit.distance = origin[1] - hit.world_point[1] hit.entity = terrain hit.entities = [terrain, ] hit.hits = [True, ] return hit else: hit = HitInfo(hit=False, distance=inf) return hit
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