private static RayHit Trace(LightRay lightRay, MainBakeArgs bakeArgs)
        {
            RayHit bestHit = BlankHit();

            for (int i = 0; i < bakeArgs.ObjectData.Count; i++)
            {
                var objectDatum = bakeArgs.ObjectData[i];

                if (!objectDatum.Bounds.IntersectsLightRay(lightRay))
                {
                    continue;
                }

                for (int j = 0; j < objectDatum.IndicesCount; j += 3)
                {
                    int subIndex0 = j + objectDatum.IndicesOffset;

                    // Going from an object's Local-Space and into World-Space requires matrix-multiplication. For this to
                    // work, we need to Vector4s with a 'w' component that contains a value of 1.0. This value of 1.0
                    // allows the matrix multiplication to perfrom a transpose that will carry over into the result. It's
                    // one of the reasons for using "w = 1.0" for positions and "w = 0.0" for directions.
                    // -FCT
                    Vector3 v0 = objectDatum.LocalToWorldMatrix * bakeArgs.Vertices[bakeArgs.Indices[subIndex0] + objectDatum.VerticesOffset].Position();
                    Vector3 v1 = objectDatum.LocalToWorldMatrix * bakeArgs.Vertices[bakeArgs.Indices[subIndex0 + 1] + objectDatum.VerticesOffset].Position();
                    Vector3 v2 = objectDatum.LocalToWorldMatrix * bakeArgs.Vertices[bakeArgs.Indices[subIndex0 + 2] + objectDatum.VerticesOffset].Position();

                    float   s = float.MaxValue;
                    Vector3 n = new Vector3(0.0f, 0.0f, 0.0f);

                    bool theresAHit = TriangleIntersect(lightRay, v0, v1, v2, bakeArgs.LightSourcePosition, out s, out n);

                    if (theresAHit)
                    {
                        if (s < bestHit.Distance)
                        {
                            bestHit.Distance = s;
                            bestHit.Position = lightRay.Origin + (s * lightRay.Direction);
                            bestHit.Normal   = n;
                            bestHit.HasAHit  = true;
                        }
                    }
                }               // End loop that checks all triangles for current object
            }                   // End loop that checks all objects

            if (bestHit.HasAHit)
            {
                bestHit.Normal = bestHit.Normal.normalized;
            }

            return(bestHit);
        }
        private static LightRay CreateInitialLightRay(Vector2 uv, float halfSize, MainBakeArgs bakeArgs)
        {
            var intersectionPointOffset = ((uv.x * halfSize) * bakeArgs.LightSourceRightward)
                                          + ((uv.y * halfSize) * bakeArgs.LightSourceUpward)
                                          + (s_shadowFocusDistance * bakeArgs.LightSourceForward);

            LightRay lightRay = new LightRay()
            {
                Color     = new Vector3(1.0f, 1.0f, 1.0f),
                Origin    = bakeArgs.LightSourcePosition,
                Direction = intersectionPointOffset.normalized
            };

            return(lightRay);
        }
        private IEnumerator CookieBaking()
        {
            yield return(null);

            var resolution = s_resolutionOptions[s_selectedCookieResolution];


            // We only want to use GameObjects that are within the outer radius that we have set. So, a quick test
            // to see if something is within the ball park would be to check if their bounding box insersects with
            // a bounding box that contains the outer radius. Once that has been done, we can take a closer look at
            // what's left.
            var meshRenders        = FindObjectsOfType <MeshRenderer>();
            var lightCenter        = s_currentLightComponent.transform.position;
            var lightBoundingBox   = new UnityEngine.Bounds(lightCenter, 2.0f * s_outerRadius * Vector3.one);
            var intersectingBounds = new List <MeshRenderer>();

            foreach (var meshRenderer in meshRenders)
            {
                var otherBounds = meshRenderer.bounds;
                if (otherBounds.Intersects(lightBoundingBox))
                {
                    intersectingBounds.Add(meshRenderer);
                }
            }

            yield return(null);

            // Now, we can limit things a bit more by finding the point within our mesh bounds that is nearset to
            // the light's center. We then check this point to see if it's within the distance of our outer radius.
            var intersectingOuterRadius = new List <MeshRenderer>();

            foreach (var meshRenderer in intersectingBounds)
            {
                var otherBounds         = meshRenderer.bounds;
                var nearsetPointToLight = otherBounds.ClosestPoint(lightCenter);
                if ((nearsetPointToLight - lightCenter).sqrMagnitude <= (s_outerRadius * s_outerRadius))
                {
                    intersectingOuterRadius.Add(meshRenderer);
                }
            }

            yield return(null);

            var processMeshRenderer = intersectingBounds;
            var processMeshFilter   = new List <MeshFilter>();

            // We're reusing the List from intersectingBounds and placing it under a more appropriate name.
            // Basically, we're reusing a piece of memory that's no longer needed instead of allocating a new List
            // with a new array. Also, this array shouldn't need to be resized to something larger because it
            // already a capacity that is greater than or equal to the number of items we will be processing.
            processMeshRenderer.Clear();
            intersectingBounds = null;


            // Also, while we're at it, lets make sure that we are only baking items that are static. After all,
            // what's the point of baking the shadow casting item that might not be there?
            foreach (var meshRender in intersectingOuterRadius)
            {
                var gameObject = meshRender.gameObject;

                // If the gameObject isn't static, than it can be moved. Since we are not updating the cookie at
                // runtime, having a moving object would be bad.
                if (!gameObject.isStatic)
                {
                    continue;
                }

                // The mesh for our object is inside the MeshFilter, so we need that to get the mesh. Also, since we're
                // dealing with static meshes, then we really shouldn't be dealing with any Skinned Mesh Renderers.
                var meshFilter = gameObject.GetComponent <MeshFilter>();
                if (meshFilter == null)
                {
                    continue;
                }

                // If there's no mesh, then what are we even bothering with this?
                if (meshFilter.sharedMesh == null)
                {
                    continue;
                }

                processMeshRenderer.Add(meshRender);
                processMeshFilter.Add(meshFilter);
            }

            yield return(null);


            // Todo: With this design, items that use the same mesh will replicate the mesh verts and triangle index
            //		 arrays. I need to add a way to check if we have already added a mesh and if so, pull up the triangle
            //		 index array information to pass along to the meshRefDatum.
            var meshObjecRefData = new List <MeshObject>(processMeshRenderer.Count);
            var indexList        = new List <int>();
            var vertexList       = new List <Vector3>(processMeshRenderer.Count * 50);

            for (int i = 0; i < processMeshRenderer.Count; i++)
            {
                var mesh                = processMeshFilter[i].sharedMesh;
                var meshVerts           = mesh.vertices;
                var meshTriangleIndices = mesh.triangles;

                var meshRefDatum = new MeshObject()
                {
                    LocalToWorldMatrix = processMeshRenderer[i].localToWorldMatrix,
                    IndicesOffset      = indexList.Count,                                       // The starting index of the vert-index for this object's mesh.
                    IndicesCount       = meshTriangleIndices.Length,                            // The number of indices used to form all of the mesh triangles.
                    VerticesOffset     = vertexList.Count,
                    Bounds             = new Bounds(processMeshRenderer[i].bounds)
                };

                meshObjecRefData.Add(meshRefDatum);
                vertexList.AddRange(meshVerts);
                indexList.AddRange(meshTriangleIndices);
            }

            yield return(null);

            var threadArgs = new MainBakeArgs()
            {
                ObjectData = meshObjecRefData,
                Vertices   = vertexList,
                Indices    = indexList,

                ImageResolution = resolution,

                LightSourcePosition  = lightCenter,
                LightSourceForward   = s_currentLightComponent.transform.forward,
                LightSourceUpward    = s_currentLightComponent.transform.up,
                LightSourceRightward = s_currentLightComponent.transform.right,
                LightSourceTheata    = Mathf.Deg2Rad * s_currentLightComponent.spotAngle / 2.0f
            };

            using (var mainBakeThread = new BackgroundWorker())
            {
                mainBakeThread.DoWork += MainBakeThread_DoWork;
                yield return(null);

                mainBakeThread.RunWorkerAsync(threadArgs);
                s_currentBakeState = BakeState.Bake;
                yield return(null);

                while (!threadArgs.Complete)
                {
                    yield return(new EditorWaitForSeconds(0.2f));
                }
            }


            s_currentBakeState = BakeState.Finalize;

            yield return(null);


            ///
            /// Code for saving our results into the project and applying them to the light component.
            ///

            // Create a Texture2D to place our results into. This Texture2D will be added to the project's assets
            // and it will be applied to the light source we were raytracing.
            Texture2D finalResults = new Texture2D(resolution, resolution, TextureFormat.RGBAFloat, false, true)
            {
                alphaIsTransparency = true,
                filterMode          = FilterMode.Point,
                wrapMode            = TextureWrapMode.Clamp,
                name = s_currentLightComponent.name + "_ShadowCookie_" + resolution.ToString()
            };

            yield return(null);

            finalResults.SetPixels(threadArgs.Result);
            yield return(null);


            // Check for the location where we will be saving the Texture2D. If that location doesn't exist, then
            // create it.
            var assetFolderPath = "Assets/Light_Cookies/" + s_currentLightComponent.gameObject.scene.name;

            if (!Directory.Exists(assetFolderPath))
            {
                if (!Directory.Exists("Assets/Light_Cookies"))
                {
                    AssetDatabase.CreateFolder("Assets", "Light_Cookies");
                    yield return(null);
                }
                AssetDatabase.CreateFolder("Assets/Light_Cookies", s_currentLightComponent.gameObject.scene.name);
            }
            yield return(null);


            // Save the Texture2D as a project asset.
            var assetPath = assetFolderPath + "/" + finalResults.name + ".asset";

            AssetDatabase.CreateAsset(finalResults, assetPath);

            yield return(null);

            // Apply the Texture2D to the light as a cooke, then finish cleaning up.
            s_currentLightComponent.cookie = finalResults;
            EditorUtility.SetDirty(s_currentLightComponent.gameObject);
            s_bakingCoroutine = null;                       // The coroutines for MonoBehaviors have the option to manipulate them from the outside, which I have
            // made use of. With a bit of luck, those functions will be added to the EditorCoroutines at some point
            // in time.
            // -FCT
            yield return(null);

            s_currentBakeState = BakeState.SettingSelection;
        }