def sample(self, u1: FLOAT, u2: FLOAT) -> ['geo.Point', 'geo.Normal']: z = util.lerp(u1, self.zmin, self.zmax) t = u2 * self.phiMax p = geo.Point(self.radius * np.cos(t), self.radius * np.sin(t), z) Ns = geo.normalize(self.o2w(geo.Normal(p.x, p.y, 0.))) if self.ro: Ns *= -1. return [self.o2w(p), Ns]
def __call__(self, arg, dtype=None): if isinstance(arg, geo.Point) or (isinstance(arg, np.ndarray) and dtype == geo.Point): res = self.m[0:4, 0:3].dot(arg) + self.m[0:4, 3] p = geo.Point(res[0], res[1], res[2]) if util.ne_unity(res[3]): p /= res[3] return p elif isinstance(arg, geo.Vector) or (isinstance(arg, np.ndarray) and dtype == geo.Vector): res = self.m[0:3, 0:3].dot(arg) return geo.Vector(res[0], res[1], res[2]) elif isinstance(arg, geo.Normal) or (isinstance(arg, np.ndarray) and dtype == geo.Normal): # must be transformed by inverse transpose res = self.m_inv[0:3, 0:3].T.dot(arg) return geo.Normal(res[0], res[1], res[2]) elif isinstance(arg, geo.RayDifferential): r = geo.RayDifferential.from_rd(arg) r.o = self(r.o) r.d = self(r.d) return r elif isinstance(arg, geo.Ray): r = geo.Ray.from_ray(arg) r.o = self(r.o) r.d = self(r.d) return r elif isinstance(arg, geo.BBox): res = geo.BBox.from_bbox(arg) x = geo.Vector(res.pMax.x - res.pMin.x, 0., 0.) y = geo.Vector(0., res.pMax.y - res.pMin.y, 0.) z = geo.Vector(0., 0., res.pMax.z - res.pMin.z) res.pMin = self(res.pMin) x = self(x) y = self(y) z = self(z) res.pMax = res.pMin + (x + y + z) return res else: raise TypeError( 'Transform can only be called on geo.Point, geo.Vector, geo.Normal, geo.Ray or geo.geo.BBox' )
def sample(self, u1: FLOAT, u2: FLOAT) -> ['geo.Point', 'geo.Normal']: """ account for partial sphere """ from pytracer.montecarlo import uniform_sample_sphere v = uniform_sample_sphere(u1, u2) phi = geo.spherical_theta(v) * self.phiMax * INV_2PI theta = self.thetaMin + geo.spherical_theta(v) * (self.thetaMax - self.thetaMin) v = geo.spherical_direction(np.sin(theta), np.cos(theta), phi) * self.radius v.z = self.zmin + v.z * (self.zmax - self.zmin) p = geo.Point.from_arr(v) Ns = geo.normalize(self.o2w(geo.Normal(p.x, p.y, p.z))) if self.ro: Ns *= -1. return [self.o2w(p), Ns]
def intersect( self, r: 'geo.Ray') -> (bool, FLOAT, FLOAT, 'geo.DifferentialGeometry'): """ Returns: - bool: specify whether intersects - tHit: hit point param - rEps: error tolerance - dg: geo.DifferentialGeometry object """ # transform ray to object space ray = self.w2o(r) # parallel ray has not intersection if np.fabs(ray.d.z) < EPS: return [False, None, None, None] thit = (self.height - ray.o.z) / ray.d.z if thit < ray.mint or thit > ray.maxt: return [False, None, None, None] phit = ray(thit) # check radial distance dt2 = phit.x * phit.x + phit.y * phit.y if dt2 > self.radius * self.radius or \ dt2 < self.inner_radius * self.inner_radius: return [False, None, None, None] # check angle phi = np.arctan2(phit.y, phit.x) if phi < 0: phi += 2. * np.pi if phi > self.phiMax: return [False, None, None, None] # otherwise ray hits the disk # initialize the differential structure u = phi / self.phiMax v = 1. - ((np.sqrt(dt2 - self.inner_radius)) / (self.radius - self.inner_radius)) # find derivatives dpdu = geo.Vector(-self.phiMax * phit.y, self.phiMax * phit.x, 0.) dpdv = geo.Vector(-phit.x / (1. - v), -phit.y / (1. - v), 0.) dpdu *= self.phiMax * INV_2PI dpdv *= (self.radius - self.inner_radius) / self.radius # derivative of geo.Normals dndu = geo.Normal( 0., 0., 0., ) dndv = geo.Normal( 0., 0., 0., ) o2w = self.o2w dg = geo.DifferentialGeometry(o2w(phit), o2w(dpdu), o2w(dpdv), o2w(dndu), o2w(dndv), u, v, self) return True, thit, EPS * thit, dg
from pytracer import FLOAT import pytracer.geometry as geo from pytracer.transform import Transform import pytracer.transform.quat as quat N_TEST_CASE = 5 VAR = 10. EPS = 6 np.random.seed(1) rng = np.random.rand test_data = { 'vector': [geo.Vector(rng(), rng(), rng()) * VAR for _ in range(N_TEST_CASE)], 'normal': [geo.Normal(rng(), rng(), rng()) * VAR for _ in range(N_TEST_CASE)], 'point': [geo.Point(rng(), rng(), rng()) * VAR for _ in range(N_TEST_CASE)], 'quat': [ quat.Quaternion(rng(), rng(), rng(), rng()) * VAR for _ in range(N_TEST_CASE) ], } test_data['vector'].extend([ geo.Vector(0., 0., 0.), geo.Vector(1., 0., 0.), geo.Vector( 0., 1., 0., ),
def get_shading_geometry( self, o2w: 'trans.Transform', dg: 'geo.DifferentialGeometry') -> 'geo.DifferentialGeometry': if self.mesh.n is None or self.mesh.s is None: return dg # compute barycentric coord uvs = self.get_uvs() A = np.array([[uvs[1][0] - uvs[0][0], uvs[2][0] - uvs[0][0]], [uvs[1][1] - uvs[0][1], uvs[2][1] - uvs[0][1]]], dtype=FLOAT) C = np.array([dg.u - uvs[0][0], dg.v - uvs[0][1]]) try: b = np.linalg.solve(A, C) except: b = [1. / 3, 1. / 3, 1. / 3] else: b = [1. - b[0] - b[1], b[0], b[1]] # compute shading tangents if self.mesh.n is not None: ns = geo.normalize( o2w(b[0] * self.mesh.n[self.v] + b[1] * self.mesh.n[self.v + 1] + b[2] * self.mesh.n[self.v + 2])) else: ns = dg.nn if self.mesh.s is not None: ss = geo.normalize( o2w(b[0] * self.mesh.s[self.v] + b[1] * self.mesh.s[self.v + 1] + b[2] * self.mesh.s[self.v + 2])) else: ss = geo.normalize(dg.dpdu) ts = ss.cross(ns) if ts.sq_length() > 0.: ts.normalize() ss = ts.cross(ns) else: _, ss, ts = geo.coordinate_system(ns) # compute dndu and dndv if self.mesh.n is not None: du1 = uvs[0][0] - uvs[2][0] du2 = uvs[1][0] - uvs[2][0] dv1 = uvs[0][1] - uvs[2][1] dv2 = uvs[1][1] - uvs[2][1] dn1 = self.mesh.n[self.mesh.vertexIndex[self.v]] - self.mesh.n[ self.mesh.vertexIndex[self.v + 2]] dn2 = self.mesh.n[self.mesh.vertexIndex[self.v + 1]] - self.mesh.n[ self.mesh.vertexIndex[self.v + 2]] det = du1 * dv2 - du2 * dv1 if det == 0.: # choose an arbitrary system dndu = dndv = geo.Normal(0., 0., 0.) else: detInv = 1. / det dndu = (dv2 * dn1 - dv1 * dn2) * detInv dndv = (-du2 * dn1 + du1 * dn2) * detInv else: dndu = geo.Normal(0., 0., 0.) dndv = geo.Normal(0., 0., 0.) dgs = geo.DifferentialGeometry(dg.p, ss, ts, self.mesh.o2w(dndu), self.mesh.o2w(dndv), dg.u, dg.v, dg.shape) dgs.dudx = dg.dudx dgs.dvdx = dg.dvdx dgs.dudy = dg.dudy dgs.dvdy = dg.dvdy dgs.dpdx = dg.dpdx dgs.dpdy = dg.dpdy return dgs
def intersect_p(self, r: 'Ray') -> bool: """ Determine whether intersects using Barycentric coordinates """ # compute s1 p1 = self.mesh.p[self.mesh.vertexIndex[self.v]] p2 = self.mesh.p[self.mesh.vertexIndex[self.v + 1]] p3 = self.mesh.p[self.mesh.vertexIndex[self.v + 2]] e1 = p2 - p1 # geo.Vector e2 = p3 - p1 s1 = r.d.cross(e2) div = s1.dot(e1) if div == 0.: return False divInv = 1. / div # compute barycentric coordinate ## first one d = r.o - p1 b1 = d.dot(s1) * divInv if b1 < 0. or b1 > 1.: return False ## second one s2 = d.cross(e1) b2 = r.d.dot(s2) * divInv if b2 < 0. or (b1 + b2) > 1.: return False # compute intersection t = e2.dot(s2) * divInv if t < r.mint or t > r.maxt: return False # compute partial derivatives uvs = self.get_uvs() du1 = uvs[0][0] - uvs[2][0] du2 = uvs[1][0] - uvs[2][0] dv1 = uvs[0][1] - uvs[2][1] dv2 = uvs[1][1] - uvs[2][1] dp1 = p1 - p3 dp2 = p2 - p3 det = du1 * dv2 - du2 * dv1 if det == 0.: # choose an arbitrary system _, dpdu, dpdv = geo.coordinate_system(geo.normalize(e2.cross(e1))) else: detInv = 1. / det dpdu = (dv2 * dp1 - dv1 * dp2) * detInv dpdv = (-du2 * dp1 + du1 * dp2) * detInv # interpolate triangle parametric coord. b0 = 1. - b1 - b2 tu = b0 * uvs[0][0] + b1 * uvs[1][0] + b2 * uvs[2][0] tv = b0 * uvs[0][1] + b1 * uvs[1][1] + b2 * uvs[2][1] # test alpha texture dg = geo.DifferentialGeometry(r(t), dpdu, dpdv, geo.Normal(0., 0., 0.), geo.Normal(0., 0., 0.), tu, tv, self) if self.mesh.alphaTexture is not None: # alpha mask presents if self.mesh.alphaTexture.evaluate(dg) == 0.: return False # have a hit return True