Example #1
0
        public static void FindFacesAndGroups(Map map, out List <LMFace> faces, out List <LightmapGroup> lmGroups)
        {
            faces    = new List <LMFace>();
            lmGroups = new List <LightmapGroup>();
            foreach (Solid solid in map.WorldSpawn.Find(x => x is Solid).OfType <Solid>())
            {
                foreach (Face tface in solid.Faces)
                {
                    tface.Vertices.ForEach(v => { v.LMU = -500.0f; v.LMV = -500.0f; });
                    tface.UpdateBoundingBox();
                    if (tface.Texture?.Texture == null)
                    {
                        continue;
                    }
                    if (tface.Texture.Name.ToLowerInvariant() == "tooltextures/invisible_collision")
                    {
                        continue;
                    }
                    if (tface.Texture.Name.ToLowerInvariant() == "tooltextures/remove_face")
                    {
                        continue;
                    }
                    if (tface.Texture.Name.ToLowerInvariant() == "tooltextures/block_light")
                    {
                        continue;
                    }
                    if (tface.Texture.Texture.HasTransparency())
                    {
                        continue;
                    }
                    LMFace        face    = new LMFace(tface, solid);
                    LightmapGroup group   = LightmapGroup.FindCoplanar(lmGroups, face);
                    BoxF          faceBox = new BoxF(face.BoundingBox.Start - new CoordinateF(3.0f, 3.0f, 3.0f), face.BoundingBox.End + new CoordinateF(3.0f, 3.0f, 3.0f));
                    if (group == null)
                    {
                        group             = new LightmapGroup();
                        group.BoundingBox = faceBox;
                        group.Faces       = new List <LMFace>();
                        group.Plane       = new PlaneF(face.Plane.Normal, face.Vertices[0].Location);
                        lmGroups.Add(group);
                    }
#if DEBUG
                    if (face.Texture.ToLowerInvariant() == "tooltextures/debug_breakpoint")
                    {
                        group.DebugBreakpoint = true;
                    }
#endif
                    group.Faces.Add(face);
                    group.Plane       = new PlaneF(group.Plane.Normal, (face.Vertices[0].Location + group.Plane.PointOnPlane) / 2);
                    group.BoundingBox = new BoxF(new BoxF[] { group.BoundingBox, faceBox });
                }
            }
        }
Example #2
0
 public static LightmapGroup FindCoplanar(List <LightmapGroup> lmGroups, LMFace otherFace)
 {
     foreach (LightmapGroup group in lmGroups)
     {
         if ((group.Plane.Normal - otherFace.Plane.Normal).LengthSquared() < 0.01f)
         {
             PlaneF plane2 = new PlaneF(otherFace.Plane.Normal, otherFace.Vertices[0].Location);
             if (Math.Abs(plane2.EvalAtPoint((group.Plane.PointOnPlane))) > 4.0f)
             {
                 continue;
             }
             BoxF faceBox = new BoxF(otherFace.BoundingBox.Start - new CoordinateF(3.0f, 3.0f, 3.0f), otherFace.BoundingBox.End + new CoordinateF(3.0f, 3.0f, 3.0f));
             if (faceBox.IntersectsWith(group.BoundingBox))
             {
                 return(group);
             }
         }
     }
     return(null);
 }
Example #3
0
        protected static CoordinateF GetIntersectionPoint(LMFace face, LineF line, bool ignoreDirection = false)
        {
            var plane     = face.Plane;
            var intersect = plane.GetIntersectionPoint(line, ignoreDirection);
            List <CoordinateF> coordinates = face.Vertices.Select(x => x.Location).ToList();

            if (intersect == null)
            {
                return(null);
            }
            BoxF bbox = new BoxF(face.BoundingBox.Start - new CoordinateF(0.5f, 0.5f, 0.5f), face.BoundingBox.End + new CoordinateF(0.5f, 0.5f, 0.5f));

            if (!bbox.CoordinateIsInside(intersect))
            {
                return(null);
            }

            CoordinateF centerPoint = face.BoundingBox.Center;

            for (var i = 0; i < coordinates.Count; i++)
            {
                var i1 = i;
                var i2 = (i + 1) % coordinates.Count;

                var lineMiddle     = (coordinates[i1] + coordinates[i2]) * 0.5f;
                var middleToCenter = centerPoint - lineMiddle;
                var v          = coordinates[i1] - coordinates[i2];
                var lineNormal = face.Plane.Normal.Cross(v);

                if ((middleToCenter - lineNormal).LengthSquared() > (middleToCenter + lineNormal).LengthSquared())
                {
                    lineNormal = -lineNormal;
                }

                if (lineNormal.Dot(intersect - lineMiddle) < 0.0f)
                {
                    return(null);
                }
            }
            return(intersect);
        }
