def generateSample(self, intersection, scene, camera, ray, depth=0):
        sample = Irradiance_Sample(intersection.pos, intersection.n)
        minSamples = 64
        l = np.array([0., 0., 0.])

        # generate a sample for irradiance
        if (depth > 0):
            numSample = np.max(
                [minSamples, self.directLightSampleCount / (2**depth)])
            for i in range(int(numSample)):
                h2Vec = self.getCosineWeightedPointH2()
                d = self.transformH2toR3(h2Vec, intersection.n)
                r = Ray(intersection.pos + 0.001 * intersection.n, d)
                ni = Intersection()
                if (scene.intersect(r, ni)):
                    if r.t <= 0:
                        print(
                            "\033[34mWARNING\033[30m: ray intersects with t <= 0"
                        )
                    if (sample.minHitDist > r.t):
                        sample.minHitDist = r.t
                    procData = Irradiance_ProcessData(ni.pos, ni.n,
                                                      self.minWeight,
                                                      self.maxCosAngleDiff)
                    intVal = self.getInterpolatedValue(procData, depth - 1)
                    # if interpolation is successful
                    if (intVal >= 0).all():
                        lightval = ni.color * intVal * ni.BSDF(
                            procData.avgLightDir, d, ni.n)
                        """
                        if lightval < 0:
                            print("\033[34mWARNING\033[30m: Interpolation lead to a negative light value")
                        """

                        l += lightval
                        sample.avgLightDir += d * np.linalg.norm(lightval)
                        v = self.transformH2toR3(
                            np.array([
                                h2Vec[0], (h2Vec[1] - np.pi / 4) % (2 * np.pi)
                            ]), intersection.n)
                        sample.rotGrad += -v * np.tan(
                            h2Vec[0]) * np.linalg.norm(lightval)
                    # else make a new sample und use its irradiance
                    else:
                        s = self.generateSample(ni, scene, camera, r,
                                                depth - 1)
                        lightval = ni.color * s.irradiance * ni.BSDF(
                            s.avgLightDir, d, ni.n)
                        if (lightval < 0).any():
                            print(
                                "\033[31mERROR\033[30m: Generating a new sample lead to a negative light value"
                            )
                        l += lightval
                        sample.avgLightDir += d * np.linalg.norm(
                            lightval)  #s.avgLightDir
                        v = self.transformH2toR3(
                            np.array([
                                h2Vec[0], (h2Vec[1] - np.pi / 4) % (2 * np.pi)
                            ]), intersection.n)
                        sample.rotGrad += -v * np.tan(
                            h2Vec[0]) * np.linalg.norm(lightval)

                    if np.dot(sample.avgLightDir, sample.normal) < 0:
                        print(
                            "\033[34mWARNING\033[30m: The average Light direction points temporally in the wrong half space"
                        )

            l *= np.pi / numSample
            sample.rotGrad *= np.pi / numSample
        else:
            # generate a sample for direct light
            l = self.MonteCarlo(intersection, scene,
                                self.directLightSampleCount,
                                sample)  #+ intersection.ell

        pixelSpacing = self.computeIntersectionPixelSpacing(
            camera, ray, intersection)
        sample.irradiance = l
        sample.computeSampleMaxContribution(self.minPixelDist,
                                            self.maxPixelDist, pixelSpacing)
        norm = np.linalg.norm(sample.avgLightDir)
        if (norm > 0):
            sample.avgLightDir /= norm

        if ((self.showSamples) & (depth == self.maxBounceDepth)):
            camera.imageDepths[-1][ray.pixel[0],
                                   ray.pixel[1], :] = [1., 0., 0.]

        if np.dot(sample.avgLightDir, sample.normal) < 0:
            print(
                "\033[31mERROR\033[30m: Sample was generated with avgLightDir pointing in the wrong half space"
            )

        self.cache[depth].addObj(sample)
        return sample
    def ell(self, scene, ray, camera):
        #very first call for ell() means the cache has to be filled
        cameraImageCount = self.maxBounceDepth + 1 + (1 if self.showSamples
                                                      else 0)
        if len(camera.imageDepths) < cameraImageCount:
            for i in range(len(camera.imageDepths), cameraImageCount):
                camera.addImage()

        if ((ray.pixel[0] == 0) & (ray.pixel[1] == 0)):
            start = time.perf_counter()
            if self.completelyFillCache:
                self.FillCacheComplete(camera, scene)
            else:
                self.fillCache(camera, scene)
            end = time.perf_counter()
            s = end - start
            m = int(s / 60)
            s = s % 60
            h = int(m / 60)
            m = m % 60

            print("filling cache took:", h, "h ", m, "min ", s, "s")
            for i in range(len(self.cache)):
                print("In cache depth: ", i, "are ", self.cache[i].objCount,
                      "samples")

        intersection = Intersection()
        val = np.array([0.0, 0., 0.])
        if (scene.intersect(ray, intersection)):
            #interpolate indirect light
            for i in range(self.maxBounceDepth, 0, -1):
                interpolatedPoint = Irradiance_ProcessData(
                    intersection.pos, intersection.n, self.minWeight,
                    self.maxCosAngleDiff)
                interpval = self.getInterpolatedValue(interpolatedPoint, i)
                e = 0
                if (interpval >= 0).all():
                    e += interpval * intersection.BSDF(
                        interpolatedPoint.avgLightDir, -ray.d, intersection.n)

                #if interpolation failed, compute new sample
                else:
                    #print("new sample generated at ", ray.pixel)
                    s = self.generateSample(intersection, scene, camera, ray,
                                            i)
                    e += s.irradiance * intersection.BSDF(
                        s.avgLightDir, -ray.d, s.normal)

                if (e < 0).any():
                    print(e)

                camera.imageDepths[i][ray.pixel[0], ray.pixel[1], :] = e
                val += e

            #compute direct light
            if self.renderDirectLight:
                sample = Irradiance_Sample(intersection.pos, intersection.n)
                self.MonteCarlo(intersection,
                                scene,
                                sampleCount=self.directLightSampleCount,
                                sample=sample)

                camera.imageDepths[0][
                    ray.pixel[0], ray.pixel[1], :] = intersection.color * (
                        sample.irradiance * intersection.BSDF(
                            sample.avgLightDir, -ray.d, intersection.n) +
                        intersection.ell)

                val += sample.irradiance * intersection.BSDF(
                    sample.avgLightDir, -ray.d, intersection.n)
                val += intersection.ell
            if (val < 0).any():
                print("light value is negative :", val)

        return val * intersection.color