def hit(self, r: Ray, t_min: float, t_max: float) -> Optional[HitRecord]: oc: Vec3 = r.origin() - self.center a: float = r.direction().length_squared() half_b: float = oc @ r.direction() c: float = oc.length_squared() - self.radius**2 discriminant: float = half_b**2 - a * c if discriminant > 0: root: float = np.sqrt(discriminant) t_0: float = (-half_b - root) / a t_1: float = (-half_b + root) / a if t_min < t_0 < t_max: t = t_0 elif t_min < t_1 < t_max: t = t_1 else: return None point: Point3 = r.at(t) outward_normal: Vec3 = (point - self.center) / self.radius rec = HitRecord(point, t, self.material) rec.set_face_normal(r, outward_normal) return rec return None
def hit(self, r: Ray, t_min: float, t_max: float) -> Optional[HitRecord]: origin = r.origin().copy() direction = r.direction().copy() origin[0] = self.cos_theta * r.origin()[0] - self.sin_theta * r.origin( )[2] origin[2] = self.sin_theta * r.origin()[0] + self.cos_theta * r.origin( )[2] direction[0] = \ self.cos_theta*r.direction()[0] - self.sin_theta*r.direction()[2] direction[2] = \ self.sin_theta*r.direction()[0] + self.cos_theta*r.direction()[2] rotated_r = Ray(origin, direction, r.time()) rec = self.obj.hit(rotated_r, t_min, t_max) if rec is None: return None p = rec.p.copy() normal = rec.normal.copy() p[0] = self.cos_theta * rec.p[0] + self.sin_theta * rec.p[2] p[2] = -self.sin_theta * rec.p[0] + self.cos_theta * rec.p[2] normal[0] = \ self.cos_theta*rec.normal[0] + self.sin_theta*rec.normal[2] normal[2] = \ -self.sin_theta*rec.normal[0] + self.cos_theta*rec.normal[2] rec.p = p rec.set_face_normal(rotated_r, normal) return rec
def scatter(self, r_in: Ray, rec: HitRecord) \ -> Optional[Tuple[Ray, Color]]: reflected: Vec3 = r_in.direction().unit_vector().reflect(rec.normal) \ + Vec3.random_in_unit_sphere() * self.fuzz scattered = Ray(rec.p, reflected) attenuation = self.albedo if scattered.direction() @ rec.normal > 0: return scattered, attenuation return None
def hit_deprecated(self, r: Ray, tmin: float, tmax: float) -> bool: for i in range(3): t0: float = min((self.min()[i] - r.origin()[i]) / r.direction()[i], (self.max()[i] - r.origin()[i]) / r.direction()[i]) t1: float = min((self.min()[i] - r.origin()[i]) / r.direction()[i], (self.max()[i] - r.origin()[i]) / r.direction()[i]) tmin = max(t0, tmin) tmax = min(t1, tmax) if tmax <= tmin: return False return True
def hit(self, r: Ray, t_min: float, t_max: float) -> Optional[HitRecord]: t = (self.k - r.origin().x()) / r.direction().x() if t < t_min or t > t_max: return None y = r.origin().y() + t*r.direction().y() z = r.origin().z() + t*r.direction().z() if (y < self.y0) or (y > self.y1) or (z < self.z0) or (z > self.z1): return None rec = HitRecord(r.at(t), t, self.material) rec.set_face_normal(r, Vec3(1, 0, 0)) rec.u = (y - self.y0) / (self.y1 - self.y0) rec.v = (z - self.z0) / (self.z1 - self.z0) return rec
def hit(self, r: Ray, t_min: float, t_max: float) -> Optional[HitRecord]: rec1 = self.boundary.hit(r, -np.inf, np.inf) if rec1 is None: return None rec2 = self.boundary.hit(r, rec1.t + 0.0001, np.inf) if rec2 is None: return None if rec1.t < t_min: rec1.t = t_min if rec1.t < 0: rec1.t = 0 if rec2.t > t_max: rec2.t = t_max if rec1.t >= rec2.t: return None ray_length = r.direction().length() distance_inside_boundary = (rec2.t - rec1.t) * ray_length hit_distance = self.neg_inv_density * np.log(random_float()) if hit_distance > distance_inside_boundary: return None t = rec1.t + hit_distance / ray_length p = r.at(t) rec = HitRecord(p, t, self.phase_function) rec.normal = Vec3(1, 0, 0) rec.front_face = True return rec
def scatter(self, r_in: Ray, rec: HitRecord) \ -> Optional[Tuple[Ray, Color]]: if rec.front_face: etai_over_etat = 1 / self.ref_idx else: etai_over_etat = self.ref_idx unit_direction: Vec3 = r_in.direction().unit_vector() cos_theta: float = min(-unit_direction @ rec.normal, 1) sin_theta: float = np.sqrt(1 - cos_theta**2) reflect_prob: float = self.schlick(cos_theta, etai_over_etat) if etai_over_etat * sin_theta > 1 or random_float() < reflect_prob: # total internal reflection reflected: Vec3 = unit_direction.reflect(rec.normal) scattered = Ray(rec.p, reflected) else: # refraction refracted: Vec3 = unit_direction.refract( rec.normal, etai_over_etat ) scattered = Ray(rec.p, refracted) attenuation = Color(1, 1, 1) return scattered, attenuation
def hit(self, r: Ray, t_min: float, t_max: float) -> Optional[HitRecord]: # remove the offset to hit the real object moved_r = Ray(r.origin() - self.offset, r.direction(), r.time()) rec = self.obj.hit(moved_r, t_min, t_max) if rec is None: return None # add the offset back to simulate the move rec.p += self.offset rec.set_face_normal(moved_r, rec.normal) return rec
def hit(self, r: Ray, tmin: float, tmax: float) -> bool: for i in range(3): invD: float = 1 / r.direction()[i] t0: float = (self.min()[i] - r.origin()[i]) * invD t1: float = (self.max()[i] - r.origin()[i]) * invD if invD < 0: t0, t1 = t1, t0 tmin = max(t0, tmin) tmax = min(t1, tmax) if tmax <= tmin: return False return True
def ray_color(r: Ray, world: HittableList, depth: int) -> Color: if depth <= 0: return Color(0, 0, 0) rec: Optional[HitRecord] = world.hit(r, 0.001, np.inf) if rec is not None: scatter_result = rec.material.scatter(r, rec) if scatter_result is not None: scattered, attenuation = scatter_result return attenuation * ray_color(scattered, world, depth - 1) return Color(0, 0, 0) unit_direction: Vec3 = r.direction().unit_vector() t = (unit_direction.y() + 1) * 0.5 return Color(1, 1, 1) * (1 - t) + Color(0.5, 0.7, 1) * t
def set_face_normal(self, r: Ray, outward_normal: Vec3) -> HitRecord: self.front_face = (r.direction() @ outward_normal) < 0 self.normal = outward_normal if self.front_face else -outward_normal return self