Example #4
0
 public static void FindFacesAndGroups(Map map, out List <LMFace> faces, out List <LightmapGroup> lmGroups)
 {
     faces    = new List <LMFace>();
     lmGroups = new List <LightmapGroup>();
     foreach (Solid solid in map.WorldSpawn.Find(x => x is Solid).OfType <Solid>())
     {
         foreach (Face tface in solid.Faces)
         {
             tface.Vertices.ForEach(v => { v.LMU = -500.0f; v.LMV = -500.0f; });
             tface.UpdateBoundingBox();
             if (tface.Texture?.Texture == null)
             {
                 continue;
             }
             if (tface.Texture.IsToolTexture)
             {
                 continue;
             }
             if (tface.Texture.Texture.HasTransparency())
             {
                 continue;
             }
             LMFace        face    = new LMFace(tface, solid);
             LightmapGroup group   = LightmapGroup.FindCoplanar(lmGroups, face);
             BoxF          faceBox = new BoxF(face.BoundingBox.Start - new CoordinateF(3.0f, 3.0f, 3.0f), face.BoundingBox.End + new CoordinateF(3.0f, 3.0f, 3.0f));
             if (group == null)
             {
                 group             = new LightmapGroup();
                 group.BoundingBox = faceBox;
                 group.Faces       = new List <LMFace>();
                 group.Plane       = new PlaneF(face.Plane.Normal, face.Vertices[0].Location);
                 lmGroups.Add(group);
             }
             group.Faces.Add(face);
             group.Plane       = new PlaneF(group.Plane.Normal, (face.Vertices[0].Location + group.Plane.PointOnPlane) / 2);
             group.BoundingBox = new BoxF(new BoxF[] { group.BoundingBox, faceBox });
         }
     }
 }
