Ejemplo n.º 1
0
    def random_in_unit_disk(self):
        while True:
            p = Vec3(random.random(), random.random(), 0) * 2.0 - Vec3(1, 1, 0)
            if (p.dot(p) < 1.0):
                break

        return p
    def scatter(self, r_in, rec):
        outward_normal = Vec3()
        reflected = Vec3.reflect(r_in.direction, rec["normal"])
        ni_over_nt = 0.0
        attenuation = Vec3(1.0, 1.0, 1.0)
        cosine = 0.0
        if Vec3.dot(r_in.direction, rec["normal"]) > 0:
            outward_normal = -rec["normal"]
            ni_over_nt = self.ref_idx
            cosine = self.ref_idx * Vec3.dot(
                r_in.direction, rec["normal"]) / r_in.direction.length()
        else:
            outward_normal = rec["normal"]
            ni_over_nt = 1.0 / self.ref_idx
            cosine = -Vec3.dot(r_in.direction,
                               rec["normal"]) / r_in.direction.length()

        scattered = Ray()
        reflect_prob = 0.0
        refracted = Vec3.refract(r_in.direction, outward_normal, ni_over_nt)
        if refracted is not None:
            reflect_prob = schlick(cosine, self.ref_idx)
        else:
            reflect_prob = 1.0
        if random() < reflect_prob:
            scattered = Ray(rec["p"], reflected)
        else:
            scattered = Ray(rec["p"], refracted)
        return attenuation, scattered
Ejemplo n.º 3
0
def cornell_box():
    '''Generate a scene with a Cornell box, using rectangles and boxes'''
    world = HittableList()

    red = Lambertian(Color(0.65, 0.05, 0.05))
    white = Lambertian(Color(0.73, 0.73, 0.73))
    green = Lambertian(Color(0.12, 0.45, 0.15))
    light = DiffuseLight(Color(15, 15, 15))
    aluminum = Metal(Color(0.8, 0.85, 0.88), 0.0)
    glass = Dielectric(1.5)

    box1 = Box(Point3(0, 0, 0), Point3(165, 330, 165), aluminum)
    box1 = RotateY(box1, 15)
    box1 = Translate(box1, Vec3(265, 0, 295))

    box2 = Box(Point3(0, 0, 0), Point3(165, 165, 165), white)
    box2 = RotateY(box2, -18)
    box2 = Translate(box2, Vec3(130, 0, 65))

    world.add(yzRect(0, 555, 0, 555, 555, green))
    world.add(yzRect(0, 555, 0, 555, 0, red))
    world.add(xzRect(0, 555, 0, 555, 555, white))
    world.add(xzRect(0, 555, 0, 555, 0, white))
    world.add(xyRect(0, 555, 0, 555, 555, white))

    world.add(box1)
    world.add(box2)

    world.add(FlipFace(xzRect(213, 343, 227, 332, 554, light)))

    return world
Ejemplo n.º 4
0
    def __init__(self,
                 look_from: Vec3 = Vec3(3.0, 3.0, 2.0),
                 look_at: Vec3 = Vec3(0.0, 0.0, -1.0),
                 vec_up: Vec3 = Vec3(0.0, 1.0, 0.0),
                 v_fov: float = 90.0,
                 aspect: float = 1.0,
                 aperture: float = 0.0,
                 focus_dist: float = 1.0):

        self.lens_radius = aperture / 2.0

        theta = v_fov * 3.14159 / 180.0
        half_height = math.tan(theta / 2.0)
        half_width = aspect * half_height

        w = unit_vector(look_from - look_at)
        self.u = unit_vector(vec_up.cross(w))
        self.v = w.cross(self.u)

        self.origin = look_from
        self.upper_left_corner = look_from - \
                                 half_width*self.u*focus_dist + \
                                 half_height*self.v*focus_dist - w*focus_dist
        self.horizontal = 2 * half_width * self.u * focus_dist
        self.vertical = -2 * half_height * self.v * focus_dist
Ejemplo n.º 5
0
def main():

    nx = 200
    ny = 100
    ns = 30

    f = open("generated_images/first_world.ppm","w")
    f.write("P3\n%d %d\n255\n"%(nx,ny))

    cam = Camera()

    world = Object3DList(
            [Sphere(Vec3(0.0,0.0,-1.0), 0.5),
             Sphere(Vec3(0.0,-100.5,-1.0),100)])

    # Note break with guide convention, vertical pixels start with index 0 at top
    for y in range(0,ny):
        for x in range(0,nx):
            col = Vec3(0.0,0.0,0.0)
            for _ in range(0, ns):
                u = (float(x)+random.random())/float(nx)
                v = (float(y)+random.random())/float(ny)
                r = cam.get_ray(u, v)
                col += color(r, world)

            col /= ns

            f.write(col.color_string(scale=255.99))

    f.close()
