public static void BakeLights()
    {
        DateTime bakeStart = DateTime.Now;

        // current selection
        GameObject[] selection = Selection.gameObjects;

        List <MeshFilter> meshes = new List <MeshFilter>();

        // gather meshes in selection
        foreach (GameObject go in selection)
        {
            meshes.AddRange(go.GetComponentsInChildren <MeshFilter>());
        }
        if (meshes.Count == 0)
        {
            EditorUtility.DisplayDialog("Error", "No meshes in selection", "ok");
            return;
        }

        // mesh renderers allow access to additional mesh data
        List <MeshRenderer> meshRenderers = new List <MeshRenderer>();

        // gather meshes in selection build 1 to 1 list of mesh filters to mesh renderers
        foreach (MeshFilter filter in meshes)
        {
            MeshRenderer mr = filter.GetComponent <MeshRenderer>();
            if (mr != null)
            {
                meshRenderers.Add(mr);
            }
        }

        if (meshes.Count != meshRenderers.Count)
        {
            EditorUtility.DisplayDialog("Error", "MeshRenderers are not 1 to 1 with Mesh Filters", "ok");
            return;
        }

        List <Light> lights = new List <Light>();

        // Gather lights
        GameObject[] roots = SceneManager.GetActiveScene().GetRootGameObjects();
        foreach (GameObject go in roots)
        {
            if (go.activeSelf)
            {
                lights.AddRange(go.GetComponentsInChildren <Light>());
            }
        }

        int workerThreadCount = Math.Max(7, Environment.ProcessorCount - 1);

        ThreadPool.SetMaxThreads(workerThreadCount, workerThreadCount);
        ThreadPool.SetMinThreads(1, 1);

        // create new meshes with lights baked in
        for (int i = 0, count = meshes.Count; i < count; ++i)
        {
            Matrix4x4 worldM = meshes[i].gameObject.transform.localToWorldMatrix;

            // create new mesh to hold color data
            for (int c = 0; c < 3; ++c)
            {
                m_basisValues[c] = new List <Vector3>();
            }


            ManualResetEvent[] waitHandles = new ManualResetEvent[workerThreadCount];
            for (int e = 0; e < workerThreadCount; ++e)
            {
                waitHandles[e] = new ManualResetEvent(false);
            }

            int itemsPerThread = meshes[i].sharedMesh.vertexCount / workerThreadCount;
            int remainder      = meshes[i].sharedMesh.vertexCount % workerThreadCount;

            // store basis results here then combine into final result
            List <List <Vector3>[]> tempBasisValues = new List <List <Vector3>[]>();

            for (int t = 0; t < workerThreadCount; ++t)
            {
                tempBasisValues.Add(new List <Vector3> [3]);

                for (int c = 0; c < 3; ++c)
                {
                    tempBasisValues[t][c] = new List <Vector3>();
                }

                // create cache friendly lists of data
                SOAVertex verts = new SOAVertex(meshes[i].sharedMesh.vertices.Length);
                verts.vertices = meshes[i].sharedMesh.vertices;
                verts.normals  = meshes[i].sharedMesh.normals;
                verts.tangents = meshes[i].sharedMesh.tangents;

                LightSOA lightsSoa = new LightSOA(lights);

                object context = new object[] {
                    waitHandles[t],
                    verts,
                    worldM,
                    lightsSoa,
                    tempBasisValues[t],           // output list
                    itemsPerThread,               // number of verts to process
                    itemsPerThread *t,            // vert offset
                    remainder,                    // last thread will process remainder
                    t == (workerThreadCount - 1), // is last
                    t                             // id
                };

                ThreadPool.QueueUserWorkItem(ProcessVertex, context);
                //ProcessVertex(context);
            }

            // wait for jobs to finish
            WaitHandle.WaitAll(waitHandles);

            // collect basis value results
            for (int t = 0; t < workerThreadCount; ++t)
            {
                m_basisValues[0].AddRange(tempBasisValues[t][0]);
                m_basisValues[1].AddRange(tempBasisValues[t][1]);
                m_basisValues[2].AddRange(tempBasisValues[t][2]);
            }

            const int uvOffset = 1;
            // if vertices is not set than we cannot set UVS
            // these vertices seem to be used then in place of the primary meshes vertices
            Mesh colorData = new Mesh();
            colorData.vertices = meshes[i].sharedMesh.vertices;
            colorData.SetUVs(uvOffset + 0, m_basisValues[0]);
            colorData.SetUVs(uvOffset + 1, m_basisValues[1]);
            colorData.SetUVs(uvOffset + 2, m_basisValues[2]);
            colorData.UploadMeshData(true);


            string assetPath = "Assets/BakedLighting";

            if (!Directory.Exists(assetPath))
            {
                Directory.CreateDirectory(assetPath);
            }

            AssetDatabase.CreateAsset(colorData, assetPath + "/" + meshRenderers[i].GetInstanceID() + ".asset");
            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
            colorData = AssetDatabase.LoadAssetAtPath <Mesh>(assetPath + "/" + meshRenderers[i].GetInstanceID() + ".asset");

            MeshDataContainer meshDataContainer = meshRenderers[i].GetComponent <MeshDataContainer>();

            if (meshDataContainer == null)
            {
                meshDataContainer = meshRenderers[i].gameObject.AddComponent <MeshDataContainer>();
            }

            meshDataContainer.m_mesh = colorData;

            meshRenderers[i].additionalVertexStreams = colorData;

            EditorUtility.SetDirty(meshDataContainer.m_mesh);
            EditorUtility.SetDirty(meshDataContainer);
            EditorUtility.SetDirty(meshRenderers[i].gameObject);
            AssetDatabase.SaveAssets();
        }

        Debug.Log("Bake time: " + (DateTime.Now - bakeStart).TotalSeconds + " seconds");
    }
    private static void ProcessVertex(object context)
    {
        object[]         data       = context as object[];
        int              d          = 0;
        ManualResetEvent waitHandle = data[d++] as ManualResetEvent;
        SOAVertex        verts      = (SOAVertex)data[d++];
        Matrix4x4        worldM     = (Matrix4x4)data[d++];
        LightSOA         lights     = (LightSOA)data[d++];

        List <Vector3>[] basisValues = data[d++] as List <Vector3>[];
        int  itemsPerThread          = (int)data[d++];
        int  offset    = (int)data[d++];
        int  remainder = (int)data[d++];
        bool isLast    = (bool)data[d++];

        try {
            int vertCount = itemsPerThread;
            if (isLast)
            {
                vertCount += remainder;
            }


            int vertCount4 = 4 * (vertCount / 4);

            // per vertex calculations
            for (int j = offset, k = offset + vertCount4; j < k; j += 4)
            {
                Vector3 worldPos0 = worldM * (verts.vertices[j].ToVector4(1f));
                Vector3 worldPos1 = worldM * (verts.vertices[j + 1].ToVector4(1f));
                Vector3 worldPos2 = worldM * (verts.vertices[j + 2].ToVector4(1f));
                Vector3 worldPos3 = worldM * (verts.vertices[j + 3].ToVector4(1f));

                Vector3 tangent0 = worldM * verts.tangents[j + 0];
                Vector3 tangent1 = worldM * verts.tangents[j + 1];
                Vector3 tangent2 = worldM * verts.tangents[j + 2];
                Vector3 tangent3 = worldM * verts.tangents[j + 3];

                Vector3 normal0 = worldM * verts.normals[j + 0];
                Vector3 normal1 = worldM * verts.normals[j + 1];
                Vector3 normal2 = worldM * verts.normals[j + 2];
                Vector3 normal3 = worldM * verts.normals[j + 3];

                tangent0.Normalize();
                tangent1.Normalize();
                tangent2.Normalize();
                tangent3.Normalize();

                normal0.Normalize();
                normal1.Normalize();
                normal2.Normalize();
                normal3.Normalize();

                Vector3 binormal0 = Vector3.Cross(tangent0, normal0) * verts.tangents[j + 0].w;
                Vector3 binormal1 = Vector3.Cross(tangent1, normal1) * verts.tangents[j + 1].w;
                Vector3 binormal2 = Vector3.Cross(tangent2, normal2) * verts.tangents[j + 2].w;
                Vector3 binormal3 = Vector3.Cross(tangent3, normal3) * verts.tangents[j + 3].w;

                Matrix4x4 worldToTangentM0 = new Matrix4x4 {
                    m00 = tangent0.x, m10 = binormal0.x, m20 = normal0.x, m30 = 0f,
                    m01 = tangent0.y, m11 = binormal0.y, m21 = normal0.y, m31 = 0f,
                    m02 = tangent0.z, m12 = binormal0.z, m22 = normal0.z, m32 = 0f,
                    m03 = 0f, m13 = 0f, m23 = 0f, m33 = 0f,
                };
                Matrix4x4 worldToTangentM1 = new Matrix4x4 {
                    m00 = tangent1.x, m10 = binormal1.x, m20 = normal1.x, m30 = 0f,
                    m01 = tangent1.y, m11 = binormal1.y, m21 = normal1.y, m31 = 0f,
                    m02 = tangent1.z, m12 = binormal1.z, m22 = normal1.z, m32 = 0f,
                    m03 = 0f, m13 = 0f, m23 = 0f, m33 = 0f,
                };
                Matrix4x4 worldToTangentM2 = new Matrix4x4 {
                    m00 = tangent2.x, m10 = binormal2.x, m20 = normal2.x, m30 = 0f,
                    m01 = tangent2.y, m11 = binormal2.y, m21 = normal2.y, m31 = 0f,
                    m02 = tangent2.z, m12 = binormal2.z, m22 = normal2.z, m32 = 0f,
                    m03 = 0f, m13 = 0f, m23 = 0f, m33 = 0f,
                };
                Matrix4x4 worldToTangentM3 = new Matrix4x4 {
                    m00 = tangent3.x, m10 = binormal3.x, m20 = normal3.x, m30 = 0f,
                    m01 = tangent3.y, m11 = binormal3.y, m21 = normal3.y, m31 = 0f,
                    m02 = tangent3.z, m12 = binormal3.z, m22 = normal3.z, m32 = 0f,
                    m03 = 0f, m13 = 0f, m23 = 0f, m33 = 0f,
                };

                // calculate light basis contributions

                // 0
                Vector3 basis0_0 = Vector3.zero;
                Vector3 basis1_0 = Vector3.zero;
                Vector3 basis2_0 = Vector3.zero;

                Vector3 basis0_1 = Vector3.zero;
                Vector3 basis1_1 = Vector3.zero;
                Vector3 basis2_1 = Vector3.zero;

                Vector3 basis0_2 = Vector3.zero;
                Vector3 basis1_2 = Vector3.zero;
                Vector3 basis2_2 = Vector3.zero;

                Vector3 basis0_3 = Vector3.zero;
                Vector3 basis1_3 = Vector3.zero;
                Vector3 basis2_3 = Vector3.zero;

                /// 0
                for (int l = 0, lk = lights.pos_range.Length; l < lk; ++l)
                {
                    Vector3   lightPos   = lights.pos_range[l];
                    Vector3   lightDir   = lights.dir_inten[l];
                    float     lightRange = lights.pos_range[l].w;
                    float     lightInten = lights.dir_inten[l].w;
                    float     spotAngle  = lights.spot_angle[l].w;
                    Vector3   lightColor = lights.color_types[l];
                    LightType lightType  = (LightType)lights.color_types[l].w;
                    ComputeLightBasis(worldPos0, normal0, worldToTangentM0, lightPos, lightDir, lightColor, lightRange, lightInten, spotAngle, lightType, ref basis0_0, ref basis1_0, ref basis2_0);
                    ComputeLightBasis(worldPos1, normal1, worldToTangentM1, lightPos, lightDir, lightColor, lightRange, lightInten, spotAngle, lightType, ref basis0_1, ref basis1_1, ref basis2_1);
                    ComputeLightBasis(worldPos2, normal2, worldToTangentM2, lightPos, lightDir, lightColor, lightRange, lightInten, spotAngle, lightType, ref basis0_2, ref basis1_2, ref basis2_2);
                    ComputeLightBasis(worldPos3, normal3, worldToTangentM3, lightPos, lightDir, lightColor, lightRange, lightInten, spotAngle, lightType, ref basis0_3, ref basis1_3, ref basis2_3);
                }

                basisValues[0].Add(basis0_0);
                basisValues[1].Add(basis1_0);
                basisValues[2].Add(basis2_0);

                basisValues[0].Add(basis0_1);
                basisValues[1].Add(basis1_1);
                basisValues[2].Add(basis2_1);

                basisValues[0].Add(basis0_2);
                basisValues[1].Add(basis1_2);
                basisValues[2].Add(basis2_2);

                basisValues[0].Add(basis0_3);
                basisValues[1].Add(basis1_3);
                basisValues[2].Add(basis2_3);

                Thread.Sleep(1);
            }


            // remainder
            int remainderLoopCount = vertCount % 4;
            for (int j = vertCount4 + offset, k = vertCount4 + offset + remainderLoopCount; j < k; ++j)
            {
                Vector3 worldPos0 = worldM * (verts.vertices[j].ToVector4(1f));
                Vector3 tangent0  = worldM * verts.tangents[j];
                Vector3 normal0   = worldM * verts.normals[j];

                tangent0.Normalize();
                normal0.Normalize();

                Vector3 binormal0 = Vector3.Cross(tangent0, normal0) * verts.tangents[j].w;

                Matrix4x4 worldToTangentM0 = new Matrix4x4 {
                    m00 = tangent0.x, m10 = binormal0.x, m20 = normal0.x, m30 = 0f,
                    m01 = tangent0.y, m11 = binormal0.y, m21 = normal0.y, m31 = 0f,
                    m02 = tangent0.z, m12 = binormal0.z, m22 = normal0.z, m32 = 0f,
                    m03 = 0f, m13 = 0f, m23 = 0f, m33 = 0f,
                };


                // calculate light basis contributions

                // 0
                Vector3 basis0_0 = Vector3.zero;
                Vector3 basis1_0 = Vector3.zero;
                Vector3 basis2_0 = Vector3.zero;
                /// 0
                for (int l = 0, lk = lights.pos_range.Length; l < lk; ++l)
                {
                    Vector3   lightPos   = lights.pos_range[l];
                    Vector3   lightDir   = lights.dir_inten[l];
                    float     lightRange = lights.pos_range[l].w;
                    float     lightInten = lights.dir_inten[l].w;
                    float     spotAngle  = lights.spot_angle[l].w;
                    Vector3   lightColor = lights.color_types[l];
                    LightType lightType  = (LightType)lights.color_types[l].w;
                    ComputeLightBasis(worldPos0, normal0, worldToTangentM0, lightPos, lightDir, lightColor, lightRange, lightInten, spotAngle, lightType, ref basis0_0, ref basis1_0, ref basis2_0);
                }
                basisValues[0].Add(basis0_0);
                basisValues[1].Add(basis1_0);
                basisValues[2].Add(basis2_0);
            }
        } finally {
            // release the wait on this handle
            waitHandle.Set();
        }
    }