Example #5
0
        private static void RenderLightOntoFace(Document doc, float[][] bitmaps, List <Light> lights, LightmapGroup group, LMFace targetFace, IEnumerable <LMFace> blockerFaces)
        {
            Random rand = new Random();

            int writeX = group.writeX;
            int writeY = group.writeY;

            int textureDims;

            lock (doc.TextureCollection.Lightmaps)
            {
                textureDims = doc.TextureCollection.Lightmaps[0].Width;
            }

            lights = lights.FindAll(x =>
            {
                float range   = x.Range;
                BoxF lightBox = new BoxF(x.Origin - new CoordinateF(range, range, range), x.Origin + new CoordinateF(range, range, range));
                return(lightBox.IntersectsWith(targetFace.BoundingBox));
            });

            float?minX = null; float?maxX = null;
            float?minY = null; float?maxY = null;

            foreach (CoordinateF coord in targetFace.Vertices.Select(x => x.Location))
            {
                float x = coord.Dot(group.uAxis);
                float y = coord.Dot(group.vAxis);

                if (minX == null || x < minX)
                {
                    minX = x;
                }
                if (minY == null || y < minY)
                {
                    minY = y;
                }
                if (maxX == null || x > maxX)
                {
                    maxX = x;
                }
                if (maxY == null || y > maxY)
                {
                    maxY = y;
                }
            }

            CoordinateF leewayPoint = group.Plane.PointOnPlane + (group.Plane.Normal * Math.Max(LightmapConfig.DownscaleFactor * 0.25f, 1.5f));

            minX -= LightmapConfig.DownscaleFactor; minY -= LightmapConfig.DownscaleFactor;
            maxX += LightmapConfig.DownscaleFactor; maxY += LightmapConfig.DownscaleFactor;

            minX /= LightmapConfig.DownscaleFactor; minX = (float)Math.Ceiling(minX.Value); minX *= LightmapConfig.DownscaleFactor;
            minY /= LightmapConfig.DownscaleFactor; minY = (float)Math.Ceiling(minY.Value); minY *= LightmapConfig.DownscaleFactor;
            maxX /= LightmapConfig.DownscaleFactor; maxX = (float)Math.Ceiling(maxX.Value); maxX *= LightmapConfig.DownscaleFactor;
            maxY /= LightmapConfig.DownscaleFactor; maxY = (float)Math.Ceiling(maxY.Value); maxY *= LightmapConfig.DownscaleFactor;

            foreach (LMFace.Vertex vert in targetFace.Vertices)
            {
                float x = vert.Location.Dot(group.uAxis);
                float y = vert.Location.Dot(group.vAxis);

                float u = (writeX + 0.5f + (x - group.minTotalX.Value) / LightmapConfig.DownscaleFactor);
                float v = (writeY + 0.5f + (y - group.minTotalY.Value) / LightmapConfig.DownscaleFactor);

                targetFace.LmIndex = (u >= LightmapConfig.TextureDims ? 1 : 0) + (v >= LightmapConfig.TextureDims ? 2 : 0);

                u /= (float)textureDims;
                v /= (float)textureDims;

                vert.LMU = u; vert.LMV = v;
                vert.OriginalVertex.LMU = u; vert.OriginalVertex.LMV = v;
            }

            float centerX           = (maxX.Value + minX.Value) / 2;
            float centerY           = (maxY.Value + minY.Value) / 2;

            int iterX = (int)Math.Ceiling((maxX.Value - minX.Value) / LightmapConfig.DownscaleFactor);
            int iterY = (int)Math.Ceiling((maxY.Value - minY.Value) / LightmapConfig.DownscaleFactor);

            float[][,] r = new float[4][, ];
            r[0]         = new float[iterX, iterY];
            r[1]         = new float[iterX, iterY];
            r[2]         = new float[iterX, iterY];
            r[3]         = new float[iterX, iterY];
            float[][,] g = new float[4][, ];
            g[0]         = new float[iterX, iterY];
            g[1]         = new float[iterX, iterY];
            g[2]         = new float[iterX, iterY];
            g[3]         = new float[iterX, iterY];
            float[][,] b = new float[4][, ];
            b[0]         = new float[iterX, iterY];
            b[1]         = new float[iterX, iterY];
            b[2]         = new float[iterX, iterY];
            b[3]         = new float[iterX, iterY];

            foreach (Light light in lights)
            {
                CoordinateF lightPos   = light.Origin;
                float       lightRange = light.Range;
                CoordinateF lightColor = light.Color * (1.0f / 255.0f) * light.Intensity;

                BoxF          lightBox = new BoxF(new BoxF[] { targetFace.BoundingBox, new BoxF(light.Origin - new CoordinateF(30.0f, 30.0f, 30.0f), light.Origin + new CoordinateF(30.0f, 30.0f, 30.0f)) });
                List <LMFace> applicableBlockerFaces = blockerFaces.Where(x =>
                {
                    if (x == targetFace)
                    {
                        return(false);
                    }
                    if (group.Faces.Contains(x))
                    {
                        return(false);
                    }
                    //return true;
                    if (lightBox.IntersectsWith(x.BoundingBox))
                    {
                        return(true);
                    }
                    return(false);
                }).ToList();

                bool[,] illuminated = new bool[iterX, iterY];

                for (int y = 0; y < iterY; y++)
                {
                    for (int x = 0; x < iterX; x++)
                    {
                        illuminated[x, y] = true;
                    }
                }

                for (int y = 0; y < iterY; y++)
                {
                    for (int x = 0; x < iterX; x++)
                    {
                        int tX = (int)(writeX + x + (int)(minX - group.minTotalX) / LightmapConfig.DownscaleFactor);
                        int tY = (int)(writeY + y + (int)(minY - group.minTotalY) / LightmapConfig.DownscaleFactor);

                        if (tX >= 0 && tY >= 0 && tX < textureDims && tY < textureDims)
                        {
                            int offset = (tX + tY * textureDims) * Bitmap.GetPixelFormatSize(System.Drawing.Imaging.PixelFormat.Format32bppArgb) / 8;
                            bitmaps[0][offset + 3] = 1.0f;
                            bitmaps[1][offset + 3] = 1.0f;
                            bitmaps[2][offset + 3] = 1.0f;
                            bitmaps[3][offset + 3] = 1.0f;
                        }
                    }
                }

                for (int y = 0; y < iterY; y++)
                {
                    for (int x = 0; x < iterX; x++)
                    {
                        float       ttX          = minX.Value + (x * LightmapConfig.DownscaleFactor);
                        float       ttY          = minY.Value + (y * LightmapConfig.DownscaleFactor);
                        CoordinateF pointOnPlane = (ttX - centerX) * group.uAxis + (ttY - centerY) * group.vAxis + targetFace.BoundingBox.Center;

                        /*Entity entity = new Entity(map.IDGenerator.GetNextObjectID());
                         * entity.Colour = Color.Pink;
                         * entity.Origin = new Coordinate(pointOnPlane);
                         * entity.UpdateBoundingBox();
                         * entity.SetParent(map.WorldSpawn);*/

                        int tX = (int)(writeX + x + (int)(minX - group.minTotalX) / LightmapConfig.DownscaleFactor);
                        int tY = (int)(writeY + y + (int)(minY - group.minTotalY) / LightmapConfig.DownscaleFactor);

                        CoordinateF luxelColor0    = new CoordinateF(r[0][x, y], g[0][x, y], b[0][x, y]);
                        CoordinateF luxelColor1    = new CoordinateF(r[1][x, y], g[1][x, y], b[1][x, y]);
                        CoordinateF luxelColor2    = new CoordinateF(r[2][x, y], g[2][x, y], b[2][x, y]);
                        CoordinateF luxelColorNorm = new CoordinateF(r[3][x, y], g[3][x, y], b[3][x, y]);

                        float dotToLight0    = Math.Max((lightPos - pointOnPlane).Normalise().Dot(targetFace.LightBasis0), 0.0f);
                        float dotToLight1    = Math.Max((lightPos - pointOnPlane).Normalise().Dot(targetFace.LightBasis1), 0.0f);
                        float dotToLight2    = Math.Max((lightPos - pointOnPlane).Normalise().Dot(targetFace.LightBasis2), 0.0f);
                        float dotToLightNorm = Math.Max((lightPos - pointOnPlane).Normalise().Dot(targetFace.Normal), 0.0f);

                        if (illuminated[x, y] && (pointOnPlane - lightPos).LengthSquared() < lightRange * lightRange)
                        {
#if TRUE
                            LineF lineTester = new LineF(lightPos, pointOnPlane);
                            for (int i = 0; i < applicableBlockerFaces.Count; i++)
                            {
                                LMFace      otherFace = applicableBlockerFaces[i];
                                CoordinateF hit       = otherFace.GetIntersectionPoint(lineTester);
                                if (hit != null && ((hit - leewayPoint).Dot(group.Plane.Normal) > 0.0f || (hit - pointOnPlane).LengthSquared() > LightmapConfig.DownscaleFactor * 2f))
                                {
                                    applicableBlockerFaces.RemoveAt(i);
                                    applicableBlockerFaces.Insert(0, otherFace);
                                    illuminated[x, y] = false;
                                    i++;
                                    break;
                                }
                            }
#endif
                        }
                        else
                        {
                            illuminated[x, y] = false;
                        }

                        if (illuminated[x, y])
                        {
                            float brightness = (lightRange - (pointOnPlane - lightPos).VectorMagnitude()) / lightRange;

                            if (light.Direction != null)
                            {
                                float directionDot = light.Direction.Dot((pointOnPlane - lightPos).Normalise());

                                if (directionDot < light.innerCos)
                                {
                                    if (directionDot < light.outerCos)
                                    {
                                        brightness = 0.0f;
                                    }
                                    else
                                    {
                                        brightness *= (directionDot - light.outerCos.Value) / (light.innerCos.Value - light.outerCos.Value);
                                    }
                                }
                            }

                            float brightness0    = dotToLight0 * brightness * brightness;
                            float brightness1    = dotToLight1 * brightness * brightness;
                            float brightness2    = dotToLight2 * brightness * brightness;
                            float brightnessNorm = dotToLightNorm * brightness * brightness;

                            brightness0    += ((float)rand.NextDouble() - 0.5f) * 0.005f;
                            brightness1    += ((float)rand.NextDouble() - 0.5f) * 0.005f;
                            brightness2    += ((float)rand.NextDouble() - 0.5f) * 0.005f;
                            brightnessNorm += ((float)rand.NextDouble() - 0.5f) * 0.005f;

                            r[0][x, y] += lightColor.Z * brightness0; if (r[0][x, y] > 1.0f)
                            {
                                r[0][x, y] = 1.0f;
                            }
                            if (r[0][x, y] < 0)
                            {
                                r[0][x, y] = 0;
                            }
                            g[0][x, y] += lightColor.Y * brightness0; if (g[0][x, y] > 1.0f)
                            {
                                g[0][x, y] = 1.0f;
                            }
                            if (g[0][x, y] < 0)
                            {
                                g[0][x, y] = 0;
                            }
                            b[0][x, y] += lightColor.X * brightness0; if (b[0][x, y] > 1.0f)
                            {
                                b[0][x, y] = 1.0f;
                            }
                            if (b[0][x, y] < 0)
                            {
                                b[0][x, y] = 0;
                            }

                            r[1][x, y] += lightColor.Z * brightness1; if (r[1][x, y] > 1.0f)
                            {
                                r[1][x, y] = 1.0f;
                            }
                            if (r[1][x, y] < 0)
                            {
                                r[1][x, y] = 0;
                            }
                            g[1][x, y] += lightColor.Y * brightness1; if (g[1][x, y] > 1.0f)
                            {
                                g[1][x, y] = 1.0f;
                            }
                            if (g[1][x, y] < 0)
                            {
                                g[1][x, y] = 0;
                            }
                            b[1][x, y] += lightColor.X * brightness1; if (b[1][x, y] > 1.0f)
                            {
                                b[1][x, y] = 1.0f;
                            }
                            if (b[1][x, y] < 0)
                            {
                                b[1][x, y] = 0;
                            }

                            r[2][x, y] += lightColor.Z * brightness2; if (r[2][x, y] > 1.0f)
                            {
                                r[2][x, y] = 1.0f;
                            }
                            if (r[2][x, y] < 0)
                            {
                                r[2][x, y] = 0;
                            }
                            g[2][x, y] += lightColor.Y * brightness2; if (g[2][x, y] > 1.0f)
                            {
                                g[2][x, y] = 1.0f;
                            }
                            if (g[2][x, y] < 0)
                            {
                                g[2][x, y] = 0;
                            }
                            b[2][x, y] += lightColor.X * brightness2; if (b[2][x, y] > 1.0f)
                            {
                                b[2][x, y] = 1.0f;
                            }
                            if (b[2][x, y] < 0)
                            {
                                b[2][x, y] = 0;
                            }

                            r[3][x, y] += lightColor.Z * brightnessNorm; if (r[3][x, y] > 1.0f)
                            {
                                r[3][x, y] = 1.0f;
                            }
                            if (r[3][x, y] < 0)
                            {
                                r[3][x, y] = 0;
                            }
                            g[3][x, y] += lightColor.Y * brightnessNorm; if (g[3][x, y] > 1.0f)
                            {
                                g[3][x, y] = 1.0f;
                            }
                            if (g[3][x, y] < 0)
                            {
                                g[3][x, y] = 0;
                            }
                            b[3][x, y] += lightColor.X * brightnessNorm; if (b[3][x, y] > 1.0f)
                            {
                                b[3][x, y] = 1.0f;
                            }
                            if (b[3][x, y] < 0)
                            {
                                b[3][x, y] = 0;
                            }

                            luxelColor0    = new CoordinateF(r[0][x, y], g[0][x, y], b[0][x, y]);
                            luxelColor1    = new CoordinateF(r[1][x, y], g[1][x, y], b[1][x, y]);
                            luxelColor2    = new CoordinateF(r[2][x, y], g[2][x, y], b[2][x, y]);
                            luxelColorNorm = new CoordinateF(r[3][x, y], g[3][x, y], b[3][x, y]);

                            if (tX >= 0 && tY >= 0 && tX < textureDims && tY < textureDims)
                            {
                                int offset = (tX + tY * textureDims) * Bitmap.GetPixelFormatSize(System.Drawing.Imaging.PixelFormat.Format32bppArgb) / 8;
                                if (luxelColor0.X + luxelColor0.Y + luxelColor0.Z > bitmaps[0][offset + 2] + bitmaps[0][offset + 1] + bitmaps[0][offset + 0])
                                {
                                    bitmaps[0][offset + 0] = luxelColor0.X;
                                    bitmaps[0][offset + 1] = luxelColor0.Y;
                                    bitmaps[0][offset + 2] = luxelColor0.Z;
                                }
                                if (luxelColor1.X + luxelColor1.Y + luxelColor1.Z > bitmaps[1][offset + 2] + bitmaps[1][offset + 1] + bitmaps[1][offset + 0])
                                {
                                    bitmaps[1][offset + 0] = luxelColor1.X;
                                    bitmaps[1][offset + 1] = luxelColor1.Y;
                                    bitmaps[1][offset + 2] = luxelColor1.Z;
                                }
                                if (luxelColor2.X + luxelColor2.Y + luxelColor2.Z > bitmaps[2][offset + 2] + bitmaps[2][offset + 1] + bitmaps[2][offset + 0])
                                {
                                    bitmaps[2][offset + 0] = luxelColor2.X;
                                    bitmaps[2][offset + 1] = luxelColor2.Y;
                                    bitmaps[2][offset + 2] = luxelColor2.Z;
                                }
                                if (luxelColorNorm.X + luxelColorNorm.Y + luxelColorNorm.Z > bitmaps[3][offset + 2] + bitmaps[3][offset + 1] + bitmaps[3][offset + 0])
                                {
                                    bitmaps[3][offset + 0] = luxelColorNorm.X;
                                    bitmaps[3][offset + 1] = luxelColorNorm.Y;
                                    bitmaps[3][offset + 2] = luxelColorNorm.Z;
                                }
                            }
                        }
                    }
                }
            }
        }