Ejemplo n.º 6
0
def main():
    with open("output2.ppm", "w") as f:
        nx, ny, ns = 200, 100, 100
        header = "P3\n{} {}\n255\n".format(nx, ny)
        f.write(header)

        sphere1 = Sphere(Vec3(0, 0, -1), 0.5)
        sphere2 = Sphere(Vec3(0, -100.5, -1), 100)
        world = Hitable_list([sphere1, sphere2])

        for j in range(ny - 1, -1, -1):
            for i in range(0, nx):

                col = Vec3(0, 0, 0)
                for s in range(ns):
                    u = float(i + random()) / float(nx)
                    v = float(j + random()) / float(ny)
                    ray = Camera().get_ray(u, v)
                    col += color(ray, world)

                col /= ns
                col = Vec3(sqrt(col.r()), sqrt(col.g()), sqrt(col.b()))
                ir = int(255.99 * col.r())
                ig = int(255.99 * col.g())
                ib = int(255.99 * col.b())
                line = "{} {} {}\n".format(ir, ig, ib)
                f.write(line)
def two_spheres():
    l = []
    checker = CheckerTexture(ConstantTexture(Vec3(0.2, 0.3, 0.1)),
                             ConstantTexture(Vec3(0.9, 0.9, 0.9)))
    l.append(Sphere(Vec3(0.0, -10.0, 0.0), 10, Lambertian(checker)))
    l.append(Sphere(Vec3(0.0, 10.0, 0.0), 10, Lambertian(checker)))
    return HitableList(l)
Ejemplo n.º 8
0
def colored_sphere(i, j, nx, ny):
    s = Sphere(Vec3(0, 0, -1), 0.5)
    r = origin_to_pixel(i, j, nx, ny)
    if s.hit(r, 0, float("inf")):
        return (s.normal + Vec3(1, 1, 1)) * 255.99 / 2
    else:
        return blue_to_white(i, j, nx, ny)
Ejemplo n.º 9
0
 def from_obj_geometry(cls, obj_path):
     Point = namedtuple("Point", ["x", "y", "z"])
     min = Vec3(0, 0, 0)
     max = Vec3(0, 0, 0)
     vertex_count = 0
     for line in open(obj_path, "r"):
         if line[:2] == "v ":
             vertex_values = line[2:].rstrip("\n").split(" ")
             vertex = Point(
                 float(vertex_values[0]),
                 float(vertex_values[1]),
                 float(vertex_values[2]),
             )
             vertex_count += 1
             if vertex_count == 1:
                 min = Vec3(vertex.x, vertex.y, vertex.z)
                 max = Vec3(vertex.x, vertex.y, vertex.z)
             else:
                 if vertex.x > max.x:
                     max.x = vertex.x
                 if vertex.y > max.y:
                     max.y = vertex.y
                 if vertex.z > max.z:
                     max.z = vertex.z
                 if vertex.x < min.x:
                     min.x = vertex.x
                 if vertex.y < min.y:
                     min.y = vertex.y
                 if vertex.z < min.z:
                     min.z = vertex.z
     return cls(Vec3.axis_z_up(min), Vec3.axis_z_up(max))
Ejemplo n.º 10
0
def diffuse_sphere(i, j, nx, ny, num_samples=100):
    s1 = Sphere(Vec3(-0.75, 0.75, -1), 0.5, Lambertian(Vec3(0.8, 0.3, 0.3)))
    s2 = Sphere(Vec3(0, -100.5, -1), 100, Lambertian(Vec3(0.2, 0.6, 0.2)))
    spheres = HitableList([s1, s2])
    cam = Camera()

    def color(r, world):
        if world.hit(r, 0.001, float("inf")):
            scattered, attenuation = world.matrl.scatter(
                r, world.p, world.normal)
            if scattered:
                return color(scattered, world) * attenuation
            else:
                return Vec3(0, 0, 0)
        else:
            v = r.direction().unit_vector(
            )  # use y-coordinate of unit direction to scale blueness
            t = (v.y() + 1) / 2  # ranges between 0 and 1
            return Vec3(255.99, 255.99, 255.99) * (1 - t) + Vec3(
                255.99 * 0.5, 255.99 * 0.7, 255.99) * t

    col = Vec3(0, 0, 0)
    for k in range(num_samples):
        col += color(
            cam.get_ray((i + random.random()) / nx,
                        (j + random.random()) / ny), spheres)

    return col / num_samples
