from .utils.constants import * from .utils.vector3 import vec3, extract, rgb import numpy as np from functools import reduce as reduce RAYS = 0 PRIMARY_RAYS = 0 class Ray: """Info of the ray and the media it's travelling""" def __init__(self,origin, dir, depth, n, reflections, transmissions, diffuse_reflections): self.origin = origin # the point where the ray comes from self.dir = dir # direction of the ray self.depth = depth # ray_depth is the number of the refrections + transmissions/refractions, starting at zero for camera rays self.n = n # ray_n is the index of refraction of the media in which the ray is travelling # Instead of defining a index of refraction (n) for each wavelenght (computationally expensive) we aproximate defining the index of refraction # using a vec3 for red = 630 nm, green 555 nm, blue 475 nm, the most sensitive wavelenghts of human eye. # Index a refraction is a complex number. # The real part is involved in how much light is reflected and model refraction direction via Snell Law. # The imaginary part of n is involved in how much light is reflected and absorbed. For non-transparent materials like metals is usually between (0.1j,3j) # and for transparent materials like glass is usually between (0.j , 1e-7j) self.reflections = reflections #reflections is the number of the refrections, starting at zero for camera rays self.transmissions = transmissions #transmissions is the number of the transmissions/refractions, starting at zero for camera rays self.diffuse_reflections = diffuse_reflections #reflections is the number of the refrections, starting at zero for camera rays global RAYS RAYS += 1 if self.reflections == 0 and self.transmissions == 0 and self.diffuse_reflections == 0: global PRIMARY_RAYS PRIMARY_RAYS += 1 def extract(self,hit_check): return Ray(self.origin.extract(hit_check), self.dir.extract(hit_check), self.depth, self.n.extract(hit_check), self.reflections, self.transmissions,self.diffuse_reflections) class Hit: """Info of the ray-surface intersection""" def __init__(self, distance, orientation, material, collider,surface): self.distance = distance self.orientation = orientation self.material = material self.collider = collider self.surface = surface self.u = None self.v = None self.N = None self.point = None def get_uv(self): if self.u is None: #this is for prevent multiple computations of u,v self.u, self.v = self.collider.assigned_primitive.get_uv(self) return self.u, self.v def get_normal(self): if self.N is None: #this is for prevent multiple computations of normal self.N = self.collider.get_N(self) return self.N def get_raycolor(ray, scene): inters = [s.intersect(ray.origin, ray.dir) for s in scene.collider_list] distances, hit_orientation = zip(*inters) # get the shortest distance collision nearest = reduce(np.minimum, distances) color = rgb(0., 0., 0.) for (coll, dis , orient) in zip(scene.collider_list, distances, hit_orientation): hit_check = (nearest != FARAWAY) & (dis == nearest) if np.any(hit_check): material = coll.assigned_primitive.material hit_info = Hit(extract(hit_check,dis) , extract(hit_check,orient), material, coll, coll.assigned_primitive) cc = material.get_color(scene, ray.extract(hit_check), hit_info) color += cc.place(hit_check) return color def get_distances(ray, scene): #Used for debugging ray-surface collisions. Return a grey map of objects distances. inters = [s.intersect(ray.origin, ray.dir) for s in scene.collider_list] distances, hit_orientation = zip(*inters) # get the shortest distance collision nearest = reduce(np.minimum, distances) max_r_distance = 10 r_distance = np.where(nearest <= max_r_distance, nearest, max_r_distance) norm_r_distance = r_distance/max_r_distance return rgb(norm_r_distance, norm_r_distance, norm_r_distance) def get_rays(): return RAYS, PRIMARY_RAYS