Exemplo n.º 1
0
        // Draws an outline of all items in the current cell to act as background/template
        static void DrawCellBackground(Graphics backgroundLayer)
        {
            if (!SettingsMap.IsCellModeActive())
            {
                return;
            }

            CellScaling cellScaling = SettingsCell.GetCell().GetScaling();

            int outlineWidth = SettingsCell.outlineWidth;
            int outlineSize  = SettingsCell.outlineSize;

            Image    plotIconImg     = new Bitmap(outlineSize, outlineSize);
            Graphics plotIconGraphic = Graphics.FromImage(plotIconImg);

            plotIconGraphic.SmoothingMode = SmoothingMode.AntiAlias;
            Color outlineColor = Color.FromArgb(SettingsCell.outlineAlpha, SettingsCell.outlineColor);
            Pen   outlinePen   = new Pen(outlineColor, outlineWidth);

            plotIconGraphic.DrawEllipse(
                outlinePen,
                new RectangleF(outlineWidth, outlineWidth, outlineSize - (outlineWidth * 2), outlineSize - (outlineWidth * 2)));

            // Iterate over every data point and draw it
            foreach (MapDataPoint point in DataHelper.GetAllCellCoords(SettingsCell.GetCell().formID))
            {
                // If this coordinate exceeds the user-selected cell mapping height bounds, skip it
                // (Also accounts for the z-height of volumes)
                if (point.z < SettingsCell.GetMinHeightCoordBound() || point.z > SettingsCell.GetMaxHeightCoordBound())
                {
                    continue;
                }

                point.x += cellScaling.xOffset;
                point.y += cellScaling.yOffset;

                // Multiply the coordinates by the scaling, but multiply around 0,0
                point.x       = ((point.x - (mapDimension / 2)) * cellScaling.scale) + (mapDimension / 2);
                point.y       = ((point.y - (mapDimension / 2)) * cellScaling.scale) + (mapDimension / 2);
                point.boundX *= cellScaling.scale;
                point.boundY *= cellScaling.scale;

                backgroundLayer.DrawImage(plotIconImg, (float)(point.x - (plotIconImg.Width / 2d)), (float)(point.y - (plotIconImg.Height / 2d)));
            }

            GC.Collect();
        }