Ejemplo n.º 11
0
def blue_to_white(i, j, nx, ny):
    r = origin_to_pixel(i, j, nx, ny)
    v = r.direction().unit_vector(
    )  # use y-coordinate of unit direction to scale blueness
    t = (v.y() + 1) / 2  # ranges between 0 and 1
    return Vec3(255.99, 255.99, 255.99) * (1 - t) + Vec3(
        255.99 * 0.5, 255.99 * 0.7, 255.99) * t
Ejemplo n.º 12
0
def test_finte():
    q1 = Quat(10000000000, 210000000000, 310000000000, -2147483647)
    q2 = Quat(np.nan, 210000000000, 310000000000, -2147483647)
    q3 = Quat(np.inf, 210000000000, 310000000000, -2147483647)
    q4 = Quat(0.0, 210000000000, np.NINF, -2147483647)

    v1 = Vec3(10000000000, 210000000000, 310000000000)
    v2 = Vec3(np.nan, 210000000000, 310000000000)
    v3 = Vec3(np.inf, 210000000000, 310000000000)
    v4 = Vec3(0.0, 210000000000, np.NINF)

    t1 = Transform(q1, v1)
    assert t1.is_finite()
    assert not t1.is_nan()

    t2 = Transform(q1, v2)
    assert not t2.is_finite()
    assert t2.is_nan()

    t3 = Transform(q3, v1)
    assert not t3.is_finite()
    assert t3.is_nan()

    t4 = Transform(q4, v4)
    assert not t4.is_finite()
    assert t4.is_nan()
Ejemplo n.º 13
0
def test_rotate_vector():
    v = Vec3(1, 1, 0)

    q1 = Quat.from_rotation_on_axis(0, np.pi / 3)
    q2 = Quat.from_rotation_on_axis(1, np.pi / 3)
    q3 = Quat.from_rotation_on_axis(2, np.pi / 3)

    t1 = Transform(q1, Vec3.zero())
    t2 = Transform(q2, Vec3.zero())
    t3 = Transform(q3, Vec3.zero())

    # forward rotation
    v1 = t1.rotate(v)
    v2 = t2.rotate(v)
    v3 = t3.rotate(v)

    theta1 = (60) * np.pi / 180.0
    v1_true = Vec3(1, math.cos(theta1), math.sin(theta1))
    v2_true = Vec3(math.cos(theta1), 1, -math.sin(theta1))

    theta2 = (45 + 60) * np.pi / 180.0
    v3_true = math.sqrt(2) * Vec3(math.cos(theta2), math.sin(theta2), 0)

    assert v1 == v1_true
    assert v2 == v2_true
    assert v3 == v3_true

    # inverse rotate
    v1_rev = t1.inverse_rotate(v1_true)
    v2_rev = t2.inverse_rotate(v2_true)
    v3_rev = t3.inverse_rotate(v3_true)

    assert v == v1_rev
    assert v == v2_rev
    assert v == v3_rev
Ejemplo n.º 14
0
	def readObj(self, path):
		with open(path, "rt") as file:
			for line in file:
				if line.startswith("v "):
					info = line.split()
					self.vertices.append(Vec3(float(info[1]), float(info[2]), float(info[3])))
				elif line.startswith("vt "):
					info = line.split()
					self.tex_coords.append(Vec3(float(info[1]), float(info[2]), 0.0))
					self.has_tex_coords = True
				elif line.startswith("vn "):
					info = line.split()
					self.normals.append(Vec3(float(info[1]), float(info[2]), float(info[3])))
					self.has_normals = True
				elif line.startswith("f "):
					info = line.split()
					indices_of_vertices_of_face = []
					for i in info:
						if i != "f":
							vertex_data = []
							for j in i.split("/"):
								if j == '':
									vertex_data.append('')
								else:
									vertex_data.append(int(j) - 1)
							indices_of_vertices_of_face.append(vertex_data)
					self.faces.append(indices_of_vertices_of_face)
				elif line.startswith("o"):
					info = line.split()
					self.name = info[1]
				else:
					continue
