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 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 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 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 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 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)
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)
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
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))
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 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 test_vector_operations(self): a = Vec(1.0, 2.0, 3.0) b = Vec(4.0, 6.0, 8.0) assert (-a).is_close(Vec(-1.0, -2.0, -3.0)) assert (a + b).is_close(Vec(5.0, 8.0, 11.0)) assert (b - a).is_close(Vec(3.0, 4.0, 5.0)) assert (a * 2).is_close(Vec(2.0, 4.0, 6.0)) assert pytest.approx(40.0) == a.dot(b) assert a.cross(b).is_close(Vec(-2.0, 4.0, -2.0)) assert b.cross(a).is_close(Vec(2.0, -4.0, 2.0)) assert pytest.approx(14.0) == a.squared_norm() assert pytest.approx(14.0) == a.norm()**2
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 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 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 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)
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)
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)