Exemplo n.º 2
0
        // Construct the final map by drawing plots over the background layer
        public static void Draw()
        {
            // Reset the current image to the background layer
            finalImage = (Image)backgroundLayer.Clone();

            Graphics imageGraphic = Graphics.FromImage(finalImage);

            imageGraphic.SmoothingMode = SmoothingMode.AntiAlias;
            Font font = new Font(fontCollection.Families[0], fontSize, GraphicsUnit.Pixel);

            CellScaling cellScaling = null;

            // Prepare the game version and watermark to be printed later
            string infoText = (SettingsPlot.IsTopographic() ? "Topographic View\n" : string.Empty) + "Game version " + IOManager.GetGameVersion() + "\nMade with Mappalachia - github.com/AHeroicLlama/Mappalachia";

            // Additional steps for cell mode (Add further text to watermark text, get cell height boundings)
            if (SettingsMap.IsCellModeActive())
            {
                Cell currentCell = SettingsCell.GetCell();

                // Assign the CellScaling property
                cellScaling = currentCell.GetScaling();

                infoText =
                    currentCell.displayName + " (" + currentCell.editorID + ")\n" +
                    "Height distribution: " + SettingsCell.minHeightPerc + "% - " + SettingsCell.maxHeightPerc + "%\n" +
                    "Scale: 1:" + Math.Round(cellScaling.scale, 2) + "\n\n" +
                    infoText;
            }

            // Gather resources for drawing informational watermark text
            Brush        brushWhite              = new SolidBrush(Color.White);
            RectangleF   infoTextBounds          = new RectangleF(plotXMin, 0, mapDimension - plotXMin, mapDimension);
            StringFormat stringFormatBottomRight = new StringFormat()
            {
                Alignment = StringAlignment.Far, LineAlignment = StringAlignment.Far
            };                                                                                                                                              // Align the text bottom-right
            StringFormat stringFormatBottomLeft = new StringFormat()
            {
                Alignment = StringAlignment.Near, LineAlignment = StringAlignment.Far
            };                                                                                                                                              // Align the text bottom-left

            // Draws bottom-right info text
            imageGraphic.DrawString(infoText, font, brushWhite, infoTextBounds, stringFormatBottomRight);

            // Draw a height-color key for Topography mode
            if (SettingsPlot.IsTopographic())
            {
                // Identify the sizing and locations for drawing the height-color-key strings
                double numHeightKeys    = SettingsPlotTopograph.heightKeyIndicators;
                Font   topographFont    = new Font(fontCollection.Families[0], 62, GraphicsUnit.Pixel);
                float  singleLineHeight = imageGraphic.MeasureString(SettingsPlotTopograph.heightKeyString, topographFont, new SizeF(infoTextBounds.Width, infoTextBounds.Height)).Height;

                // Identify the lower limit to start printing the key so that it ends up centered
                double baseHeight = (mapDimension / 2) - (singleLineHeight * (numHeightKeys / 2d));

                for (int i = 0; i <= numHeightKeys - 1; i++)
                {
                    Brush brush = new SolidBrush(GetTopographColor(i / (numHeightKeys - 1)));
                    imageGraphic.DrawString(SettingsPlotTopograph.heightKeyString, topographFont, brush, new RectangleF(plotXMax, 0, mapDimension - plotXMax, (float)(mapDimension - baseHeight)), stringFormatBottomRight);
                    baseHeight += singleLineHeight;
                }
            }

            // Draw all legend text for every MapItem
            int skippedLegends = DrawLegend(font, imageGraphic);

            // Adds additional text if some items were missed from legend
            if (skippedLegends > 0)
            {
                string extraLegendText = "+" + skippedLegends + " more item" + (skippedLegends == 1 ? string.Empty : "s") + "...";
                imageGraphic.DrawString(extraLegendText, font, brushWhite, infoTextBounds, stringFormatBottomLeft);
            }

            // Start progress bar off at 0
            progressBarMain.Value = progressBarMain.Minimum;
            float progress = 0;

            // Nothing else to plot - ensure we update for the background layer but then return
            if (FormMaster.legendItems.Count == 0)
            {
                mapFrame.Image = finalImage;
                return;
            }

            // Count how many Map Data Points are due to be mapped
            int totalMapDataPoints = 0;

            foreach (MapItem mapItem in FormMaster.legendItems)
            {
                totalMapDataPoints += mapItem.count;
            }

            // Loop through every MapDataPoint represented by all the MapItems to find the min/max z coord in the dataset
            bool   first  = true;
            int    zMin   = 0;
            int    zMax   = 0;
            double zRange = 0;

            if (SettingsPlot.IsTopographic())
            {
                foreach (MapItem mapItem in FormMaster.legendItems)
                {
                    foreach (MapDataPoint point in mapItem.GetPlots())
                    {
                        if (first)
                        {
                            zMin  = point.z - (point.boundZ / 2);
                            zMax  = point.z + (point.boundZ / 2);
                            first = false;
                            continue;
                        }

                        // Do not contribute outlier values to the min/max range - this ensures they have the same
                        // color as the min/max *legitimate* item and they do not skew the color ranges
                        if (point.z > SettingsPlotTopograph.zThreshUpper || point.z < SettingsPlotTopograph.zThreshLower)
                        {
                            continue;
                        }

                        if (point.z - (point.boundZ / 2) < zMin)
                        {
                            zMin = point.z - (point.boundZ / 2);
                        }

                        if (point.z + (point.boundZ / 2) > zMax)
                        {
                            zMax = point.z + (point.boundZ / 2);
                        }
                    }
                }

                zRange = Math.Abs(zMax - zMin);

                if (zRange == 0)
                {
                    zRange = 1;
                }
            }

            if (SettingsPlot.IsIconOrTopographic())
            {
                if (SettingsPlot.IsTopographic())
                {
                    // Somehow this line prevents a memory leak
                    // Without it, if drawing a large topographic map on first map draw, GC will not collect the multiple PlotIcon elements used in topographic drawing.
                    Application.DoEvents();
                }

                // Processing each MapItem in serial, draw plots for every matching valid MapDataPoint
                foreach (MapItem mapItem in FormMaster.legendItems)
                {
                    // Generate a Plot Icon and colours/brushes to be used for all instances of the MapItem
                    PlotIcon plotIcon    = mapItem.GetIcon();
                    Image    plotIconImg = SettingsPlot.IsIcon() ? plotIcon.GetIconImage() : null;                  // Icon mode has icon per MapItem, Topography needs icons per MapDataPoint and will be generated later
                    Color    volumeColor = Color.FromArgb(volumeOpacity, plotIcon.color);
                    Brush    volumeBrush = new SolidBrush(volumeColor);

                    // Iterate over every data point and draw it
                    foreach (MapDataPoint point in mapItem.GetPlots())
                    {
                        // Override colors in Topographic mode
                        if (SettingsPlot.IsTopographic())
                        {
                            // Clamp the z values to the percieved outlier threshold
                            double z = point.z + (point.boundZ / 2);
                            z = Math.Max(Math.Min(z, SettingsPlotTopograph.zThreshUpper), SettingsPlotTopograph.zThreshLower);

                            // Normalize the height of this item between the min/max z of the whole set
                            double colorValue = (z - zMin) / zRange;

                            // Override the plot icon color
                            plotIcon.color = GetTopographColor(colorValue);
                            plotIconImg    = plotIcon.GetIconImage();                          // Generate a new icon with a unique color for this height color

                            // Apply the color to volume plotting too
                            volumeColor = Color.FromArgb(volumeOpacity, plotIcon.color);
                            volumeBrush = new SolidBrush(volumeColor);
                        }

                        if (SettingsMap.IsCellModeActive())
                        {
                            // If this coordinate exceeds the user-selected cell mapping height bounds, skip it
                            // (Also accounts for the z-height of volumes)
                            if (point.z + (point.boundZ / 2d) < SettingsCell.GetMinHeightCoordBound() || point.z - (point.boundZ / 2d) > SettingsCell.GetMaxHeightCoordBound())
                            {
                                continue;
                            }

                            point.x += cellScaling.xOffset;
                            point.y += cellScaling.yOffset;

                            // Multiply the coordinates by the scaling, but multiply around 0,0
                            point.x       = ((point.x - (mapDimension / 2)) * cellScaling.scale) + (mapDimension / 2);
                            point.y       = ((point.y - (mapDimension / 2)) * cellScaling.scale) + (mapDimension / 2);
                            point.boundX *= cellScaling.scale;
                            point.boundY *= cellScaling.scale;
                        }
                        else                         // Skip the point if its origin is outside the surface world
                        if (point.x < plotXMin || point.x >= plotXMax || point.y < plotYMin || point.y >= plotYMax)
                        {
                            continue;
                        }

                        // If this meets all the criteria to be suitable to be drawn as a volume
                        if (point.primitiveShape != string.Empty &&                                   // This is a primitive shape at all
                            SettingsPlot.drawVolumes &&                                               // Volume drawing is enabled
                            point.boundX >= minVolumeDimension && point.boundY >= minVolumeDimension) // This is large enough to be visible if drawn as a volume
                        {
                            Image    volumeImage   = new Bitmap((int)point.boundX, (int)point.boundY);
                            Graphics volumeGraphic = Graphics.FromImage(volumeImage);
                            volumeGraphic.SmoothingMode = SmoothingMode.AntiAlias;

                            switch (point.primitiveShape)
                            {
                            case "Box":
                            case "Line":
                            case "Plane":
                                volumeGraphic.FillRectangle(volumeBrush, new Rectangle(0, 0, (int)point.boundX, (int)point.boundY));
                                break;

                            case "Sphere":
                            case "Ellipsoid":
                                volumeGraphic.FillEllipse(volumeBrush, new Rectangle(0, 0, (int)point.boundX, (int)point.boundY));
                                break;

                            default:
                                continue;                                         // If we reach this, we dropped the drawing of a volume. Verify we've covered all shapes via the database summary.txt
                            }

                            volumeImage = ImageTools.RotateImage(volumeImage, point.rotationZ);
                            imageGraphic.DrawImage(volumeImage, (float)(point.x - (volumeImage.Width / 2)), (float)(point.y - (volumeImage.Height / 2)));
                        }

                        // This MapDataPoint is not suitable to be drawn as a volume - draw a normal plot icon, or topographic plot
                        else
                        {
                            imageGraphic.DrawImage(plotIconImg, (float)(point.x - (plotIconImg.Width / 2d)), (float)(point.y - (plotIconImg.Height / 2d)));
                        }
                    }

                    // Increment the progress bar per MapItem
                    progress += mapItem.count;
                    progressBarMain.Value = (int)((progress / totalMapDataPoints) * progressBarMain.Maximum);
                    Application.DoEvents();
                }
            }
            else if (SettingsPlot.IsHeatmap())
            {
                int resolution = SettingsPlotHeatmap.resolution;
                int blendRange = SettingsPlotHeatmap.blendDistance;

                // Create a 2D Array of HeatMapGridSquare
                HeatMapGridSquare[,] squares = new HeatMapGridSquare[resolution, resolution];
                for (int x = 0; x < resolution; x++)
                {
                    for (int y = 0; y < resolution; y++)
                    {
                        squares[x, y] = new HeatMapGridSquare();
                    }
                }

                int pixelsPerSquare = mapDimension / resolution;

                foreach (MapItem mapItem in FormMaster.legendItems)
                {
                    int heatmapLegendGroup = SettingsPlotHeatmap.IsDuo() ? mapItem.legendGroup % 2 : 0;

                    foreach (MapDataPoint point in mapItem.GetPlots())
                    {
                        if (SettingsMap.IsCellModeActive())
                        {
                            point.x += cellScaling.xOffset;
                            point.y += cellScaling.yOffset;

                            point.x = ((point.x - (mapDimension / 2)) * cellScaling.scale) + (mapDimension / 2);
                            point.y = ((point.y - (mapDimension / 2)) * cellScaling.scale) + (mapDimension / 2);
                        }

                        // Identify which grid square this MapDataPoint falls within
                        int squareX = (int)Math.Floor(point.x / pixelsPerSquare);
                        int squareY = (int)Math.Floor(point.y / pixelsPerSquare);

                        // Loop over every grid square within range, and increment by the weight proportional to the distance
                        for (int x = squareX - blendRange; x < squareX + blendRange; x++)
                        {
                            for (int y = squareY - blendRange; y < squareY + blendRange; y++)
                            {
                                // Don't try to target squares which would lay outside of the grid
                                if (x < 0 || x >= resolution || y < 0 || y >= resolution)
                                {
                                    continue;
                                }

                                // Pythagoras on the x and y dist gives us the 'as the crow flies' distance between the squares
                                double distance = Pythagoras(squareX - x, squareY - y);

                                // Weight and hence brightness is modified by 1/x^2 + 1 where x is the distance from actual item
                                double additionalWeight = point.weight * (1d / ((distance * distance) + 1));
                                squares[x, y].weights[heatmapLegendGroup] += additionalWeight;
                            }
                        }
                    }

                    // Increment the progress bar per MapItem
                    progress += mapItem.count;
                    progressBarMain.Value = (int)((progress / totalMapDataPoints) * progressBarMain.Maximum);
                    Application.DoEvents();
                }

                // Find the largest weight value of all squares
                double largestWeight = 0;
                for (int x = 0; x < resolution; x++)
                {
                    for (int y = 0; y < resolution; y++)
                    {
                        double weight = squares[x, y].GetTotalWeight();
                        if (weight > largestWeight)
                        {
                            largestWeight = weight;
                        }
                    }
                }

                // Finally now weights are calculated, draw a square for every HeatGripMapSquare in the array
                for (int x = 0; x < resolution; x++)
                {
                    int xCoord = x * pixelsPerSquare;

                    // Don't draw grid squares which are entirely within the legend text area
                    if (xCoord + pixelsPerSquare < plotXMin)
                    {
                        continue;
                    }

                    for (int y = 0; y < resolution; y++)
                    {
                        int yCoord = y * pixelsPerSquare;

                        Color color = squares[x, y].GetColor(largestWeight);
                        Brush brush = new SolidBrush(color);

                        Rectangle heatMapSquare = new Rectangle(xCoord, yCoord, mapDimension / SettingsPlotHeatmap.resolution, mapDimension / SettingsPlotHeatmap.resolution);
                        imageGraphic.FillRectangle(brush, heatMapSquare);
                    }
                }
            }

            mapFrame.Image = finalImage;
        }