Ejemplo n.º 15
0
def calculate(R, v, time_step, n_steps):
    # G = 1
    # M1 = 1
    # M2 = 1
    v_low = v / np.sqrt(2)
    v_high = v * 2
    v_third = v / 3
    r1 = Vec3(-R, 0, 0)
    r2 = Vec3(R, 0, 0)
    v1 = Vec3(0, -v, 0)
    v2 = Vec3(0, v, 0)
    r1_data, r2_data = [], []
    runge1, runge2 = [], []
    eccentr1, eccentr2 = [], []
    for i in range(n_steps + 1):
        r1_data.append(r1)
        r2_data.append(r2)
        rlv1 = Vec3.cross(v1, Vec3.cross(r1, v1)) - r1
        rlv2 = Vec3.cross(v2, Vec3.cross(r2, v2)) - r2
        ecc1 = math.sqrt(rlv1.abs_sq())
        ecc2 = math.sqrt(rlv2.abs_sq())
        runge1.append(rlv1)
        runge2.append(rlv2)
        eccentr1.append(ecc1)
        eccentr2.append(ecc2)
        r1, r2, v1, v2 = step(r1, r2, v1, v2, time_step)
        print("\rcalculation progress: t = {:.2f} / {:.2f} ({:.2f}%)".format(
            i * time_step, n_steps * time_step, 100 * i / n_steps),
              end="")
    # return r1_data, r2_data, eccentr1, eccentr2
    return r1, r2, v1, v2
Ejemplo n.º 16
0
    def scatter(self, ray, rec, srec):
        srec.is_specular = True
        srec.pdf_ptr = 0
        srec.attenuation = Vec3(1.0, 1.0, 1.0)
        outward_normal = Vec3()
        reflected = ray.dir.reflect(rec.normal)
        refracted = Vec3()

        if ray.dir.dot(rec.normal) > 0:
            outward_normal = -rec.normal
            ni_over_nt = self.ref_idx
            cosine = self.ref_idx * ray.dir.dot(rec.normal) / ray.dir.length()
        else:
            outward_normal = rec.normal
            ni_over_nt = 1.0 / self.ref_idx
            cosine = -ray.dir.dot(rec.normal) / ray.dir.length()

        if self.refract(ray.dir, outward_normal, ni_over_nt, refracted):
            reflect_prob = self.schlick(cosine, self.ref_idx)
        else:
            reflect_prob = 1.0

        if random() < reflect_prob:
            srec.specular_ray = Ray(rec.p, reflected)
        else:
            srec.specular_ray = Ray(rec.p, refracted)
        return True
Ejemplo n.º 17
0
def test_dot():
    v1 = Vec3(0, 1, 2)
    v2 = Vec3(3, 4, 5)

    res = v1.dot(v2)

    assert res == 14
Ejemplo n.º 18
0
    def scatter(self, r_in, rec, attenuation, scattered):
        reflected = reflect(r_in.direction, rec.normal)
        attenuation = Vec3(1, 1, 1)
        refracted = Vec3()
        if r_in.direction.dot(rec.normal) > 0.0:
            outward_normal = -rec.normal
            ni_over_nt = self.ref_idx
            cosine = r_in.direction.dot(rec.normal) / r_in.direction.length
            cosine = sqrt(1.0 - self.ref_idx * self.ref_idx *
                          (1.0 - cosine * cosine))
        else:
            outward_normal = rec.normal
            ni_over_nt = 1.0 / self.ref_idx
            cosine = -r_in.direction.dot(rec.normal) / r_in.direction.length

        if refract(r_in.direction, outward_normal, ni_over_nt, refracted):
            reflect_prob = schlick(cosine, self.ref_idx)
        else:
            reflect_prob = 1.0

        if random() < reflect_prob:
            scattered.update(rec.p, reflected)
        else:
            scattered.update(rec.p, refracted)

        return True
Ejemplo n.º 19
0
    def __init__(self):
        """A camera clas."""

        self.origin = Vec3(0.0, 0.0, 0.0)
        self.lower_left_corner = Vec3(-2.0, -1.0, -1.0)
        self.horizontal = Vec3(4.0, 0.0, 0.0)
        self.vertical = Vec3(0.0, 2.0, 0.0)
Ejemplo n.º 20
0
def random_in_unit_sphere_3():
    """Get a random vector inside the 3d unit sphere."""

    while True:
        p = Vec3(random(), random(), random()) * 2.0 - Vec3(1, 1, 1)
        if p.squared_length < 1.0:
            return p
