public override bool getSample(RayContext rayContext, double ru, double rv, out Vector3 wi, out double invPdf)
        {
            Vector3 viewDir = rayContext.ray.dir;
            Vector3 normal = rayContext.hitData.hitNormal;

            Vector3 reflectDir = viewDir - (normal * (normal * viewDir) * 2.0);
            reflectDir.normalize();

            Matrix3 shadingMatrix = new Matrix3();
            shadingMatrix.computeTangentBasis(reflectDir);

            double pdf = 1;
            invPdf = 1;

            wi = MathUtils.getSpecularDir(ru, rv, glossy, out pdf);

            wi = shadingMatrix.transformVector(wi);
            wi.normalize();

            if (pdf < 0.0001)
                return false;

            invPdf = 1.0 / pdf;

            double cosT = rayContext.hitData.hitNormal * wi;
            if (cosT < 0.0)
                return false;

            return true;
        }
        public bool trace(RayContext rayContext)
        {
            double maxt = rayContext.ray.maxt;
            bool haveIntersection = false;

            foreach (GeomPrimitive primitive in m_primitives)
            {

                if (rayContext.ignorePrimitive == primitive)
                    continue; //skip this primitive

                IntersectionData hitData = new IntersectionData();

                if (primitive.intersect(rayContext.ray, hitData))
                {

                    if (maxt > hitData.hitT && hitData.hitT > rayContext.ray.mint)
                    {
                        maxt = hitData.hitT;

                        rayContext.hitData = hitData;
                        rayContext.hitData.hitPrimitive = primitive;
                        haveIntersection = true;
                    }

                    if (haveIntersection)
                    {
                        rayContext.hitData.hitPos = rayContext.ray.p + rayContext.ray.dir * rayContext.hitData.hitT;
                    }
                }
            }//foreach
            return haveIntersection;
        }
        public Color3 getFilterColor(RayContext rayContext)
        {
            if (null != filterTexture)
                return filterTexture.evalColor(rayContext);

            return filterColor;
        }
        public virtual Color3 eval(RayContext rayContext, Vector3 wi, out double pdf)
        {
            pdf = 1.0;

            if (null != filterTexture)
                return filterTexture.evalColor(rayContext);

            return filterColor;
        }
        public Color3 getTransparency(RayContext rayContext)
        {
            Color3 transparency = filterColor.getWhiteComplement();

            if (null != filterTexture)
                transparency = filterTexture.evalColor(rayContext).getWhiteComplement();

            return transparency;
        }
        public override Color3 eval(RayContext rayContext, Vector3 wi, out double pdf)
        {
            pdf = System.Math.Max( rayContext.hitData.hitNormal * wi, 0.0) * (1.0 / System.Math.PI);

            if (null != filterTexture)
                return filterTexture.evalColor(rayContext);

            return filterColor;
        }
        public static RayContext createNewContext(RayContext previousContext, Ray newRay)
        {
            RayContext newContext = new RayContext(newRay);
            newContext.traceLevel += previousContext.traceLevel + 1;
            newContext.scene = previousContext.scene;
            newContext.ignorePrimitive = previousContext.hitData.hitPrimitive;

            newContext.m_renderer = previousContext.m_renderer;
            newContext.ignoreID = previousContext.ignoreID;

            newContext.m_rndContext = previousContext.m_rndContext.createNew(previousContext.randomIndex, 2);
            return newContext;
        }
        public override bool getSample(RayContext rayContext, double ru, double rv, out Vector3 wi, out double invPdf)
        {
            //в каква посока гледа наблюдателят (входящ лъч)
            Vector3 viewDir = rayContext.ray.dir;
            //faceforward се грижи нормалата винаги да е от страната на входящия лъч
            Vector3 normal = MathUtils.faceforward(rayContext.hitData.hitNormal, viewDir);

            //намираме посоката на отразения лъч (изходящ лъч)
            wi = viewDir - (normal * (normal * viewDir) * 2.0);
            wi.normalize();

            invPdf = 0;
            return true;
        }
        public override Color3 eval(RayContext rayContext, Vector3 wi, out double pdf)
        {
            Vector3 viewDir = rayContext.ray.dir;
            Vector3 normal = rayContext.hitData.hitNormal;
            Vector3 reflectDir = viewDir - (normal * (normal * viewDir) * 2.0);
            reflectDir.normalize();

            double cosA = wi * reflectDir;

            double k = 0.0f;

            if (cosA > 0.0f)
                k = System.Math.Pow(cosA, glossy) * (glossy + 1.0f) * (2.0 / System.Math.PI);

            pdf = k;

            return filterColor *k;
        }
        public override bool getSample(RayContext rayContext, double ru, double rv, out Vector3 wi, out double invPdf)
        {
            Vector3 viewDir = rayContext.ray.dir;
            Vector3 normal = rayContext.hitData.hitNormal;
            double ior = 1.0 / m_ior;

            double thetaView = viewDir * normal;
            double thetaT = 1.0 - (ior * ior) * (1.0 - thetaView * thetaView);

            wi = new Vector3(0, 0, 0);
            invPdf = 0;
            if (thetaT < 0.0f) // проверка за пълно вътрешно отражение
                return false;

            wi = ior * viewDir - (ior * thetaView + System.Math.Sqrt(thetaT)) * normal;

            invPdf = 0;
            return true;
        }
        public Color3 getRadiance(RayContext rayContext)
        {
            Vector3 d = new Vector3();
            d.set( rayContext.ray.dir );

            double len = System.Math.Sqrt(d.x*d.x + d.y*d.y);
            double r = (len<0.0001) ? 0.0f : System.Math.Acos(-d.z)/(2.0f*System.Math.PI*len);
            double u = r*d.x + 0.5f;
            double v = 0.5f - r*d.y;

            int w = m_skyTexture.getWidth(), h = m_skyTexture.getHeight();

            double x = System.Math.Min(System.Math.Max(u, 0), 1);
            double y = System.Math.Min(System.Math.Max(v, 0), 1);

            Color3 c = new Color3();//m_skyTexture.evalColor(x, y);

            //System.Diagnostics.Debug.WriteLine(x.ToString()+" "+y.ToString());
            return c;
        }
 public override LightSample getSample(RayContext rayContext, double ru, double rv)
 {
     //намираме векрор между двете точки
     Vector3 dir = pos - rayContext.hitData.hitPos;
     //определяме каква част от сферата (пространствен ъгъл) покрива точката
     double solidAngle = (1.0 / (4.0 * System.Math.PI * dir.getLenghtSqr()));
     //максимална дистанция за търсене на пресичания
     double maxt = dir.getLenght(); dir.normalize();
     //ако светлината се намира под повърхността от която е точката,
     //няма смисъл да пресмятаме нищо
     if (dir * (rayContext.hitData.hitNormal) <= 0)
         return null;
     //създаваме "сонда за сянка" и попълваме
     LightSample sample = new LightSample();
     sample.shadowRay = new Ray();
     sample.shadowRay.p.set(rayContext.hitData.hitPos);
     sample.shadowRay.dir.set(dir);
     sample.shadowRay.maxt = maxt;
     //задаваме енергията (изпратена през пространствения ъгъл)
     sample.color = color * (power * solidAngle);
     return sample;
 }
        public Color3 illuminate(RayContext rayContext, BRDF brdf)
        {
            Color3 finalColor = new Color3();

            BRDFTypes brdfType = brdf.getType();

            if (brdf.matchTypes(BRDFTypes.Diffuse) || brdf.matchTypes(BRDFTypes.Glossy))
            {
                if (null != m_directLightIntegrator)
                    finalColor += m_directLightIntegrator.illuminate(rayContext, brdf);

                if (brdf.matchTypes(BRDFTypes.Diffuse) && null != m_diffuseIntegrator)
                    finalColor += m_diffuseIntegrator.illuminate(rayContext, brdf);
            }

            if (brdf.matchTypes(BRDFTypes.Specular))
            {
                if (rayContext.traceLevel < m_maxTraceLevel)
                    if (null != m_specularIntegrator)
                        finalColor += m_specularIntegrator.illuminate(rayContext, brdf);
            }

            return finalColor;
        }
 public Color3 evalColor(RayContext rayContext)
 {
     return evalColorBilinear(rayContext.hitData.textureUVW.x, rayContext.hitData.textureUVW.y);
 }
 public override void shade(RayContext rayContext)
 {
     rayContext.resultColor = texture.evalColor(rayContext);
 }
 public abstract void shade(RayContext rayContext);
 //return True if the sample ray is valid and False otherwise
 public abstract bool getSample(RayContext rayContext, double ru, double rv, out Vector3 wi, out double invPdf);
        public static RayContext startFromPixel(Ray startRay, int pixX, int pixY, Scene _scene, IIntegrator integrator)
        {
            RayContext newContext = new RayContext(startRay);
            newContext.scene = _scene;
            newContext.m_renderer = integrator;
            newContext.m_rndContext = new RandomContext(2, 17 * pixX + 73 * pixY);

            return newContext;
        }
        public Color3 shadeBackground(RayContext rayContext)
        {
            if (null != m_skyDome)
                return m_skyDome.getRadiance(rayContext);

            return backgroundColor;
        }
 public bool trace(RayContext rayContext)
 {
     return m_bruteforceTracer.trace( rayContext );
 }
 public static RayContext createNewContextGI(RayContext previousContext, Ray newRay)
 {
     RayContext newContext = createNewContext(previousContext, newRay);
     newContext.traceLevelGI += previousContext.traceLevelGI + 1;
     return newContext;
 }
        public void shade(RayContext rayContext)
        {
            if( trace(rayContext) ) {

                if( null != rayContext.hitData.hitPrimitive ) {
                    rayContext.hitData.hitPrimitive.shader.shade(rayContext);
                    return;
                }

            }
            rayContext.resultColor.set( shadeBackground(rayContext) );
        }
 public bool traceShadowRay(Ray shadowRay, RayContext origContext)
 {
     RayContext rayContext = RayContext.createNewContext(origContext, shadowRay);
     rayContext.ignorePrimitive = origContext.hitData.hitPrimitive;
     rayContext.isShadowRay = true;
     return trace(rayContext);
 }
        public bool trace(RayContext rayContext)
        {
            GeomPrimitive hitObject;
            rayContext.hitData = null;

            GetIntersection(rayContext.ray.p, rayContext.ray.dir, rayContext.ignorePrimitive, out hitObject, rayContext.ray.p, rayContext);

            if (rayContext.hitData == null)
                return false;

            return rayContext.hitData.hasIntersection ;
        }
        public void GetIntersection(
            Vector3 rayOrigin, Vector3 rayDirection, GeomPrimitive lastHit,
            out GeomPrimitive pHitObject, Vector3 pStart, RayContext rayContext)
        {
            //hitPosition = null;
            pHitObject = null;
            // is branch: step through subcells and recurse
            if (isBranch)
            {
                //if (pStart == null)
                //    pStart = rayOrigin;

                // find which subcell holds ray origin (ray origin is inside cell)
                int subCell = 0;
                for (int i = 3; i-- > 0; )
                {
                    // compare dimension with center
                    if (pStart[i] >= ((bound[i] + bound[i + 3]) * 0.5f))
                        subCell |= 1 << i;
                }

                // step through intersected subcells
                Vector3 cellPosition = new Vector3(pStart);
                for (; ; )
                {
                    if (null != spatial[subCell])
                    {
                        // intersect subcell
                        spatial[subCell].GetIntersection(rayOrigin, rayDirection, lastHit, out pHitObject, cellPosition, rayContext);
                        // exit if item hit
                        if (null != pHitObject)
                            break;
                    }

                    // find next subcell ray moves to
                    // (by finding which face of the corner ahead is crossed first)
                    int axis = 2;
                    float[] step = new float[3]; // todo - move this allocation?
                    for (int i = 3; i-- > 0; axis = step[i] < step[axis] ? i : axis)
                    {
                        bool high = ((subCell >> i) & 1) != 0;
                        float face = (rayDirection[i] < 0.0f) ^ high ? bound[i + ((high ? 1 : 0) * 3)] : (bound[i] + bound[i + 3]) * 0.5f;
                        // distance to face
                        // (div by zero produces infinity, which is later discarded)
                        step[i] = (float)(face - rayOrigin[i]) / (float)rayDirection[i];
                    }

                    // leaving branch if: subcell is low and direction is negative,
                    // or subcell is high and direction is positive
                    if ((((subCell >> axis) & 1) != 0) ^ (rayDirection[axis] < 0.0f))
                        break;

                    // move to (outer face of) next subcell
                    cellPosition = rayOrigin + (rayDirection * step[axis]);
                    subCell = subCell ^ (1 << axis);
                }
            }
            else
            { // is leaf: exhaustively intersect contained items
                //float nearestDistance = float.MaxValue;

                // step through items
                foreach (GeomPrimitive item in triangles)
                {
                    if (rayContext.ignorePrimitive == item)
                        continue; //skip this primitive

                    if (item == lastHit)
                        continue; //skip this primitive

                    IntersectionData hitData = new IntersectionData();

                    if (item.intersect(rayContext.ray, hitData))
                    {
                        // check intersection is inside cell bound (with tolerance)
                        Vector3 hit = rayOrigin + (rayDirection * hitData.hitT);
                        float t = TOLERANCE;
                        if ((bound[0] - hit[0] <= t) && (hit[0] - bound[3] <= t) &&
                            (bound[1] - hit[1] <= t) && (hit[1] - bound[4] <= t) &&
                            (bound[2] - hit[2] <= t) && (hit[2] - bound[5] <= t))
                        {
                            pHitObject = item;
                            //nearestDistance = distance;
                            //hitPosition = hit;

                            rayContext.hitData = hitData;
                            rayContext.hitData.hitPrimitive = item;
                            rayContext.hitData.hitPos = rayContext.ray.p + rayContext.ray.dir * rayContext.hitData.hitT;

                            rayContext.hitData.hasIntersection = true;
                        }
                    }

                }
            }
        }
 public Color3 illuminate(RayContext rayContext, BRDF brdf)
 {
     return new Color3();
 }
        public Color3 illuminate(RayContext rayContext, BRDF brdf)
        {
            //вземаме списък с всички светлини в сцената
            List<Light> lights = rayContext.scene.getLights();
            //в sumColor ще сумираме тяхната енергия
            Color3 sumColor = new Color3();
            foreach (Light light in lights)
            {
                Color3 color = new Color3();
                //задаваме максималната бройка лъчи
                //с които ще проследяваме към всяка светлина
                int numSamples = 25;
                int curSample = 0; //номер на текущ лъч
                LightSample lightSample;

                rayContext.randomIndex = 0;

                do
                {
                    curSample++;
                    //генерирме двойка случайни числа (ru, rv)
                    double ru = rayContext.getRandom(0, curSample);
                    double rv = rayContext.getRandom(1, curSample);
                    //искаме от светлината да върне сонда за сянка
                    lightSample = light.getSample(rayContext, ru, rv);

                    rayContext.randomIndex++;

                    if (null == lightSample)
                        continue;
                    //пресмятаме косинуса между нормалата на повърхността и лъча към лампата
                    double cosT = rayContext.hitData.hitNormal * (lightSample.shadowRay.dir);
                    if (cosT < 0.00001) cosT = 0.0;

                    //проверяваме за засенчване
                    if (rayContext.scene.traceShadowRay(lightSample.shadowRay, rayContext))
                        continue;

                    double pdf = 1.0;
                    //пресмятаме, каква част от светлината ще отрази BRFD-a
                    Color3 brdfCol = brdf.eval(rayContext, lightSample.shadowRay.dir, out pdf);

                    color += brdfCol * pdf * lightSample.color * cosT;

                    //ако светлината е точкова ни е нужен само един лъч
                    if (light.isSingular())
                        break;

                } while (curSample < numSamples);

                if (curSample < 1)
                    curSample = 1;

                //сумираме резултата от всички сампъли
                color *= 1.0 / (double)curSample;
                sumColor += color;
            }

            int lightsCount = lights.Count;
            if (lightsCount < 1)
                lightsCount = 1;

            //сумираме енергията от всички светлини
            sumColor *= 1.0 / (double)lightsCount;
            return sumColor;
        }
 public abstract LightSample getSample(RayContext rayContext, double ru, double rv);
 public override void shade(RayContext rayContext)
 {
     rayContext.resultColor = color;
 }
        public Color3 illuminate(RayContext rayContext, BRDF brdf)
        {
            Ray newRay = new Ray();

            double invPdf = 0;

            //във finalColor ще сумираме резултатния цвят
            Color3 finalColor = new Color3();

            //задаваме максималната бройка лъчи
            //с които ще проследяваме грапави отражения
            int numSamples = 10;
            int curSample = 0;  //номер на текущ лъч

            for (curSample = 0; curSample < numSamples; curSample++)
            {
                //генерирме двойка случайни числа (ru, rv)
                double ru = rayContext.getRandom(0, curSample);
                double rv = rayContext.getRandom(1, curSample);

                //искаме brdf да конструира лъч използвайки (ru, rv)
                bool isValidSample = brdf.getSample(rayContext, ru, rv, out newRay.dir, out invPdf);

                if (!isValidSample)
                    continue;

                //конструираме нов лъч от текущата точка
                //и го обвиваме във собствен контекст
                newRay.p = rayContext.hitData.hitPos;
                RayContext newContext = RayContext.createNewContext(rayContext, newRay);
                //извикваме функцията shade, която проследява
                //лъча в сцената и го осветява
                rayContext.scene.shade(newContext);

                double cosT = rayContext.hitData.hitNormal * (newRay.dir);
                if (cosT < 0.0) cosT *= -1.0;

                double pdf;

                Color3 w = new Color3(1, 1, 1);
                //пресмятаме, каква част от светлината ще отрази BRFD-a
                w = brdf.eval(rayContext, newRay.dir, out pdf);

                if (!brdf.isSingular())
                {
                    w *= cosT * invPdf;
                }
                //добавяме енергията на новия лъч
                finalColor += newContext.resultColor * w;

                //if (Double.IsNaN(finalColor.getIntensity()))
                //    break;

                if (brdf.isSingular())
                    break;
            }

            //сумираме енергията от всички лъчи
            if (curSample > 0)
                finalColor *= 1.0 / (double)curSample;

            return finalColor;
        }