Example #6
0
 private static Thread CreateLightmapRenderThread(Document doc, float[][] bitmaps, List <Light> lights, LightmapGroup group, LMFace targetFace, IEnumerable <LMFace> blockerFaces)
 {
     return(new Thread(() => {
         try
         {
             RenderLightOntoFace(doc, bitmaps, lights, group, targetFace, blockerFaces);
         }
         catch (ThreadAbortException)
         {
             //do nothing
         }
         catch (Exception e)
         {
             threadExceptions.Add(new LMThreadException(e));
         }
     }));
 }
Example #7
0
        public static void Render(Document document, ExportForm exportForm, out List <LMFace> faces, out int lmCount)
        {
            var textureCollection = document.TextureCollection;

            var map = document.Map;

            faces = new List <LMFace>();
            var lightEntities = new List <Light>();

            threadExceptions = new List <LMThreadException>();

            List <LightmapGroup> lmGroups          = new List <LightmapGroup>();
            List <LMFace>        exclusiveBlockers = new List <LMFace>();

            //get faces
            UpdateProgress(exportForm, "Determining UV coordinates...", 0);
            LMFace.FindFacesAndGroups(map, out faces, out lmGroups);

            if (!lmGroups.Any())
            {
                throw new Exception("No lightmap groups!");
            }

            foreach (Solid solid in map.WorldSpawn.Find(x => x is Solid).OfType <Solid>())
            {
                foreach (Face tface in solid.Faces)
                {
                    LMFace face = new LMFace(tface, solid);
                    if (tface.Texture.Name.ToLower() != "tooltextures/block_light")
                    {
                        continue;
                    }
                    exclusiveBlockers.Add(face);
                }
            }

            for (int i = 0; i < lmGroups.Count; i++)
            {
                for (int j = i + 1; j < lmGroups.Count; j++)
                {
                    if ((lmGroups[i].Plane.Normal - lmGroups[j].Plane.Normal).LengthSquared() < 0.001f &&
                        lmGroups[i].BoundingBox.IntersectsWith(lmGroups[j].BoundingBox))
                    {
                        lmGroups[i].Faces.AddRange(lmGroups[j].Faces);
                        lmGroups[i].BoundingBox = new BoxF(new BoxF[] { lmGroups[i].BoundingBox, lmGroups[j].BoundingBox });
#if DEBUG
                        if (lmGroups[j].DebugBreakpoint)
                        {
                            lmGroups[i].DebugBreakpoint = true;
                        }
#endif
                        lmGroups.RemoveAt(j);
                        j = i + 1;
                    }
                }
            }

            //put the faces into the bitmap
            lmGroups.Sort((x, y) =>
            {
                if (x.Width == y.Width)
                {
                    if (x.Height == y.Height)
                    {
                        return(0);
                    }
                    if (x.Height < y.Height)
                    {
                        return(1);
                    }
                    return(-1);
                }

                if (x.Width < y.Width)
                {
                    return(1);
                }
                return(-1);
            });

            FaceRenderThreads = new List <Thread>();

            Light.FindLights(map, out lightEntities);

            List <LMFace> allBlockers = lmGroups.Select(q => q.Faces).SelectMany(q => q).Where(f => f.CastsShadows).Union(exclusiveBlockers).ToList();
            int           faceCount   = 0;

            List <LightmapGroup> uvCalcFaces = new List <LightmapGroup>(lmGroups);

            int totalTextureDims = LightmapConfig.TextureDims;
            lmCount = 0;
            for (int i = 0; i < 4; i++)
            {
                int x = 1 + ((i % 2) * LightmapConfig.TextureDims);
                int y = 1 + ((i / 2) * LightmapConfig.TextureDims);
                CalculateUV(uvCalcFaces, new Rectangle(x, y, LightmapConfig.TextureDims - 2, LightmapConfig.TextureDims - 2), out _, out _);
                lmCount++;
                if (uvCalcFaces.Count == 0)
                {
                    break;
                }
                totalTextureDims = LightmapConfig.TextureDims * 2;
            }

            if (uvCalcFaces.Count > 0)
            {
                throw new Exception("Could not fit lightmap into four textures; try increasing texture dimensions or downscale factor");
            }

            float[][] buffers = new float[4][];
            lock (textureCollection.Lightmaps)
            {
                for (int i = 0; i < 4; i++)
                {
                    textureCollection.Lightmaps[i]?.Dispose();
                    textureCollection.Lightmaps[i] = new Bitmap(totalTextureDims, totalTextureDims);
                    buffers[i] = new float[textureCollection.Lightmaps[i].Width * textureCollection.Lightmaps[i].Height * Bitmap.GetPixelFormatSize(PixelFormat.Format32bppArgb) / 8];
                }
            }

            foreach (LightmapGroup group in lmGroups)
            {
                foreach (LMFace face in group.Faces)
                {
                    faceCount++;
                    Thread newThread = CreateLightmapRenderThread(document, buffers, lightEntities, group, face, allBlockers);
                    FaceRenderThreads.Add(newThread);
                }
            }

            int faceNum = 0;
            UpdateProgress(exportForm, "Started calculating brightness levels...", 0.05f);
            while (FaceRenderThreads.Count > 0)
            {
                for (int i = 0; i < 8; i++)
                {
                    if (i >= FaceRenderThreads.Count)
                    {
                        break;
                    }
                    if (FaceRenderThreads[i].ThreadState == ThreadState.Unstarted)
                    {
                        FaceRenderThreads[i].Start();
                    }
                    else if (!FaceRenderThreads[i].IsAlive)
                    {
                        FaceRenderThreads.RemoveAt(i);
                        i--;
                        faceNum++;
                        UpdateProgress(exportForm, faceNum.ToString() + "/" + faceCount.ToString() + " faces complete", 0.05f + ((float)faceNum / (float)faceCount) * 0.85f);
                    }
                }

                if (threadExceptions.Count > 0)
                {
                    for (int i = 0; i < FaceRenderThreads.Count; i++)
                    {
                        if (FaceRenderThreads[i].IsAlive)
                        {
                            FaceRenderThreads[i].Abort();
                        }
                    }
                    throw new Exception(threadExceptions[0].Message + "\n" + threadExceptions[0].StackTrace);
                }
                Thread.Yield();
            }

            //blur the lightmap so it doesn't look too pixellated
            UpdateProgress(exportForm, "Blurring lightmap...", 0.95f);
            float[] blurBuffer = new float[buffers[0].Length];
            for (int k = 0; k < 4; k++)
            {
                foreach (LightmapGroup group in lmGroups)
                {
                    int downscaledWidth  = (int)Math.Ceiling(group.Width / LightmapConfig.DownscaleFactor);
                    int downscaledHeight = (int)Math.Ceiling(group.Height / LightmapConfig.DownscaleFactor);

                    CoordinateF ambientNormal = new CoordinateF(LightmapConfig.AmbientNormalX,
                                                                LightmapConfig.AmbientNormalY,
                                                                LightmapConfig.AmbientNormalZ).Normalise();
                    float       ambientMultiplier = (group.Plane.Normal.Dot(ambientNormal) + 1.5f) * 0.4f;
                    CoordinateF mAmbientColor     = new CoordinateF((LightmapConfig.AmbientColorB * ambientMultiplier / 255.0f),
                                                                    (LightmapConfig.AmbientColorG * ambientMultiplier / 255.0f),
                                                                    (LightmapConfig.AmbientColorR * ambientMultiplier / 255.0f));
                    for (int y = group.writeY; y < group.writeY + downscaledHeight; y++)
                    {
                        if (y < 0 || y >= totalTextureDims)
                        {
                            continue;
                        }
                        for (int x = group.writeX; x < group.writeX + downscaledWidth; x++)
                        {
                            if (x < 0 || x >= totalTextureDims)
                            {
                                continue;
                            }
                            int offset = (x + y * totalTextureDims) * System.Drawing.Image.GetPixelFormatSize(PixelFormat.Format32bppArgb) / 8;

                            float accumRed    = 0;
                            float accumGreen  = 0;
                            float accumBlue   = 0;
                            int   sampleCount = 0;
                            for (int j = -LightmapConfig.BlurRadius; j <= LightmapConfig.BlurRadius; j++)
                            {
                                if (y + j < 0 || y + j >= totalTextureDims)
                                {
                                    continue;
                                }
                                if (y + j < group.writeY || y + j >= group.writeY + downscaledHeight)
                                {
                                    continue;
                                }
                                for (int i = -LightmapConfig.BlurRadius; i <= LightmapConfig.BlurRadius; i++)
                                {
                                    if (i * i + j * j > LightmapConfig.BlurRadius * LightmapConfig.BlurRadius)
                                    {
                                        continue;
                                    }
                                    if (x + i < 0 || x + i >= totalTextureDims)
                                    {
                                        continue;
                                    }
                                    if (x + i < group.writeX || x + i >= group.writeX + downscaledWidth)
                                    {
                                        continue;
                                    }
                                    int sampleOffset = ((x + i) + (y + j) * totalTextureDims) * System.Drawing.Image.GetPixelFormatSize(PixelFormat.Format32bppArgb) / 8;
                                    if (buffers[k][sampleOffset + 3] < 1.0f)
                                    {
                                        continue;
                                    }
                                    sampleCount++;
                                    accumRed   += buffers[k][sampleOffset + 0];
                                    accumGreen += buffers[k][sampleOffset + 1];
                                    accumBlue  += buffers[k][sampleOffset + 2];
                                }
                            }

                            if (sampleCount < 1)
                            {
                                sampleCount = 1;
                            }
                            accumRed   /= sampleCount;
                            accumGreen /= sampleCount;
                            accumBlue  /= sampleCount;

                            accumRed   = mAmbientColor.X + (accumRed * (1.0f - mAmbientColor.X));
                            accumGreen = mAmbientColor.Y + (accumGreen * (1.0f - mAmbientColor.Y));
                            accumBlue  = mAmbientColor.Z + (accumBlue * (1.0f - mAmbientColor.Z));

                            if (accumRed > 1.0f)
                            {
                                accumRed = 1.0f;
                            }
                            if (accumGreen > 1.0f)
                            {
                                accumGreen = 1.0f;
                            }
                            if (accumBlue > 1.0f)
                            {
                                accumBlue = 1.0f;
                            }

                            blurBuffer[offset + 0] = accumRed;
                            blurBuffer[offset + 1] = accumGreen;
                            blurBuffer[offset + 2] = accumBlue;
                            blurBuffer[offset + 3] = 1.0f;
                        }
                    }
                }

                blurBuffer.CopyTo(buffers[k], 0);
            }

            for (int i = 0; i < buffers[0].Length; i++)
            {
                if (i % 4 == 3)
                {
                    buffers[0][i] = 1.0f;
                    buffers[1][i] = 1.0f;
                    buffers[2][i] = 1.0f;
                    buffers[3][i] = 1.0f;
                }
                else
                {
                    float brightnessAdd = (buffers[0][i] + buffers[1][i] + buffers[2][i]) / (float)Math.Sqrt(3.0);
                    if (brightnessAdd > 0.0f) //normalize brightness to remove artifacts when adding together
                    {
                        buffers[0][i] *= buffers[3][i] / brightnessAdd;
                        buffers[1][i] *= buffers[3][i] / brightnessAdd;
                        buffers[2][i] *= buffers[3][i] / brightnessAdd;
                    }
                }
            }

            UpdateProgress(exportForm, "Copying bitmap data...", 0.99f);
            for (int k = 0; k < 4; k++)
            {
                byte[] byteBuffer = new byte[buffers[k].Length];
                for (int i = 0; i < buffers[k].Length; i++)
                {
                    byteBuffer[i] = (byte)Math.Max(Math.Min(buffers[k][i] * 255.0f, 255.0f), 0.0f);
                }
                lock (textureCollection.Lightmaps)
                {
                    BitmapData bitmapData2 = textureCollection.Lightmaps[k].LockBits(new Rectangle(0, 0, totalTextureDims, totalTextureDims), ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
                    Marshal.Copy(byteBuffer, 0, bitmapData2.Scan0, byteBuffer.Length);
                    textureCollection.Lightmaps[k].UnlockBits(bitmapData2);
                }
            }

            faces.Clear();
            faces.AddRange(lmGroups.SelectMany(g => g.Faces));

            lock (textureCollection.Lightmaps)
            {
                document.TextureCollection.LightmapTextureOutdated = true;
            }

            UpdateProgress(exportForm, "Lightmapping complete!", 1.0f);
        }