def main():
    nx = 200
    ny = 150
    ns = 100
    print("P3\n", nx, " ", ny, "\n255")

    world = two_spheres()

    lookfrom = Vec3(13, 2, 3)
    lookat = Vec3(0, 0, 0)
    dist_to_focus = 10.0
    aperture = 0.0
    cam = Camera(lookfrom, lookat, Vec3(0, 1, 0), 20, nx / ny, aperture,
                 dist_to_focus, 0.0, 1.0)
    for j in reversed(range(ny)):
        for i in range(nx):
            col = Vec3(0, 0, 0)
            for s in range(ns):
                u = (i + random()) / nx
                v = (j + random()) / ny
                r = cam.get_ray(u, v)
                p = r.point_at_parameter(2.0)
                col += color(r, world, 0)

            col /= ns
            col = Vec3(sqrt(col[0]), sqrt(col[1]), sqrt(col[2]))
            ir = int(255.99 * col[0])
            ig = int(255.99 * col[1])
            ib = int(255.99 * col[2])
            print(ir, " ", ig, " ", ib)
Ejemplo n.º 22
0
def getSegmentsFromIntersections( solidXIntersectionList, y, z ):
	"Get endpoint segments from the intersections."
	segments = []
	xIntersectionList = []
	fill = False
	solid = False
	solidTable = {}
	solidXIntersectionList.sort( compareSolidXByX )
	for solidX in solidXIntersectionList:
		solidXYInteger = int( solidX.imag )
		if solidXYInteger >= 0:
			toggleHashtable( solidTable, solidXYInteger, "" )
		else:
			fill = not fill
		oldSolid = solid
		solid = ( len( solidTable ) == 0 and fill )
		if oldSolid != solid:
			xIntersectionList.append( solidX.real )
	for xIntersectionIndex in range( 0, len( xIntersectionList ), 2 ):
		firstX = xIntersectionList[ xIntersectionIndex ]
		secondX = xIntersectionList[ xIntersectionIndex + 1 ]
		endpointFirst = Endpoint()
		endpointSecond = Endpoint().getFromOtherPoint( endpointFirst, Vec3( secondX, y, z ) )
		endpointFirst.getFromOtherPoint( endpointSecond, Vec3( firstX, y, z ) )
		segment = ( endpointFirst, endpointSecond )
		segments.append( segment )
	return segments
Ejemplo n.º 23
0
def random_in_unit_disk_2():
    """Get a random vector inside the unit sphere."""

    while True:
        p = Vec3(random(), random(), 0) * 2.0 - Vec3(1, 1, 0)
        if p.dot(p) < 1.0:
            return p
Ejemplo n.º 24
0
def main():
    with open("output.ppm", "w") as f:
        nx, ny, ns = 200, 100, 100
        header = "P3\n{} {}\n255\n".format(nx, ny)
        f.write(header)

        camera = Camera()
        sphere1 = Sphere(Vec3(0.0, 0.0, -1.0), 0.5,
                         Lambertian(Vec3(0.8, 0.3, 0.3)))
        sphere2 = Sphere(Vec3(0.0, -100.5, -1.0), 100.0,
                         Lambertian(Vec3(0.8, 0.8, 0.0)))
        sphere3 = Sphere(Vec3(1.0, 0.0, -1.0), 0.5, Metal(Vec3(0.8, 0.6, 0.2)))
        sphere4 = Sphere(Vec3(-1.0, 0.0, -1.0), 0.5, Metal(Vec3(0.8, 0.8,
                                                                0.8)))

        world = Hitable_list([sphere1, sphere2, sphere3, sphere4])
        for j in range(ny - 1, -1, -1):
            for i in range(nx):
                col = Vec3(0.0, 0.0, 0.0)
                for k in range(0, ns):
                    u = float(i + random()) / float(nx)
                    v = float(j + random()) / float(ny)
                    ray = camera.get_ray(u, v)
                    col += color(ray, world, 0)

                col /= float(ns)
                col = Vec3(sqrt(col.e0), sqrt(col.e1), sqrt(col.e2))
                ir = int(255.99 * col.r())
                ig = int(255.99 * col.g())
                ib = int(255.99 * col.b())
                line = "{} {} {}\n".format(ir, ig, ib)
                f.write(line)
