Пример #1
0
class UniformGrid(Aggregate):
    def __init__(self, primitives: [Primitive], refine_immediately: bool):
        super().__init__()
        self.voxels = []
        self.bounds = BoundingBox()
        self.width = Vector3d()
        self.inv_width = Vector3d()
        self.nVoxels = [int] * 3
        self.primitives = []

        # Initialize _primitives_ with primitives for grid
        if refine_immediately:
            for p in primitives:
                p.get_fully_refine(self.primitives)
        else:
            self.primitives = primitives

        # Compute bounds and choose grid resolution
        for p in self.primitives:
            self.bounds = BoundingBox.get_union_bbox(self.bounds, p.get_world_bound())
        delta = self.bounds.point_max - self.bounds.point_min

        #Find _voxelsPerUnitDist_ for grid
        maxAxis = self.bounds.get_maximum_extent()
        invMaxWidth = 1.0 / delta[maxAxis]
        assert (invMaxWidth > 0.0)
        cubeRoot = 3.0 * pow(float(len(self.primitives)), 1.0 / 3.0)
        voxelsPerUnitDist = cubeRoot * invMaxWidth
        for axis in range(3):
            self.nVoxels[axis] = int(math.floor(delta[axis] * voxelsPerUnitDist))
            self.nVoxels[axis] = maths.tools.get_clamp(self.nVoxels[axis], 1, 64)

        # Compute voxel widths and allocate voxels
        for axis in range(3):
            self.width[axis] = delta[axis] / self.nVoxels[axis]
            if self.width[axis] == 0.0:
                self.inv_width[axis] = 0.0
            else:
                self.inv_width[axis] = 1.0 / self.width[axis]

        nv = self.nVoxels[0] * self.nVoxels[1] * self.nVoxels[2]
        self.voxels = [None] * nv

        #Add primitives to grid voxels
        for p in self.primitives:
            # Find voxel extent of primitive
            pb = p.get_world_bound()
            vmin = [int] * 3
            vmax = [int] * 3
            for axis in range(3):
                vmin[axis] = self.get_pos_to_voxel(pb.point_min, axis)
                vmax[axis] = self.get_pos_to_voxel(pb.point_max, axis)

            # Add primitive to overlapping voxels
            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.get_offset(x, y, z)
                        if self.voxels[o] is None:
                            # Allocate new voxel and store primitive in it
                            self.voxels[o] = Voxel(p)
                        else:
                            # Add primitive to already-allocated voxel
                            self.voxels[o].add_primitive(p)

    def get_world_bound(self):
        return self.bounds

    def get_can_intersect(self):
        return True

    def get_is_intersected(self, ray: Ray) -> bool:

        # Check ray against overall grid bounds
        if self.bounds.get_is_point_inside(ray.get_at(ray.min_t)):
            rayT = ray.min_t
        else:
            rayT, tmp = self.bounds.get_intersect(ray)
            if rayT is None:
                return False

        gridIntersect = ray.get_at(rayT)

        # Set up 3D DDA for ray
        NextCrossingT = [float] * 3
        DeltaT = [float] * 3
        Step = [int] * 3
        Out = [int] * 3
        Pos = [int] * 3
        for axis in range(3):
            # Compute current voxel for axis
            Pos[axis] = self.get_pos_to_voxel(gridIntersect, axis)
            if ray.direction[axis] >= 0:
                # Handle ray with positive direction for voxel stepping
                NextCrossingT[axis] = rayT + (self.get_voxel_to_pos(Pos[axis] + 1, axis) - gridIntersect[axis]) / \
                                             ray.direction[axis]
                DeltaT[axis] = self.width[axis] / ray.direction[axis]
                Step[axis] = 1
                Out[axis] = self.nVoxels[axis]
            else:
                # Handle ray with negative direction for voxel stepping
                NextCrossingT[axis] = rayT + (self.get_voxel_to_pos(Pos[axis], axis) - gridIntersect[axis]) / \
                                             ray.direction[axis]
                DeltaT[axis] = -self.width[axis] / ray.direction[axis]
                Step[axis] = -1
                Out[axis] = -1

        #Walk grid for shadow ray
        while True:
            o = self.get_offset(Pos[0], Pos[1], Pos[2])
            voxel = self.voxels[o]
            if voxel is not None:
                if voxel.get_is_intersected(ray):
                    return True
            # Advance to next voxel

            # Find _stepAxis_ for stepping to next voxel
            bits = ((NextCrossingT[0] < NextCrossingT[1]) << 2) + ((NextCrossingT[0] < NextCrossingT[2]) << 1) + (
            (NextCrossingT[1] < NextCrossingT[2]))
            cmpToAxis = [2, 1, 2, 1, 2, 2, 0, 0]
            stepAxis = cmpToAxis[bits]
            if ray.max_t < NextCrossingT[stepAxis]:
                break
            Pos[stepAxis] += Step[stepAxis]
            if Pos[stepAxis] == Out[stepAxis]:
                break
            NextCrossingT[stepAxis] += DeltaT[stepAxis]
        return False

    def get_intersection(self, ray: Ray, intersection: Intersection) -> bool:
        # Check ray against overall grid bounds
        if self.bounds.get_is_point_inside(ray.get_at(ray.min_t)):
            rayT = ray.min_t
        else:
            rayT, tmp = self.bounds.get_intersect(ray)
            if rayT is None:
                return False
        gridIntersect = ray.get_at(rayT)

        # Set up 3D DDA for ray
        NextCrossingT = [float] * 3
        DeltaT = [float] * 3
        Step = [int] * 3
        Out = [int] * 3
        Pos = [int] * 3
        for axis in range(3):
            # Compute current voxel for axis
            Pos[axis] = self.get_pos_to_voxel(gridIntersect, axis)
            if ray.direction[axis] >= 0:
                # Handle ray with positive direction for voxel stepping
                NextCrossingT[axis] = rayT + (self.get_voxel_to_pos(Pos[axis] + 1, axis) - gridIntersect[axis]) / \
                                             ray.direction[axis]
                DeltaT[axis] = self.width[axis] / ray.direction[axis]
                Step[axis] = 1
                Out[axis] = self.nVoxels[axis]
            else:
                # Handle ray with negative direction for voxel stepping
                NextCrossingT[axis] = rayT + (self.get_voxel_to_pos(Pos[axis], axis) - gridIntersect[axis]) / \
                                             ray.direction[axis]
                DeltaT[axis] = -self.width[axis] / ray.direction[axis]
                Step[axis] = -1
                Out[axis] = -1

        # Walk ray through voxel grid
        hitSomething = False
        while True:
            # Check for intersection in current voxel and advance to next
            voxel = self.voxels[self.get_offset(Pos[0], Pos[1], Pos[2])]
            if voxel is not None:
                hitSomething |= voxel.get_intersection(ray, intersection)

            # Advance to next voxel

            # Find _stepAxis_ for stepping to next voxel
            bits = ((NextCrossingT[0] < NextCrossingT[1]) << 2) + ((NextCrossingT[0] < NextCrossingT[2]) << 1) + (
            (NextCrossingT[1] < NextCrossingT[2]))
            cmpToAxis = [2, 1, 2, 1, 2, 2, 0, 0]
            stepAxis = cmpToAxis[bits]
            if ray.max_t < NextCrossingT[stepAxis]:
                break
            Pos[stepAxis] += Step[stepAxis]
            if Pos[stepAxis] == Out[stepAxis]:
                break
            NextCrossingT[stepAxis] += DeltaT[stepAxis]
        return hitSomething

    def get_pos_to_voxel(self, point: Point3d, axis: int) -> int:
        v = int((point[axis] - self.bounds.point_min[axis]) * self.inv_width[axis])
        return get_clamp(v, 0, self.nVoxels[axis] - 1)

    def get_voxel_to_pos(self, p: int, axis: int) -> float:
        return self.bounds.point_min[axis] + p * self.width[axis]

    def get_offset(self, x: int, y: int, z: int) -> int:
        return z * self.nVoxels[0] * self.nVoxels[1] + y * self.nVoxels[0] + x