def update(self, update_ctx): """Move and turn if min_x or max_x reached. Check for collision.""" start_pos = self.pos super().update(update_ctx) if self.pos.x > self.max_x: self.facing = Direction.LEFT self.set_x(self.max_x - (self.pos.x - self.max_x)) self.velocity = Vec(-self.speed, 0) elif self.pos.x < self.min_x: self.facing = Direction.RIGHT self.set_x(self.min_x + (self.min_x - self.pos.x)) self.velocity = Vec(self.speed, 0) wall_entities = [ entity for entity in update_ctx.world.entities if entity.blocks_movement and self.is_touching(entity) ] wall_players = [ player for player in update_ctx.game.get_players_by_world( update_ctx.world.get_world_id()) if self.is_touching(player) ] walls = wall_entities + wall_players if walls: walls.sort(key=lambda wall: wall.pos.dist_to(self.pos)) for wall in walls: block_movement(wall.get_bounding_box(), start_pos, self)
async def on_interact(self, event_ctx): """Send dialogue when player interacts with Walker.""" self.velocity = Vec(0, 0) if event_ctx.username in self.conv_progress: self.conv_progress[event_ctx.username] += 1 try: await Util.send_dialogue( event_ctx.ws, self.uuid, self.dialogue[self.conv_progress[event_ctx.username]]) event_ctx.player.talking_to = self except IndexError: del self.conv_progress[event_ctx.username] await Util.send_dialogue_end(event_ctx.ws, self.uuid) event_ctx.player.talking_to = None if not self.conv_progress: if self.facing is Direction.LEFT: self.velocity = Vec(-self.speed, 0) elif self.facing is Direction.RIGHT: self.velocity = Vec(self.speed, 0) else: self.conv_progress[event_ctx.username] = 0 await Util.send_dialogue( event_ctx.ws, self.uuid, self.dialogue[self.conv_progress[event_ctx.username]]) event_ctx.player.talking_to = self
def test_vec_point_multiplication(self): m = Transformation( m=[ [1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0], [9.0, 9.0, 8.0, 7.0], [0.0, 0.0, 0.0, 1.0], ], invm=[ [-3.75, 2.75, -1, 0], [5.75, -4.75, 2.0, 1.0], [-2.25, 2.25, -1.0, -2.0], [0.0, 0.0, 0.0, 1.0], ], ) assert m.is_consistent() expected_v = Vec(14.0, 38.0, 51.0) assert expected_v.is_close(m * Vec(1.0, 2.0, 3.0)) expected_p = Point(18.0, 46.0, 58.0) assert expected_p.is_close(m * Point(1.0, 2.0, 3.0)) expected_n = Normal(-8.75, 7.75, -3.0) assert expected_n.is_close(m * Normal(3.0, 2.0, 4.0))
def get_path_areas_mode(self, p_depart, p_arrive, area_depart=None): p_depart = Vec(p_depart) p_arrive = Vec(p_arrive) area_depart = self.find_area_for_point(p_depart) if not area_depart else area_depart area_arrive = self.find_area_for_point(p_arrive) # le point d'arrivé est inateignable if not area_arrive: return [],[],[] # aucune aire n'a été trouvée pour le point de départ, on va prendre la plus proche if not area_depart: area_depart = min(self.areas.values(), key=lambda a: (a.middle - p_depart).norm2()) #application du pathfinding areas, raw_path = self.pathfinding_area.compute_path(area_depart, area_arrive) # smooth path if len(areas) > 1: portal_edges = polys_to_portal_edges(areas) smooth_path = funnel(p_depart, p_arrive, portal_edges) else: raw_path = [p_depart, p_arrive] smooth_path = [p_depart, p_arrive] return areas,raw_path,smooth_path
def test_is_close(self): ray1 = Ray(origin=Point(1.0, 2.0, 3.0), dir=Vec(5.0, 4.0, -1.0)) ray2 = Ray(origin=Point(1.0, 2.0, 3.0), dir=Vec(5.0, 4.0, -1.0)) ray3 = Ray(origin=Point(5.0, 1.0, 4.0), dir=Vec(3.0, 9.0, 4.0)) assert ray1.is_close(ray2) assert not ray1.is_close(ray3)
def testFlatRenderer(self): sphere_color = Color(1.0, 2.0, 3.0) sphere = Sphere(transformation=translation(Vec(2, 0, 0)) * scaling(Vec(0.2, 0.2, 0.2)), material=Material(brdf=DiffuseBRDF( pigment=UniformPigment(sphere_color)))) image = HdrImage(width=3, height=3) camera = OrthogonalCamera() tracer = ImageTracer(image=image, camera=camera) world = World() world.add_shape(sphere) renderer = FlatRenderer(world=world) tracer.fire_all_rays(renderer) assert image.get_pixel(0, 0).is_close(BLACK) assert image.get_pixel(1, 0).is_close(BLACK) assert image.get_pixel(2, 0).is_close(BLACK) assert image.get_pixel(0, 1).is_close(BLACK) assert image.get_pixel(1, 1).is_close(sphere_color) assert image.get_pixel(2, 1).is_close(BLACK) assert image.get_pixel(0, 2).is_close(BLACK) assert image.get_pixel(1, 2).is_close(BLACK) assert image.get_pixel(2, 2).is_close(BLACK)
def block_movement(bbox, entity_start_pos, entity): """Reposition entity when the entity hits a wall. Args: bbox: The BoundingBox of the wall. entity_start_pos: The position of the entity before the entity moved and hit the wall. entity: The entity object. """ entity_end_pos = entity.pos entity.pos = entity_start_pos if entity.get_bounding_box().is_touching(bbox): entity.pos = entity_end_pos else: dp = entity_end_pos - entity_start_pos dx = dp.x dy = dp.y entity_width = entity.get_width() entity_height = entity.get_height() entity.move(Vec(dx, 0)) if entity.get_bounding_box().is_touching(bbox): if dx > 0: entity.set_x(bbox.get_left_b() - entity_width - 1) elif dx < 0: entity.set_x(bbox.get_right_b() + 1) entity.move(Vec(0, dy)) if entity.get_bounding_box().is_touching(bbox): if dy > 0: entity.set_y(bbox.get_top_b() - entity_height - 1) elif dy < 0: entity.set_y(bbox.get_bottom_b() + 1)
def test_point_operations(self): p1 = Point(1.0, 2.0, 3.0) v = Vec(4.0, 6.0, 8.0) p2 = Point(4.0, 6.0, 8.0) assert (p1 * 2).is_close(Point(2.0, 4.0, 6.0)) assert (p1 + v).is_close(Point(5.0, 8.0, 11.0)) assert (p2 - p1).is_close(Vec(3.0, 4.0, 5.0)) assert (p1 - v).is_close(Point(-3.0, -4.0, -5.0))
def testNormals(self): sphere = Sphere(transformation=scaling(Vec(2.0, 1.0, 1.0))) ray = Ray(origin=Point(1.0, 1.0, 0.0), dir=Vec(-1.0, -1.0)) intersection = sphere.ray_intersection(ray) # We normalize "intersection.normal", as we are not interested in its length assert intersection.normal.normalize().is_close( Normal(1.0, 4.0, 0.0).normalize())
def test_scalings(self): tr1 = scaling(Vec(2.0, 5.0, 10.0)) assert tr1.is_consistent() tr2 = scaling(Vec(3.0, 2.0, 4.0)) assert tr2.is_consistent() expected = scaling(Vec(6.0, 10.0, 40.0)) assert expected.is_close(tr1 * tr2)
class VecNormTestCase(unittest.TestCase): def setUp(self): self.v = Vec((5,5)) def test_norm2(self): self.assertEqual(self.v.norm2(), 50) def test_norm(self): self.assertEqual(round(self.v.norm()), 7)
def from_json(entity_dict): """Convert a dict representing a JSON object into an entity.""" entity_class = Entity.get_entity_by_id(entity_dict["entity_id"]) entity_pos = Vec(entity_dict["pos"]["x"], entity_dict["pos"]["y"]) entity_velocity = Vec(entity_dict["velocity"]["x"], entity_dict["velocity"]["y"]) entity_facing = Direction.str_to_direction(entity_dict["facing"]) ent = entity_class(entity_pos, entity_velocity, entity_facing) ent.uuid = uuid.UUID(entity_dict["uuid"]) return ent
def test_translations(self): tr1 = translation(Vec(1.0, 2.0, 3.0)) assert tr1.is_consistent() tr2 = translation(Vec(4.0, 6.0, 8.0)) assert tr1.is_consistent() prod = tr1 * tr2 assert prod.is_consistent() expected = translation(Vec(5.0, 8.0, 11.0)) assert prod.is_close(expected)
def testFurnace(self): pcg = PCG() # Run the furnace test several times using random values for the emitted radiance and reflectance for i in range(5): world = World() emitted_radiance = pcg.random_float() reflectance = pcg.random_float( ) * 0.9 # Be sure to pick a reflectance that's not too close to 1 enclosure_material = Material( brdf=DiffuseBRDF( pigment=UniformPigment(Color(1.0, 1.0, 1.0) * reflectance)), emitted_radiance=UniformPigment( Color(1.0, 1.0, 1.0) * emitted_radiance), ) world.add_shape(Sphere(material=enclosure_material)) path_tracer = PathTracer(pcg=pcg, num_of_rays=1, world=world, max_depth=100, russian_roulette_limit=101) ray = Ray(origin=Point(0, 0, 0), dir=Vec(1, 0, 0)) color = path_tracer(ray) expected = emitted_radiance / (1.0 - reflectance) assert pytest.approx(expected, 1e-3) == color.r assert pytest.approx(expected, 1e-3) == color.g assert pytest.approx(expected, 1e-3) == color.b
async def run(ws, path): """Run the WebSocket server.""" del path # Unused username = await ws.recv() try: player = running_game.get_player(username) print("Returning user: "******"New user: "******"starting_world" spawn_id = "center_spawn" world = World.get_world_by_id(world_id) spawn_pos = world.spawn_points[spawn_id].to_spawn_pos() player = Player(username, spawn_pos, Vec(0, 0), Direction.DOWN, ws, world_id) running_game.add_player(player) await Util.send_world(ws, world, spawn_pos) try: async for message in ws: await parseMessage(message, username, ws) except ConnectionClosed: player.online = False
def testTransformation(self): sphere = Sphere(transformation=translation(Vec(10.0, 0.0, 0.0))) ray1 = Ray(origin=Point(10, 0, 2), dir=-VEC_Z) intersection1 = sphere.ray_intersection(ray1) assert intersection1 assert HitRecord( world_point=Point(10.0, 0.0, 1.0), normal=Normal(0.0, 0.0, 1.0), surface_point=Vec2d(0.0, 0.0), t=1.0, ray=ray1, material=sphere.material, ).is_close(intersection1) ray2 = Ray(origin=Point(13, 0, 0), dir=-VEC_X) intersection2 = sphere.ray_intersection(ray2) assert intersection2 assert HitRecord( world_point=Point(11.0, 0.0, 0.0), normal=Normal(1.0, 0.0, 0.0), surface_point=Vec2d(0.0, 0.5), t=2.0, ray=ray2, material=sphere.material, ).is_close(intersection2) # Check if the sphere failed to move by trying to hit the untransformed shape assert not sphere.ray_intersection( Ray(origin=Point(0, 0, 2), dir=-VEC_Z)) # Check if the *inverse* transformation was wrongly applied assert not sphere.ray_intersection( Ray(origin=Point(-10, 0, 0), dir=-VEC_Z))
def respawn(self): """Reset player's location and other properties.""" world_id = "starting_world" spawn_id = "center_spawn" world = World.get_world_by_id(world_id) spawn_pos = world.spawn_points[spawn_id].to_spawn_pos() Player.__init__(self, self.username, spawn_pos, Vec(0, 0), Direction.DOWN, self.ws, world_id)
def testNormalDirection(self): # Scaling a sphere by -1 keeps the sphere the same but reverses its # reference frame sphere = Sphere(transformation=scaling(Vec(-1.0, -1.0, -1.0))) ray = Ray(origin=Point(0.0, 2.0, 0.0), dir=-VEC_Y) intersection = sphere.ray_intersection(ray) # We normalize "intersection.normal", as we are not interested in its length assert intersection.normal.normalize().is_close( Normal(0.0, 1.0, 0.0).normalize())
def demo(width, height, angle_deg, orthogonal, pfm_output, png_output): image = HdrImage(width, height) # Create a world and populate it with a few shapes world = World() for x in [-0.5, 0.5]: for y in [-0.5, 0.5]: for z in [-0.5, 0.5]: world.add( Sphere(transformation=translation(Vec(x, y, z)) * scaling(Vec(0.1, 0.1, 0.1)))) # Place two other balls in the bottom/left part of the cube, so # that we can check if there are issues with the orientation of # the image world.add( Sphere(transformation=translation(Vec(0.0, 0.0, -0.5)) * scaling(Vec(0.1, 0.1, 0.1)))) world.add( Sphere(transformation=translation(Vec(0.0, 0.5, 0.0)) * scaling(Vec(0.1, 0.1, 0.1)))) # Initialize a camera camera_tr = rotation_z(angle_deg=angle_deg) * translation( Vec(-1.0, 0.0, 0.0)) if orthogonal: camera = OrthogonalCamera(aspect_ratio=width / height, transformation=camera_tr) else: camera = PerspectiveCamera(aspect_ratio=width / height, transformation=camera_tr) # Run the ray-tracer tracer = ImageTracer(image=image, camera=camera) def compute_color(ray: Ray) -> Color: if world.ray_intersection(ray): return WHITE else: return BLACK tracer.fire_all_rays(compute_color) # Save the HDR image with open(pfm_output, "wb") as outf: image.write_pfm(outf) print(f"HDR demo image written to {pfm_output}") # Apply tone-mapping to the image image.normalize_image(factor=1.0) image.clamp_image() # Save the LDR image with open(png_output, "wb") as outf: image.write_ldr_image(outf, "PNG") print(f"PNG demo image written to {png_output}")
def parse_vector(input_file: InputStream, scene: Scene) -> Vec: expect_symbol(input_file, "[") x = expect_number(input_file, scene) expect_symbol(input_file, ",") y = expect_number(input_file, scene) expect_symbol(input_file, ",") z = expect_number(input_file, scene) expect_symbol(input_file, "]") return Vec(x, y, z)
def testOnbFromNormal(self): pcg = PCG() expected_zero = pytest.approx(0.0) expected_one = pytest.approx(1.0) for i in range(100): normal = Vec(pcg.random_float(), pcg.random_float(), pcg.random_float()) normal.normalize() e1, e2, e3 = create_onb_from_z(normal) assert e3.is_close(normal) assert expected_one == e1.squared_norm() assert expected_one == e2.squared_norm() assert expected_one == e3.squared_norm() assert expected_zero == e1.dot(e2) assert expected_zero == e2.dot(e3) assert expected_zero == e3.dot(e1)
def set_points(self, points): if len(points) < 2: raise InvalidStreetException("Street must have atleast 2 points") self.points = [] for (x, y) in points: self.points.append(Vec(x, y)) self.segments = [] for i in range(len(self.points) - 1): a = self.points[i] b = self.points[i + 1] self.segments.append(Segment(a, b))
def scatter_ray(self, pcg: PCG, incoming_dir: Vec, interaction_point: Point, normal: Normal, depth: int): # There is no need to use the PCG here, as the reflected direction is always completely deterministic # for a perfect mirror ray_dir = Vec(incoming_dir.x, incoming_dir.y, incoming_dir.z).normalize() normal = normal.to_vec().normalize() dot_prod = normal.dot(ray_dir) return Ray( origin=interaction_point, dir=ray_dir - normal * 2 * dot_prod, tmin=1e-5, tmax=inf, depth=depth, )
def __init__(self, pos, velocity, facing): """Initialize the Stander with certain default properties. Set velocity to 0. Set dialogue. """ super().__init__(pos, velocity, facing) self.velocity = Vec(0, 0) self.dialogue = [ "Don't get so close, scum! Do you know who my father is?", ["Yes", "No"], { 0: "That makes one of us...", 1: "Me neither..." }, ] self.conv_progress = {}
def __init__(self, pos, velocity, facing): """Initialize the Walker with certain default properties. Set velocity rightward with the norm of the given velocity. Set dialogue. Set min_x and max_x to three blocks left and three blocks to the right. """ super().__init__(pos, velocity, facing) self.speed = velocity.norm() self.velocity = Vec(self.speed, 0) self.min_x = pos.x - BLOCK_WIDTH * 3 self.max_x = pos.x + BLOCK_WIDTH * 3 self.dialogue = [ "Hi!", "This is dialogue.", f"And this is {'really '*42}long dialogue.", ] self.conv_progress = {}
def fire_ray(self, u, v): """Shoot a ray through the camera's screen The coordinates (u, v) specify the point on the screen where the ray crosses it. Coordinates (0, 0) represent the bottom-left corner, (0, 1) the top-left corner, (1, 0) the bottom-right corner, and (1, 1) the top-right corner, as in the following diagram:: (0, 1) (1, 1) +------------------------------+ | | | | | | +------------------------------+ (0, 0) (1, 0) """ origin = Point(-self.screen_distance, 0.0, 0.0) direction = Vec(self.screen_distance, (1.0 - 2 * u) * self.aspect_ratio, 2 * v - 1) return Ray(origin=origin, dir=direction, tmin=1.0).transform(self.transformation)
class Ray: """A ray of light propagating in space The class contains the following members: - `origin` (``Point``): the 3D point where the ray originated - `dir` (``Vec``): the 3D direction along which this ray propagates - `tmin` (float): the minimum distance travelled by the ray is this number times `dir` - `tmax` (float): the maximum distance travelled by the ray is this number times `dir` - `depth` (int): number of times this ray was reflected/refracted""" origin: Point = Point() dir: Vec = Vec() tmin: float = 1e-5 tmax: float = inf depth: int = 0 def is_close(self, other: Ray, epsilon=1e-5): """Check if two rays are similar enough to be considered equal""" return (self.origin.is_close(other.origin, epsilon=epsilon) and self.dir.is_close(other.dir, epsilon=epsilon)) def at(self, t): """Compute the point along the ray's path at some distance from the origin Return a ``Point`` object representing the point in 3D space whose distance from the ray's origin is equal to `t`, measured in units of the length of `Vec.dir`.""" return self.origin + self.dir * t def transform(self, transformation: Transformation): """Transform a ray This method returns a new ray whose origin and direction are the transformation of the original ray""" return Ray(origin=transformation * self.origin, dir=transformation * self.dir, tmin=self.tmin, tmax=self.tmax, depth=self.depth)
def __mul__(self, other): if isinstance(other, Vec): row0, row1, row2, row3 = self.m return Vec( x=other.x * row0[0] + other.y * row0[1] + other.z * row0[2], y=other.x * row1[0] + other.y * row1[1] + other.z * row1[2], z=other.x * row2[0] + other.y * row2[1] + other.z * row2[2]) elif isinstance(other, Point): row0, row1, row2, row3 = self.m p = Point(x=other.x * row0[0] + other.y * row0[1] + other.z * row0[2] + row0[3], y=other.x * row1[0] + other.y * row1[1] + other.z * row1[2] + row1[3], z=other.x * row2[0] + other.y * row2[1] + other.z * row2[2] + row2[3]) w = other.x * row3[0] + other.y * row3[1] + other.z * row3[ 2] + row3[3] if w == 1.0: return p else: return Point(p.x / w, p.y / w, p.z / w) elif isinstance(other, Normal): row0, row1, row2, _ = self.invm return Normal( x=other.x * row0[0] + other.y * row1[0] + other.z * row2[0], y=other.x * row0[1] + other.y * row1[1] + other.z * row2[1], z=other.x * row0[2] + other.y * row1[2] + other.z * row2[2]) elif isinstance(other, Transformation): result_m = _matr_prod(self.m, other.m) result_invm = _matr_prod( other.invm, self.invm) # Reverse order! (A B)^-1 = B^-1 A^-1 return Transformation(m=result_m, invm=result_invm) else: raise TypeError( f"Invalid type {type(other)} multiplied to a Transformation object" )
def to_pos(self): """Get the position of the upper-left corner of the tile.""" return Vec(self.block_x * BLOCK_WIDTH, self.block_y * BLOCK_WIDTH)
def to_spawn_pos(self): """Get the position in the tile in which the player spawns.""" return Vec( self.block_x * BLOCK_WIDTH + (BLOCK_WIDTH - PLAYER_WIDTH) / 2, self.block_y * BLOCK_WIDTH + (BLOCK_WIDTH - PLAYER_WIDTH) / 2)
import >>> from geometry import Vec init >>> v = Vec([1,2,3]) >>> v = Vec((1,2,3)) >>> v = Vec(range(4)) operators >>> v1 = Vec([1,2,3]) >>> v2 = Vec([4,5,6]) >>> v1 != (1,2,3) False >>> v1 + v2 == [5,7,9] True >>> (4,5,6) + v1 == [5,7,9] True >>> v1 - v2 == [-3,-3,-3] True >>> [5,7,9] - v1 == (4,5,6) True >>> v1 += v2 >>> v1 == [5,7,9] True
def test_transform(self): ray = Ray(origin=Point(1.0, 2.0, 3.0), dir=Vec(6.0, 5.0, 4.0)) transformation = translation(Vec(10.0, 11.0, 12.0)) * rotation_x(90.0) transformed = ray.transform(transformation) assert transformed.origin.is_close(Point(11.0, 8.0, 14.0)) assert transformed.dir.is_close(Vec(6.0, -4.0, 5.0))
def test_round(self): v = Vec([4.2,5.6,6.0]) self.assertEqual(v.round(), (4,6,6))
def setUp(self): self.v = Vec((5,5))
def get_path_vertex_mode(self, p_depart, p_arrive, enable_smooth=True): p_depart = Vec(p_depart) p_arrive = Vec(p_arrive) area_depart = self.find_area_for_point(p_depart) area_arrive = self.find_area_for_point(p_arrive) remove_arrive_at_end = True # le point d'arrivé est inateignable if not area_arrive: return [],[],[] # le point de départ est déjà dans les vertices (très peut probable) if p_depart in self.vertices: vertex_depart = self.vertices[p_depart] # sinon, on doit le rajouter 'à la main', puis le supprimer après le traitement else: vertex_depart = Node() vertex_depart.init(p_depart.__hash__(),p_depart, []) # si le point est dans une zone if area_depart: for p in area_depart.points: vertex_depart.neighbors.append(self.vertices[p]) # sinon, il est dans une zone interdite, on va chercher le chemin le plus court pour sortir else: near_vertex = min(self.vertices.values(), key=lambda vertex: (p_depart - vertex.pos).norm2()) vertex_depart.neighbors.append(self.vertices[near_vertex.pos]) if p_arrive not in self.vertices: vertex_arrive = Node() vertex_arrive.init(p_arrive.__hash__(),p_arrive, []) for p in area_arrive.points: self.vertices[p].neighbors.append(vertex_arrive) remove_arrive_at_end = True # application du pathfinding vertices, raw_path = self.pathfinding_vertices.compute_path(vertex_depart, vertex_arrive) if not enable_smooth: return [], vertices, raw_path # remove des vertices ajoutées if remove_arrive_at_end: for p in area_arrive.points: self.vertices[p].neighbors.remove(vertex_arrive) # smooth path if len(vertices) > 2: smooth_path = raw_path id_areas = set() for vertex in vertices[1:-1]: for area in vertex.areas: id_areas.add(area.id) for area in self.areas.values(): if area.id not in id_areas: area.walkable = False if not area_depart: # le départ est en zone interdite area_depart = min(vertices[1].areas, key=lambda a: (a.middle - p_arrive).norm2()) _areas, _raw_path, smooth_path = self.get_path_areas_mode(p_depart, p_arrive, area_depart) for area in self.areas.values(): area.walkable = True elif len(vertices) == 2: smooth_path = raw_path else: raw_path = [] smooth_path = [] return vertices, raw_path, smooth_path