Ejemplo n.º 25
0
def random_in_unit_sphere():
    #    p = Vec3(0, 0, 0)
    while True:
        p = Vec3(random(), random(), random()) * 2.0 - Vec3(1.0, 1.0, 1.0)
        if p.squared_length < 1.0:
            break
    return p
Ejemplo n.º 26
0
    def __init__(self):

        self.position = Vec3()
        self.normal = Vec3()

        self.t = 0.0
        self.frontFacing = False
Ejemplo n.º 27
0
def main():
    # G = 1
    # M1 = 1
    # M2 = 1
    R = 1
    v = math.sqrt(1 / (4 * R))
    r1 = Vec3(-R, 0, 0)
    r2 = Vec3(R, 0, 0)
    v1 = Vec3(0, -v, 0)
    v2 = Vec3(0, v, 0)
    time_step = 0.01
    r1_data, r2_data = [], []
    n_steps = 100000
    for i in range(n_steps + 1):
        r1_data.append(r1)
        r2_data.append(r2)
        r1, r2, v1, v2 = leapfrog(r1, r2, v1, v2, time_step)
        # print("\rcalculation progress: t = {:.2f} / {:.2f} ({:.2f}%)".format(i * time_step, n_steps * time_step,
        #                                                                      100 * i / n_steps),
        #       end="")
        print(runge(r1, v1))
        print(runge(r2, v2))
    x1_data = [p.x for p in r1_data]
    y1_data = [p.y for p in r1_data]
    x2_data = [p.x for p in r2_data]
    y2_data = [p.y for p in r2_data]
    plt.figure(figsize=(8, 8))
    plt.plot(x1_data, y1_data)
    plt.plot(x2_data, y2_data)
    plt.xlim(-5, 5)
    plt.ylim(-5, 5)
    plt.show()
Ejemplo n.º 28
0
 def __init__(self, h):
     nz = cos(radians(h))
     nx = sqrt(1 - nz * nz)
     height = nz / nx
     #print(nx, nz, height)
     self.lside = Plane(Vec3(0.0, 0.0, -height), Vec3(nx, 0.0, nz))
     self.rside = Plane(Vec3(0.0, 0.0, -height), Vec3(-nx, 0.0, nz))
Ejemplo n.º 29
0
def cornell_smoke():
    '''Generate a scene with a Cornell box where the boxes are filled with smoke'''
    world = HittableList()

    red = Lambertian(Color(0.65, 0.05, 0.05))
    white = Lambertian(Color(0.73, 0.73, 0.73))
    green = Lambertian(Color(0.12, 0.45, 0.15))
    light = DiffuseLight(Color(7, 7, 7))

    box1 = Box(Point3(0, 0, 0), Point3(165, 330, 165), white)
    box1 = RotateY(box1, 15)
    box1 = Translate(box1, Vec3(265, 0, 295))

    box2 = Box(Point3(0, 0, 0), Point3(165, 165, 165), white)
    box2 = RotateY(box2, -18)
    box2 = Translate(box2, Vec3(130, 0, 65))

    world.add(yzRect(0, 555, 0, 555, 555, green))
    world.add(yzRect(0, 555, 0, 555, 0, red))
    world.add(xzRect(0, 555, 0, 555, 555, white))
    world.add(xzRect(0, 555, 0, 555, 0, white))
    world.add(xyRect(0, 555, 0, 555, 555, white))

    world.add(ConstantMedium(box1, 0.01, Color(0, 0, 0)))
    world.add(ConstantMedium(box2, 0.01, Color(1, 1, 1)))

    world.add(xzRect(113, 443, 127, 432, 554, light))

    return world
Ejemplo n.º 30
0
def color(r, world, depth):
    """Get colour for:

    r      ray of interest
    world  set of hitable objects
    depth  max interaction count

    Returns colour as Vec3.
    """

    rec = Hit_Record()
    if world.hit(r, 0.001, MAXFLOAT, rec):
        #
        print('rec=%s' % str(rec))
        sys.stdout.flush()
        #
        scattered = Ray()
        attenuation = Vec3()
        if depth < 50.0 and rec.mat_ptr.scatter(r, rec, attenuation,
                                                scattered):
            print('depth=%s' % str(depth))
            return attenuation * color(scattered, world, depth + 1)

        return Vec3(0, 0, 0)
    else:
        unit_direction = r.direction.unit_vector
        t = 0.5 * (unit_direction.y + 1.0)
        return Vec3(1, 1, 1) * (1.0 - t) + Vec3(0.5, 0.7, 1.0) * t