def test_bbox_intersect(self): b = geo.BBox(geo.Point(1., 1., 1.), geo.Point(2., 2., 2.)) ray = geo.Ray(geo.Point(0., 0., 0.), geo.Vector(1., 1., 1.)) hit, t1, t2 = b.intersect_p(ray) assert hit assert_almost_eq(t1, 1.) assert_almost_eq(t2, 2.) # ambiguity in this case ray = geo.Ray(geo.Point(1. - EPS, 1. - EPS, 0.), geo.Vector(-EPS, -EPS, 1.)) hit, t1, t2 = b.intersect_p(ray) assert not hit assert_almost_eq(t1, 0.) assert_almost_eq(t2, 0.) ray = geo.Ray(geo.Point(1. - EPS, 1. - EPS, 1. - EPS), geo.Vector(-1., -1., -1.)) hit, t1, t2 = b.intersect_p(ray) assert not hit assert_almost_eq(t1, 0.) assert_almost_eq(t2, 0.) ray = geo.Ray(geo.Point(0., 0., 0.), geo.Vector(-1., -1., -1.)) hit, t1, t2 = b.intersect_p(ray) assert not hit assert_almost_eq(t1, 0.) assert_almost_eq(t2, 0.)
def __init__(self, c2w: 'trans.AnimatedTransform', scr_win: [FLOAT], s_open: FLOAT, s_close: FLOAT, lensr: FLOAT, focald: FLOAT, f: 'Film'): super().__init__(c2w, trans.Transform.orthographic(0., 1.), scr_win, s_open, s_close, lensr, focald, f) # compute differential changes in origin self.dxCam = self.r2c(geo.Vector(1., 0., 0.)) self.dyCam = self.r2c(geo.Vector(0., 1., 0.))
def __init__(self, p: 'np.ndarray', refine_imm: bool): super().__init__() if refine_imm: self.primitives = [] for prim in p: prim.full_refine(self.primitives) else: self.primitives = p # compute bounds and choose grid resolution self.bounds = geo.BBox() self.n_voxels = np.full(3, 0.) for prim in self.primitives: self.bounds.union(prim.world_bound()) delta = self.bounds.pMax - self.bounds.pMin max_axis = self.bounds.maximum_extent() inv_max_width = 1. / delta[max_axis] cube_root = 3. * np.power(len(self.primitives), 1. / 3.) voxels_per_unit_dist = cube_root * inv_max_width self.width = geo.Vector() self.invWidth = geo.Vector() for axis in range(3): self.n_voxels[axis] = INT(delta[axis] * voxels_per_unit_dist) self.n_voxels[axis] = np.clip(self.n_voxels[axis], 1., 64.) self.width[axis] = delta[axis] / self.n_voxels[axis] self.invWidth[ axis] = 0. if self.width[axis] == 0. else 1. / self.width[axis] nv = np.prod(self.n_voxels).astype(INT) self.voxels = np.full(nv, None) # add primitives to voxels for prim in self.primitives: pb = prim.world_bound() vmin = [self.pos2voxel(pb.pMin, axis) for axis in range(3)] vmax = [self.pos2voxel(pb.pMax, axis) for axis in range(3)] for z in range(vmin[2], vmax[2] + 1): for y in range(vmin[1], vmax[1] + 1): for x in range(vmin[0], vmax[0] + 1): o = self.offset(x, y, z) if self.voxels[o] is None: # new voxel self.voxels[o] = Voxel([prim]) else: # add primitive self.voxels[o].add_primitive(prim) # create mutex for grid self.lock = threading.Lock()
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 decompose( m: 'np.ndarray' ) -> ['geo.Vector', 'quat.Quaternion', 'np.ndarray']: """ decompose into m = T R S Assume m is an affine transformation """ if not np.shape(m) == (4, 4): raise TypeError T = geo.Vector(m[0, 3], m[1, 3], m[2, 3]) M = m.copy() M[0:3, 3] = M[3, 0:3] = 0 M[3, 3] = 1 # polar decomposition norm = 2 * EPS R = M.copy() for _ in range(100): if norm < EPS: break Rit = np.linalg.inv(R.T) Rnext = .5 * (Rit + R) D = np.fabs(Rnext - Rit)[0:3, 0:3] norm = max(norm, np.max(np.sum(D, axis=0))) R = Rnext from pytracer.transform.quat import from_arr Rquat = from_arr(R) S = np.linalg.inv(R).dot(M) return T, Rquat, S
def sample_l(self, p: 'geo.Point', pEps: FLOAT, ls: 'LightSample', time: FLOAT,) -> ['Spectrum', 'geo.Vector', FLOAT, 'VisibilityTester']: # find (u, v) sample coords in inf. light texture uv, pdf = self.dist.sample_cont(ls.u_pos[0], ls.u_pos[1]) # convert inf light sample pnt to direction theta = uv[1] * PI phi = uv[0] * 2. * PI ct = np.cos(theta) st = np.sin(theta) sp = np.sin(phi) cp = np.cos(phi) wi = self.l2w(geo.Vector(st * cp, st * sp, ct)) # compute pdf for sampled inf light direction if st == 0.: pdf = 0. pdf = pdf / (2. * PI * PI * st) # return radiance value vis = VisibilityTester() vis.set_ray(p, pEps, wi, time) return [Spectrum.from_rgb(self.radMap.look_up([uv[0], uv[1]]), SpectrumType.ILLUMINANT), wi, pdf, vis]
def test_ray_differential_scale(self, vec, pnt): ray = geo.RayDifferential(pnt, vec) ray.rxOrigin = ray.ryOrigin = pnt ray.rxDirection = vec + geo.Vector(rng(), rng(), rng()) ray.ryDirection = vec + geo.Vector(rng(), rng(), rng()) s = rng() r = geo.RayDifferential.from_rd(ray) ray.scale_differential(s) assert ray.rxOrigin == r.o + (r.rxOrigin - r.o) * s assert ray.ryOrigin == r.o + (r.ryOrigin - r.o) * s assert ray.rxDirection == r.d + (r.rxDirection - r.d) * s assert ray.ryDirection == r.d + (r.ryDirection - r.d) * s assert isinstance(ray.rxOrigin, geo.Point) assert isinstance(ray.ryOrigin, geo.Point) assert isinstance(ray.rxDirection, geo.Vector) assert isinstance(ray.ryDirection, geo.Vector)
def generate_ray(self, sample: 'CameraSample') -> [FLOAT, 'geo.Ray']: """ Generate ray based on image sample. Returned ray direction is normalized """ # generate raster and camera samples Pras = geo.Point(sample.imageX, sample.imageY, 0.) Pcam = self.r2c(Pras) ray = geo.Ray(Pcam, geo.Vector(0., 0., 1.), 0., np.inf) # modify ray for dof if self.lens_rad > 0.: # sample point on lens from pytracer.montecarlo import concentric_sample_disk lens_u, lens_v = \ concentric_sample_disk(sample.lens_u, sample.lens_v) lens_u *= self.lens_rad lens_v *= self.lens_rad # compute point on focal plane ft = self.focal_dist / ray.d.z Pfoc = ray(ft) # update ray ray.o = geo.Point(lens_u, lens_v, 0.) ray.d = geo.normalize(Pfoc - ray.o) ray.time = util.lerp(sample.time, self.s_open, self.s_close) ray = self.c2w(ray) return [1., ray]
def w2l(self, v: 'geo.Vector') -> 'geo.Vector': """ w2l() Transform a `geo.Vector` in world in the local surface normal coord. system """ return geo.Vector(v.dot(self.sn), v.dot(self.tn), v.dot(self.nn))
def test_rotate_x(self, x_axis, y_axis, z_axis): t = Transform.rotate_x(45) assert_elem_eq(t(x_axis), x_axis) v = t(y_axis) assert_elem_eq(v, geo.Vector(0., np.sqrt(2) / 2., np.sqrt(2) / 2.)) vv = t(v) assert_elem_eq(vv, z_axis) vv = t.inverse()(v) assert_elem_eq(vv, y_axis)
def sample_f(self, wo: 'geo.Vector', u1: FLOAT, u2: FLOAT) -> [FLOAT, 'geo.Vector', 'Spectrum']: # find direction wi = geo.Vector(-wo.x, -wo.y, wo.z) return [ 1., wi, self.fresnel(cos_theta(wo)) * self.R / abs_cos_theta(wi) ] # 1. suggests no MC samples needed
def l2w(self, v: 'geo.Vector') -> 'geo.Vector': """ l2w() Transform a `geo.Vector` in the local system to the world system """ return geo.Vector(self.sn.x * v.x + self.tn.x * v.x + self.nn.x * v.x, self.sn.y * v.y + self.tn.y * v.y + self.nn.y * v.y, self.sn.z * v.z + self.tn.z * v.z + self.nn.z * v.z)
def uniform_sample_hemisphere(u1: FLOAT, u2: FLOAT) -> 'geo.Vector': """ uniform_sample_hemisphere() Sampling unifromly on a unit hemishpere. `u1` and `u2` are two random numbers passed in. """ r = np.sqrt(max(0., 1. - u1 * u1)) phi = 2. * PI * u2 return geo.Vector(r * np.cos(phi), r * np.sin(phi), u1)
def test_rotate_bbox(self): t = Transform.rotate(180., geo.Vector(1., 1., 1.)) p1 = geo.Point(0., 0., 0.) p2 = geo.Point(1., 1., 1.) box = geo.BBox(p1, p2) b = t(box) pmax = t(box.pMax) pmin = t(box.pMin) assert_elem_eq(b.pMin, pmin) assert_elem_eq(b.pMax, pmax) bb = t(b) pmax = t(pmax) pmin = t(pmin) assert_elem_eq(bb.pMin, pmin) assert_elem_eq(bb.pMax, pmax)
def __init__(self, c2w: 'trans.AnimatedTransform', proj: 'trans.Transform', scr_win: [FLOAT], s_open: FLOAT, s_close: FLOAT, lensr: FLOAT, focald: FLOAT, f: 'Film'): super().__init__(c2w, s_open, s_close, f) # set dof prarms self.lens_rad = lensr self.focal_dist = focald # compute transfomations self.c2s = proj ## compute projective screen transfomations s2r = trans.Transform.scale(f.xResolution, f.yResolution, 1.) * \ trans.Transform.scale(1. / (scr_win[1] - scr_win[0]), 1. / (scr_win[2] - scr_win[3]), 1.) * \ trans.Transform.translate(geo.Vector(-scr_win[0], -scr_win[3], 0.)) # upper-left corner to origin r2s = s2r.inverse() self.r2c = self.c2s.inverse() * r2s
def generate_ray(self, sample: 'CameraSample') -> [FLOAT, 'geo.Ray']: """ Generate ray based on image sample. Returned ray direction is normalized """ time = util.lerp(sample.time, self.s_open, self.s_close) # compute ray direction theta = np.pi * sample.imageY / self.film.yResolution phi = 2 * np.pi * sample.imageX / self.film.xResolution stheta = np.sin(theta) ray = self.c2w( geo.Ray( geo.Point(0., 0., 0.), geo.Vector(stheta * np.cos(phi), np.cos(theta), stheta * np.sin(phi)), 0., np.inf, time)) return [1., ray]
def test_rotate(self, x_axis, y_axis, z_axis): t = Transform.rotate(45., x_axis) assert_elem_eq(t(x_axis), x_axis) v = t(y_axis) assert_elem_eq(v, geo.Vector(0., np.sqrt(2) / 2., np.sqrt(2) / 2.)) vv = t(v) assert_elem_eq(vv, z_axis) vv = t.inverse()(v) assert_elem_eq(vv, y_axis) t = Transform.rotate(30., y_axis) assert_elem_eq(t(y_axis), y_axis) p = geo.Point.from_arr(z_axis) pp = t(t(t(p))) assert_elem_eq(pp, x_axis) t = Transform.rotate(90., z_axis) assert_elem_eq(t(z_axis), z_axis) n = geo.Normal.from_arr(y_axis) nn = t(t(t(n))) assert_elem_eq(nn, x_axis)
def uniform_sample_cone(u1: FLOAT, u2: FLOAT, ct_max: FLOAT, x: 'geo.Vector' = None, y: 'geo.Vector' = None, z: 'geo.Vector' = None) -> 'geo.Vector': """ uniform_sample_cone() Sample from a uniform distribution over the cone of directions. """ if x is None or y is None or z is None: ct = (1. - u1) + u1 * ct_max st = np.sqrt(1. - ct * ct) phi = u2 * 2. * PI return geo.Vector(np.scos(phi) * st, np.sin(phi) * st, ct) else: ct = util.lerp(u1, ct_max, 1.) st = np.sqrt(1. - ct * ct) phi = u2 * 2. * PI return np.cos(phi) * st * x + np.sin(phi) * st * y + ct * z
def sample_r(self, scene: 'Scene', ls: 'LightSample', u1: FLOAT, u2: FLOAT, time: FLOAT) -> ['geo.Ray', 'geo.Normal', FLOAT, 'Spectrum']: """ Create a bounding disk and uniformly sample on it. """ # find (u, v) sample coords in inf. light texture uv, pdf = self.dist.sample_cont(ls.u_pos[0], ls.u_pos[1]) if pdf == 0.: return [None, None, 0., Spectrum(0.)] theta = uv[1] * PI phi = uv[0] * 2. * PI ct = np.cos(theta) st = np.sin(theta) sp = np.sin(phi) cp = np.cos(phi) d = -self.l2w(geo.Vector(st * cp, st * sp, ct)) Ns = geo.Normal.fromVector(d) # choose point on disk oriented towards light ctr, rad = scene.world_bound().bounding_sphere() _, v1, v2 = geo.coordinate_system(self.di) d1, d2 = mc.concentric_sample_disk(ls.u_pos[0], ls.u_pos[1]) pnt = ctr + rad * (d1 * v1 + d2 * v2) # set ray ray = geo.Ray(pnt + rad * (-d), d, 0., np.inf, time) # compute pdf dir_pdf = pdf / (2. * PI * PI * st) area_pdf = 1. / (PI * rad * rad) pdf = dir_pdf * area_pdf if st == 0.: pdf == 0. return [ray, Ns, pdf, Spectrum.from_rgb(self.radMap.look_up([uv[0], uv[1]]), SpectrumType.ILLUMINANT)]
def sample_f(self, wo: 'geo.Vector', u1: FLOAT, u2: FLOAT) -> [FLOAT, 'geo.Vector', 'Spectrum']: # find eta pair ei, et = self.ei, self.et if cos_theta(wo) > 0.: ei, et = et, ei # compute transmited ray direction si_sq = sin_theta_sq(wo) eta = ei / et st_sq = eta * eta * si_sq if st_sq >= 1.: return [0., None, None] ct = np.sqrt(max(0., 1. - st_sq)) if cos_theta(wo) > 0.: ct = -ct wi = geo.Vector(eta * (-wo.x), eta * (-wo.y), ct) F = self.fresnel(cos_theta(wo)) return [1., wi, (et * et) / (ei * ei) * (Spectrum(1.) - F) * \ self.T / abs_cos_theta(wi)] # 1. suggests no MC samples needed
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
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) # solve quad eqn A = ray.d.sq_length() B = 2 * ray.d.dot(ray.o) C = ray.o.sq_length() - self.radius * self.radius D = B * B - 4. * A * C if D <= 0.: return [False, None, None, None] # validate solutions [t0, t1] = np.roots([A, B, C]) if t0 > t1: t0, t1 = t1, t0 if t0 > ray.maxt or t1 < ray.mint: return [False, None, None, None] thit = t0 if t0 < ray.mint: thit = t1 if thit > ray.maxt: return [False, None, None, None] # sphere hit position phit = ray(thit) if phit.x == 0. and phit.y == 0.: phit.x = EPS * self.radius phi = np.arctan2(phit.y, phit.x) if phi < 0.: phi += 2. * np.pi # test intersection against clipping params if (self.zmin > -self.radius and phit.z < self.zmin) or \ (self.zmax < self.radius and phit.z > self.zmax) or \ phi > self.phiMax: if thit == t1 or t1 > ray.maxt: return [False, None, None, None] # try again with t1 thit = t1 phit = ray(thit) if phit.x == 0. and phit.y == 0.: phit.x = EPS * self.radius phi = np.arctan2(phit.y, phit.x) if phi < 0.: phi += 2. * np.pi if (self.zmin > -self.radius and phit.z < self.zmin) or \ (self.zmax < self.radius and phit.z > self.zmax) or \ phi > self.phiMax: return [False, None, None, None] # otherwise ray hits the sphere # initialize the differential structure u = phi / self.phiMax theta = np.arccos(np.clip(phit.z / self.radius, -1., 1.)) delta_theta = self.thetaMax - self.thetaMin v = (theta - self.thetaMin) * delta_theta # find derivatives dpdu = geo.Vector(-self.phiMax * phit.y, self.phiMax * phit.x, 0.) zrad = np.sqrt(phit.x * phit.x + phit.y * phit.y) inv_zrad = 1. / zrad cphi = phit.x * inv_zrad sphi = phit.y * inv_zrad dpdv = delta_theta \ * geo.Vector(phit.z * cphi, phit.z * sphi, -self.radius * np.sin(theta)) # derivative of Normals # given by Weingarten Eqn d2pduu = -self.phiMax * self.phiMax * geo.Vector(phit.x, phit.y, 0.) d2pduv = delta_theta * phit.z * self.phiMax * geo.Vector(-sphi, cphi, 0.) d2pdvv = -delta_theta * delta_theta * geo.Vector(phit.x, phit.y, phit.z) # fundamental forms E = dpdu.dot(dpdu) F = dpdu.dot(dpdv) G = dpdv.dot(dpdv) N = geo.normalize(dpdu.cross(dpdv)) e = N.dot(d2pduu) f = N.dot(d2pduv) g = N.dot(d2pdvv) invEGFF = 1. / (E * G - F * F) dndu = geo.Normal.from_arr((f * F - e * G) * invEGFF * dpdu + (e * F - f * E) * invEGFF * dpdv) dndv = geo.Normal.from_arr((g * F - f * G) * invEGFF * dpdu + (f * F - g * E) * invEGFF * dpdv) 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
def switch(w: 'geo.Vector'): return geo.Vector(w.x, w.y, -w.z)
""" from __future__ import absolute_import import numpy as np import pytest from pytracer import pyEPS as EPS import pytracer.geometry as geo N_TEST_CASE = 5 VAR = 10. np.random.seed(1) rng = np.random.rand testdata = { '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)], 'theta': [np.random.uniform(0., np.pi) for _ in range(N_TEST_CASE)], 'phi': [np.random.uniform(0., 2 * np.pi) for _ in range(N_TEST_CASE)], } testdata['vector'].extend([ geo.Vector(0., 0., 0.), geo.Vector(1., 0., 0.), geo.Vector( 0., 1., 0., ),
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) # solve quad eqn A = ray.d.x * ray.d.x + ray.d.y * ray.d.y B = 2 * (ray.d.x * ray.o.x + ray.d.y * ray.o.y) C = ray.o.x * ray.o.x + ray.o.y * ray.o.y - self.radius * self.radius D = B * B - 4. * A * C if D <= 0.: return [False, None, None, None] # validate solutions [t0, t1] = np.roots([A, B, C]) if t0 > t1: t0, t1 = t1, t0 if t0 > ray.maxt or t1 < ray.mint: return [False, None, None, None] thit = t0 if t0 < ray.mint: thit = t1 if thit > ray.maxt: return [False, None, None, None] # cylinder hit position phit = ray(thit) phi = np.arctan2(phit.y, phit.x) if phi < 0.: phi += 2. * np.pi # test intersection against clipping params if phit.z < self.zmin or phit.z > self.zmax or phi > self.phiMax: if thit == t1 or t1 > ray.maxt: return [False, None, None, None] # try again with t1 thit = t1 phit = ray(thit) phi = np.arctan2(phit.y, phit.x) if phi < 0.: phi += 2. * np.pi if phit.z < self.zmin or phit.z > self.zmax or phi > self.phiMax: if thit == t1 or t1 > ray.maxt: return [False, None, None, None] # otherwise ray hits the cylinder # initialize the differential structure u = phi / self.phiMax v = (phit.z - self.zmin) / (self.thetaMax - self.thetaMin) # find derivatives dpdu = geo.Vector(-self.phiMax * phit.y, self.phiMax * phit.x, 0.) dpdv = geo.Vector(0., 0., self.zmax - self.zmin) # derivative of geo.Normals # given by Weingarten Eqn d2pduu = -self.phiMax * self.phiMax * geo.Vector(phit.x, phit.y, 0.) d2pduv = geo.Vector(0., 0., 0.) d2pdvv = geo.Vector(0., 0., 0.) # fundamental forms E = dpdu.dot(dpdu) F = dpdu.dot(dpdv) G = dpdv.dot(dpdv) N = geo.normalize(dpdu.cross(dpdv)) e = N.dot(d2pduu) f = N.dot(d2pduv) g = N.dot(d2pdvv) invEGFF = 1. / (E * G - F * F) dndu = geo.Normal.from_arr((f * F - e * G) * invEGFF * dpdu + (e * F - f * E) * invEGFF * dpdv) dndv = geo.Normal.from_arr((g * F - f * G) * invEGFF * dpdu + (f * F - g * E) * invEGFF * dpdv) 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 numpy.testing import assert_array_almost_equal import pytest 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.,
def orthographic(cls, znear: FLOAT, zfar: FLOAT): return cls.scale(1., 1., 1. / (zfar - znear)) * cls.translate( geo.Vector(0., 0., -znear))