Browse Source

Updates to Terrain Editor

+ Added settings for the currently selected tool. This includes brush size, brush opacity, and brush height
+ Improved the speed of the smoothing algorithm by removing the new Array that was being created
+ Ignore transparent pixels in the smoothing algorithm
+ The "Create Terrain" action now creates a new image, saves it to Textures folder and adds it to the scene
+ Hid the "Paint" tools, as they have yet to be implemented
luveti 8 years ago
parent
commit
3511050a96
2 changed files with 452 additions and 348 deletions
  1. 344 318
      bin/Data/Scripts/Editor/EditorTerrain.as
  2. 108 30
      bin/Data/UI/EditorTerrainWindow.xml

+ 344 - 318
bin/Data/Scripts/Editor/EditorTerrain.as

@@ -1,3 +1,4 @@
+
 // Urho3D terrain editor
 // Urho3D terrain editor
 
 
 const uint TERRAIN_EDITMODE_RAISELOWERHEIGHT = 0, TERRAIN_EDITMODE_SETHEIGHT = 1, TERRAIN_EDITMODE_SMOOTHHEIGHT = 3,
 const uint TERRAIN_EDITMODE_RAISELOWERHEIGHT = 0, TERRAIN_EDITMODE_SETHEIGHT = 1, TERRAIN_EDITMODE_SMOOTHHEIGHT = 3,
@@ -7,19 +8,23 @@ funcdef bool TerrainEditorShowCallback();
 
 
 class TerrainEditor
 class TerrainEditor
 {
 {
-    bool dirty = true;
-    uint editMode = 0;
-
-    Window@ window;
-    UIElement@ toolbar;
-    Text@ currentToolDescText;
-    Array<Image@> brushes;
-    CheckBox@ selectedBrush;
-    Image@ selectedBrushImage;
-
-    Array<Terrain@> terrainsEdited;
-
-    Color targetColor;
+    private bool dirty = true;
+    private uint editMode = 0;
+
+    private Window@ window;
+    private UIElement@ toolbar;
+    private Text@ currentToolDescText;
+    private Array<Image@> brushes;
+    private CheckBox@ selectedBrush;
+    private Image@ selectedBrushImage;
+    private Image@ scaledSelectedBrushImage;
+    private Slider@ brushSizeSlider;
+    private Slider@ brushOpacitySlider;
+    private Slider@ brushHeightSlider;
+
+    private Array<Terrain@> terrainsEdited;
+
+    private Color targetColor;
     bool targetColorSelected = false;
     bool targetColorSelected = false;
 
 
     // Create the Terrain Editor window and initialize it
     // Create the Terrain Editor window and initialize it
@@ -32,73 +37,47 @@ class TerrainEditor
         ui.root.AddChild(window);
         ui.root.AddChild(window);
         window.opacity = uiMaxOpacity;
         window.opacity = uiMaxOpacity;
 
 
-        BorderImage@ currentToolDesc = BorderImage("CurrentToolDesc");
-        currentToolDesc.style = "EditorToolBar";
-        currentToolDesc.SetLayout(LM_HORIZONTAL);
-        currentToolDesc.layoutSpacing = 4;
-        currentToolDesc.layoutBorder = IntRect(8, 4, 4, 8);
-        currentToolDesc.minHeight = 32;
-        currentToolDesc.horizontalAlignment = HA_CENTER;
-        currentToolDesc.opacity = uiMaxOpacity;
-        currentToolDesc.SetPosition(0, 0);
-        window.AddChild(currentToolDesc);
-
-        currentToolDescText = Text("CurrentToolDescText");
-        currentToolDescText.text = "Raise or lower terrain using the current brush";
-        currentToolDescText.SetStyleAuto(uiStyle);
-        currentToolDescText.color = Color(1.0f, 1.0, 1.0f, 1.0f);
-        currentToolDescText.position = IntVector2(16, 16);
-        currentToolDescText.SetFont(cache.GetResource("Font", "Fonts/BlueHighway.ttf"), 10);
-        currentToolDesc.AddChild(currentToolDescText);
-
-        Text@ brushesText = Text("BrushesText");
-        brushesText.text = "Brushes";
-        brushesText.SetStyleAuto(uiStyle);
-        brushesText.color = Color(1.0f, 1.0, 1.0f, 1.0f);
-        brushesText.SetFont(cache.GetResource("Font", "Fonts/BlueHighway.ttf"), 10);
-        window.AddChild(brushesText);
-
-        ListView@ brushesContainer = ListView("BrushesContainer");
-        brushesContainer.defaultStyle = uiStyle;
-        brushesContainer.style = "ListView";
-        brushesContainer.layoutSpacing = 4;
-        brushesContainer.internal = false;
-        brushesContainer.layoutBorder = IntRect(8, 4, 4, 8);
-        brushesContainer.opacity = uiMaxOpacity;
+        BorderImage@ currentToolDesc = window.GetChild("CurrentToolDesc", true);
+        currentToolDesc.layoutBorder = IntRect(8, 8, 8, 8);
+
+        currentToolDescText = window.GetChild("CurrentToolDescText", true);
+
+        ListView@ brushesContainer = window.GetChild("BrushesContainer", true);
         brushesContainer.SetScrollBarsVisible(true, false);
         brushesContainer.SetScrollBarsVisible(true, false);
         brushesContainer.contentElement.layoutMode = LM_HORIZONTAL;
         brushesContainer.contentElement.layoutMode = LM_HORIZONTAL;
         brushesContainer.SetFixedHeight(84);
         brushesContainer.SetFixedHeight(84);
-        window.AddChild(brushesContainer);
 
 
-        Text@ settingsText = Text("SettingsText");
-        settingsText.text = "Settings";
-        settingsText.SetStyleAuto(uiStyle);
-        settingsText.color = Color(1.0f, 1.0, 1.0f, 1.0f);
-        settingsText.SetFont(cache.GetResource("Font", "Fonts/BlueHighway.ttf"), 10);
-        window.AddChild(settingsText);
+        BorderImage@ settingsArea = window.GetChild("SettingsArea", true);
+        settingsArea.layoutBorder = IntRect(8, 8, 8, 8);
+
+        LineEdit@ createTerrainValue = window.GetChild("CreateTerrainValue", true);
+        createTerrainValue.text = "1024";
+
+        brushSizeSlider = window.GetChild("BrushSize", true);
+        brushOpacitySlider = window.GetChild("BrushOpacity", true);
+        brushHeightSlider = window.GetChild("BrushHeight", true);
 
 
         window.height = 300;
         window.height = 300;
         window.SetPosition(ui.root.width - 10 - window.width, attributeInspectorWindow.position.y + attributeInspectorWindow.height + 10);
         window.SetPosition(ui.root.width - 10 - window.width, attributeInspectorWindow.position.y + attributeInspectorWindow.height + 10);
 
 
-        SubscribeToEvent(window.GetChild("RaiseLowerHeight", true), "Toggled", "EditModeRaiseLowerHeight");
-        SubscribeToEvent(window.GetChild("SetHeight", true), "Toggled", "EditModeSetHeight");
-        SubscribeToEvent(window.GetChild("SmoothHeight", true), "Toggled", "EditModeSmoothHeight");
-        SubscribeToEvent(window.GetChild("PaintBrush", true), "Toggled", "EditModePaintBrush");
-        SubscribeToEvent(window.GetChild("PaintTrees", true), "Toggled", "EditModePaintTrees");
-        SubscribeToEvent(window.GetChild("PaintFoliage", true), "Toggled", "EditModePaintFoliage");
+        SubscribeToEvent(window.GetChild("RaiseLowerHeight", true), "Toggled", "OnEditModeSelected");
+        SubscribeToEvent(window.GetChild("SetHeight", true), "Toggled", "OnEditModeSelected");
+        SubscribeToEvent(window.GetChild("SmoothHeight", true), "Toggled", "OnEditModeSelected");
+        //SubscribeToEvent(window.GetChild("PaintBrush", true), "Toggled", "OnEditModeSelected");
+        //SubscribeToEvent(window.GetChild("PaintTrees", true), "Toggled", "OnEditModeSelected");
+        //SubscribeToEvent(window.GetChild("PaintFoliage", true), "Toggled", "OnEditModeSelected");
         SubscribeToEvent(window.GetChild("CloseButton", true), "Released", "Hide");
         SubscribeToEvent(window.GetChild("CloseButton", true), "Released", "Hide");
         SubscribeToEvent(window.GetChild("CreateTerrainButton", true), "Released", "CreateTerrain");
         SubscribeToEvent(window.GetChild("CreateTerrainButton", true), "Released", "CreateTerrain");
+        SubscribeToEvent(brushSizeSlider, "DragEnd", "UpdateScaledBrush");
 
 
         LoadBrushes();
         LoadBrushes();
         Show();
         Show();
     }
     }
 
 
-    void CreateTerrain()
+    // Hide the window
+    void Hide()
     {
     {
-        Node@ node = CreateNode(LOCAL);
-        node.position = Vector3(0, 0, 0);
-        Component@ comp = node.CreateComponent("Terrain");
-        SelectComponent(comp, false);
+        window.visible = false;
     }
     }
 
 
     // Save all the terrains we have edited
     // Save all the terrains we have edited
@@ -139,36 +118,158 @@ class TerrainEditor
         terrainsEdited.Clear();
         terrainsEdited.Clear();
     }
     }
 
 
-    // Returns a brush based on its name (its filename minus the extension)
-    Image@ GetBrushImage(String brushName)
+    // Show the window
+    bool Show()
     {
     {
-        for (uint i = 0; i < brushes.length; ++i)
+        window.visible = true;
+        window.BringToFront();
+        return true;
+    }
+
+    // Update the UI
+    void UpdateDirty()
+    {
+        if (!dirty)
+            return;
+
+        CheckBox@ raiseLowerHeight = window.GetChild("RaiseLowerHeight", true);
+        CheckBox@ setHeight = window.GetChild("SetHeight", true);
+        CheckBox@ smoothHeight = window.GetChild("SmoothHeight", true);
+        //CheckBox@ paintBrush = window.GetChild("PaintBrush", true);
+        //CheckBox@ paintTrees = window.GetChild("PaintTrees", true);
+        //CheckBox@ paintFoliage = window.GetChild("PaintFoliage", true);
+
+        raiseLowerHeight.checked = (editMode == TERRAIN_EDITMODE_RAISELOWERHEIGHT) ? true : false;
+        setHeight.checked = (editMode == TERRAIN_EDITMODE_SETHEIGHT) ? true : false;
+        smoothHeight.checked = (editMode == TERRAIN_EDITMODE_SMOOTHHEIGHT) ? true : false;
+        //paintBrush.checked = (editMode == TERRAIN_EDITMODE_PAINTBRUSH) ? true : false;
+        //paintTrees.checked = (editMode == TERRAIN_EDITMODE_PAINTTREES) ? true : false;
+        //paintFoliage.checked = (editMode == TERRAIN_EDITMODE_PAINTFOLIAGE) ? true : false;
+
+        raiseLowerHeight.enabled = !raiseLowerHeight.checked;
+        setHeight.enabled = !setHeight.checked;
+        smoothHeight.enabled = !smoothHeight.checked;
+
+        ListView@ terrainBrushes = window.GetChild("BrushesContainer", true);
+
+        for (uint i = 0; i < terrainBrushes.numItems; ++i)
         {
         {
-            if (brushes[i].name == brushName)
-            {
-                return brushes[i];
-            }
+            CheckBox@ checkbox = cast<CheckBox>(terrainBrushes.items[i]);
+            checkbox.checked = terrainBrushes.items[i] is selectedBrush;
+            checkbox.enabled = !checkbox.checked;
         }
         }
 
 
-        return null;
+        dirty = false;
     }
     }
 
 
-    // Loads all the brushes from a hard-coded folder
-    void LoadBrushes()
+    // This gets called when we want to do something to a terrain
+    void Work(Terrain@ terrainComponent, Image@ terrainImage, IntVector2 position)
     {
     {
-        ListView@ terrainBrushes = window.GetChild("BrushesContainer", true);
+        SetSceneModified();
 
 
-        String brushesFileLocation = fileSystem.programDir + "Data/Textures/Editor/TerrainBrushes";
-        Array<String> files = fileSystem.ScanDir(brushesFileLocation, "*.*", SCAN_FILES, false);
+        // Add that terrain to the terrainsEdited if its not already in there
+        if (terrainsEdited.FindByRef(terrainComponent) == -1)
+            terrainsEdited.Push(terrainComponent);
 
 
-        for (uint i = 0; i < files.length; ++i)
+        // Only work if a brush is selected
+        if (selectedBrushImage is null || scaledSelectedBrushImage is null)
+            return;
+
+        switch (editMode)
         {
         {
-            terrainBrushes.AddItem(CreateBrush(brushesFileLocation + "/" + files[i]));
+        case TERRAIN_EDITMODE_RAISELOWERHEIGHT:
+            UpdateTerrainRaiseLower(terrainImage, position);
+            break;
+        case TERRAIN_EDITMODE_SETHEIGHT:
+            UpdateTerrainSetHeight(terrainImage, position);
+            break;
+        case TERRAIN_EDITMODE_SMOOTHHEIGHT:
+            UpdateTerrainSmooth(terrainImage, position);
+            break;
         }
         }
+
+        // Apply our changes
+        terrainComponent.ApplyHeightMap();
+    }
+
+    private uint NearestPowerOf2(uint value) {
+        if (value < 2)
+            return 2;
+
+        for (uint i = 1; i <= 2048; i *= 2)
+        {
+            if (value == i)
+                return i;
+
+            if (value < i || value > i * 2)
+                continue;
+
+            return value < (i + (i / 2)) ? i : i * 2;
+    	}
+
+        return 2048;
+    }
+
+    private void CreateTerrain()
+    {
+        String fileName = "Textures/heightmap-" + time.timeSinceEpoch  + ".png";
+        String fileLocation = fileSystem.programDir + "Data/" + fileName;
+
+        Node@ node = CreateNode(LOCAL);
+        node.position = Vector3(0, 0, 0);
+
+        LineEdit@ lineEdit = window.GetChild("CreateTerrainValue", true);
+
+        uint lineEditLength = lineEdit.text.Trimmed().ToUInt();
+
+        // The parse failed, so use a decent default
+        if (lineEditLength == 0)
+            lineEditLength = 1024;
+
+        Image@ image = Image();
+        uint length = NearestPowerOf2(lineEditLength) + 1;
+        image.SetSize(length, length, 3);
+
+        UpdateTerrainSetConstantHeight(image, 0);
+
+        image.SavePNG(fileLocation);
+
+        Terrain@ terrain = node.CreateComponent("Terrain");
+        terrain.heightMap = image;
+
+        Resource@ res = cache.GetResource("Image", fileLocation);
+
+        ResourceRef ref = ResourceRef();
+        ref.type = res.type;
+        ref.name = fileName;
+        terrain.SetAttribute("Height Map", Variant(ref));
+        terrain.ApplyAttributes();
+
+        SelectComponent(terrain, false);
+    }
+
+    // Utility function, returns the difference of the two numbers
+    private float Difference(float a, float b)
+    {
+        return (a > b) ? a - b : b - a;
+    }
+
+    // Returns a brush based on its name (its filename minus the extension)
+    private Image@ GetBrushImage(String brushName)
+    {
+        for (uint i = 0; i < brushes.length; ++i)
+        {
+            if (brushes[i].name == brushName)
+            {
+                return brushes[i];
+            }
+        }
+
+        return null;
     }
     }
 
 
     // Creates a brush element
     // Creates a brush element
-    UIElement@ CreateBrush(String fileLocation)
+    private UIElement@ LoadBrush(String fileLocation)
     {
     {
         Array<String> chunks = fileLocation.Split('/');
         Array<String> chunks = fileLocation.Split('/');
         Array<String> parts = chunks[chunks.length - 1].Split('.');
         Array<String> parts = chunks[chunks.length - 1].Split('.');
@@ -185,7 +286,7 @@ class TerrainEditor
         brush.defaultStyle = uiStyle;
         brush.defaultStyle = uiStyle;
         brush.style = "TerrainEditorCheckbox";
         brush.style = "TerrainEditorCheckbox";
         brush.SetFixedSize(64, 64);
         brush.SetFixedSize(64, 64);
-        SubscribeToEvent(brush, "Toggled", "BrushSelected");
+        SubscribeToEvent(brush, "Toggled", "OnBrushSelected");
 
 
         BorderImage@ icon = BorderImage("Icon");
         BorderImage@ icon = BorderImage("Icon");
         icon.defaultStyle = iconStyle;
         icon.defaultStyle = iconStyle;
@@ -197,286 +298,211 @@ class TerrainEditor
         return brush;
         return brush;
     }
     }
 
 
-    // Show the window
-    bool Show()
+    // Loads all the brushes from a hard-coded folder
+    private void LoadBrushes()
     {
     {
-        window.visible = true;
-        window.BringToFront();
-        return true;
+        ListView@ terrainBrushes = window.GetChild("BrushesContainer", true);
+
+        String brushesFileLocation = fileSystem.programDir + "Data/Textures/Editor/TerrainBrushes";
+        Array<String> files = fileSystem.ScanDir(brushesFileLocation, "*.*", SCAN_FILES, false);
+
+        for (uint i = 0; i < files.length; ++i)
+        {
+            terrainBrushes.AddItem(LoadBrush(brushesFileLocation + "/" + files[i]));
+        }
     }
     }
 
 
-    // Hide the window
-    void Hide()
+    private void OnEditModeSelected(StringHash eventType, VariantMap& eventData)
     {
     {
-        window.visible = false;
+        CheckBox@ edit = eventData["Element"].GetPtr();
+
+        if (!edit.checked)
+            return;
+
+        if (edit.name == "RaiseLowerHeight")
+            SetEditMode(TERRAIN_EDITMODE_RAISELOWERHEIGHT, "Raise or lower terrain");
+
+        else if (edit.name == "SetHeight")
+            SetEditMode(TERRAIN_EDITMODE_SETHEIGHT, "Set height to specified height");
+
+        else if (edit.name == "SmoothHeight")
+            SetEditMode(TERRAIN_EDITMODE_SMOOTHHEIGHT, "Smooth the terrain");
+
+        else if (edit.name == "PaintBrush")
+            SetEditMode(TERRAIN_EDITMODE_PAINTBRUSH, "Paint textures onto the terrain");
+
+        else if (edit.name == "PaintTrees")
+            SetEditMode(TERRAIN_EDITMODE_PAINTTREES, "Paint trees onto the terrain");
+
+        else if (edit.name == "PaintFoliage")
+            SetEditMode(TERRAIN_EDITMODE_PAINTFOLIAGE, "Paint foliage onto the terrain");
     }
     }
 
 
-    // This gets called when we want to do something to a terrain
-    void Work(Terrain@ terrainComponent, Image@ terrainImage, IntVector2 position)
+    private void OnBrushSelected(StringHash eventType, VariantMap& eventData)
     {
     {
-        SetSceneModified();
+        CheckBox@ checkbox = cast<CheckBox>(eventData["Element"].GetPtr());
+        if (checkbox.checked == false)
+            return;
+        selectedBrush = checkbox;
+        selectedBrushImage = GetBrushImage(selectedBrush.name);
+        UpdateScaledBrush();
+        dirty = true;
+    }
 
 
-        // Add that terrain to the terrainsEdited if its not already in there
-        if (terrainsEdited.FindByRef(terrainComponent) == -1)
-            terrainsEdited.Push(terrainComponent);
+    private void SetEditMode(uint mode, String description)
+    {
+        window.GetChild("BrushOpacityLabel", true).visible = (mode == TERRAIN_EDITMODE_RAISELOWERHEIGHT);
+        window.GetChild("BrushHeightLabel", true).visible = (mode == TERRAIN_EDITMODE_SETHEIGHT);
 
 
-        // Only work if a brush is selected
-        if (selectedBrushImage !is null){
-            uint brushImageWidth = selectedBrushImage.width;
-            uint brushImageHeight = selectedBrushImage.height;
+        window.GetChild("BrushOpacity", true).visible = (mode == TERRAIN_EDITMODE_RAISELOWERHEIGHT);
+        window.GetChild("BrushHeight", true).visible = (mode == TERRAIN_EDITMODE_SETHEIGHT);
 
 
-            switch (editMode)
-            {
-                case TERRAIN_EDITMODE_RAISELOWERHEIGHT: {
-                    // Iterate over the entire brush image
-                    for (int y = 0; y < brushImageHeight; ++y)
-                    {
-                        for (int x = 0; x < brushImageWidth; ++x)
-                        {
-                            // Get the current terrain height at that position (centred to the brush's size)
-                            IntVector2 pos = IntVector2(position.x + x - (brushImageWidth / 2), position.y + y - (brushImageHeight / 2));
-                            Color newColor = terrainImage.GetPixel(pos.x, pos.y);
-                            Color brushColor = selectedBrushImage.GetPixel(x, y);
-
-                            // lower or raise (respectively)
-                            float modifer = (input.keyDown[KEY_SHIFT]) ? -0.01f : 0.01f;
-
-                            // Now apply the brush to the terrain (we only use the alpha)
-                            newColor.r += brushColor.a * modifer;
-                            newColor.g += brushColor.a * modifer;
-                            newColor.b += brushColor.a * modifer;
-
-                            terrainImage.SetPixel(pos.x, pos.y, newColor);
-                        }
-                    }
-                } break;
-
-                case TERRAIN_EDITMODE_SETHEIGHT: {
-                    // The color we want to set the height to (this stays the same until targetColorSelected is false again)
-                    if (targetColorSelected == false)
-                    {
-                        targetColor = terrainImage.GetPixel(position.x, position.y);
-                        targetColorSelected = true;
-                    }
-
-                    // Iterate over the entire brush image
-                    for (int y = 0; y < brushImageHeight; ++y)
-                    {
-                        for (int x = 0; x < brushImageWidth; ++x)
-                        {
-                            // Get the current terrain height at that position (centred to the brush's size)
-                            IntVector2 pos = IntVector2(position.x + x - (brushImageWidth / 2), position.y + y - (brushImageHeight / 2));
-                            Color newColor = terrainImage.GetPixel(pos.x, pos.y);
-                            Color brushColor = selectedBrushImage.GetPixel(x, y);
-
-                            // Ease the height to the target height (using the brush alpha as the 'speed'), making sure the alpha isn't 0
-                            newColor.r += (targetColor.r - newColor.r) * brushColor.a;
-                            newColor.g += (targetColor.g - newColor.g) * brushColor.a;
-                            newColor.b += (targetColor.b - newColor.b) * brushColor.a;
-
-                            // Set it to target if its close enough
-                            if (Difference(targetColor.r, newColor.r) < 0.01f) newColor.r = targetColor.r;
-                            if (Difference(targetColor.g, newColor.g) < 0.01f) newColor.g = targetColor.g;
-                            if (Difference(targetColor.b, newColor.b) < 0.01f) newColor.b = targetColor.b;
-
-                            terrainImage.SetPixel(pos.x, pos.y, newColor);
-                        }
-                    }
-                } break;
-
-                case TERRAIN_EDITMODE_SMOOTHHEIGHT: {
-                    //IntRect rect = IntRect(position.x - (brushImageWidth / 2), position.y - (brushImageHeight / 2),
-                    //    position.x - (brushImageWidth / 2) + brushImageWidth, position.y - (brushImageHeight / 2) + brushImageHeight);
-
-                    //Image@ subImage = terrainImage.GetSubimage(rect);
-
-                    // Iterate over the entire brush image
-                    for (int y = 0; y < brushImageHeight; ++y)
-                    {
-                        for (int x = 0; x < brushImageWidth; ++x)
-                        {
-                            // Get the current terrain height at that position (centred to the brush's size)
-                            IntVector2 pos = IntVector2(position.x + x - (brushImageWidth / 2), position.y + y - (brushImageHeight / 2));
-
-                            // Make sure the pixel we're working on is atleast one pixel inside the terrainImage
-                            // as we need an adjacent pixel on all sides for the smoothing algorithm to work
-                            if (pos.x > 0 && pos.y > 0 && pos.x < terrainImage.width - 1 && pos.y < terrainImage.height - 1)
-                            {
-                                Color newColor = terrainImage.GetPixel(pos.x, pos.y);
-                                Color brushColor = selectedBrushImage.GetPixel(x, y);
-
-                                Array<Color> adjacentColors;
-
-                                adjacentColors.Push(terrainImage.GetPixel(pos.x + 1, pos.y + 1));
-                                adjacentColors.Push(terrainImage.GetPixel(pos.x + 1, pos.y));
-                                adjacentColors.Push(terrainImage.GetPixel(pos.x + 1, pos.y - 1));
-                                adjacentColors.Push(terrainImage.GetPixel(pos.x - 1, pos.y + 1));
-                                adjacentColors.Push(terrainImage.GetPixel(pos.x - 1, pos.y));
-                                adjacentColors.Push(terrainImage.GetPixel(pos.x - 1, pos.y - 1));
-                                adjacentColors.Push(terrainImage.GetPixel(pos.x, pos.y + 1));
-                                adjacentColors.Push(terrainImage.GetPixel(pos.x, pos.y));
-                                adjacentColors.Push(terrainImage.GetPixel(pos.x, pos.y - 1));
-
-                                float avgR = 0.0f;
-                                float avgG = 0.0f;
-                                float avgB = 0.0f;
-
-                                // Get the average of those pixels
-                                for (uint i = 0; i < adjacentColors.length; ++i)
-                                {
-                                    avgR += adjacentColors[i].r;
-                                    avgG += adjacentColors[i].g;
-                                    avgB += adjacentColors[i].b;
-                                }
-
-                                newColor.r = avgR / adjacentColors.length;
-                                newColor.g = avgG / adjacentColors.length;
-                                newColor.b = avgB / adjacentColors.length;
-
-                                Color originalColor = terrainImage.GetPixel(pos.x, pos.y);
-
-                                newColor.r = (Difference(originalColor.r, newColor.r) * brushColor.a) + Smaller(originalColor.r, newColor.r);
-                                newColor.g = (Difference(originalColor.g, newColor.g) * brushColor.a) + Smaller(originalColor.g, newColor.g);
-                                newColor.b = (Difference(originalColor.b, newColor.b) * brushColor.a) + Smaller(originalColor.b, newColor.b);
-
-                                terrainImage.SetPixel(position.x + x - (brushImageWidth / 2), position.y + y - (brushImageHeight / 2), newColor);
-                            }
-                        }
-                    }
-
-                    // Apply our changes to that actual terrain
-                    //for (int y = 0; y < subImage.height; ++y)
-                    //{
-                    //    for (int x = 0; x < subImage.width; ++x)
-                    //    {
-                    //        terrainImage.SetPixel(position.x + x - (brushImageWidth / 2), position.y + y - (brushImageHeight / 2), subImage.GetPixel(x, y));
-                    //    }
-                    //}
-
-                } break;
-            }
+        editMode = mode;
+        currentToolDescText.text = description;
+        dirty = true;
 
 
-            // Apply our changes
-            terrainComponent.ApplyHeightMap();
-        }
+        // force the window to resize its height to fit its children
+        window.height = 0;
     }
     }
 
 
     // Utility function, returns the smaller of the two numbers
     // Utility function, returns the smaller of the two numbers
-    float Smaller(float a, float b)
+    private float Smaller(float a, float b)
     {
     {
         return (a > b) ? b : a;
         return (a > b) ? b : a;
     }
     }
 
 
-    // Utility function, returns the difference of the two numbers
-    float Difference(float a, float b)
+    private void UpdateScaledBrush()
     {
     {
-        return (a > b) ? a - b : b - a;
+        if (selectedBrushImage is null)
+            return;
+        float size = (brushSizeSlider.value / 25) + 0.5;
+        Print(brushSizeSlider.value);
+        Print(size);
+        scaledSelectedBrushImage = selectedBrushImage.GetSubimage(IntRect(0, 0, selectedBrushImage.width, selectedBrushImage.height));
+        scaledSelectedBrushImage.Resize(int(selectedBrushImage.width * size), int(selectedBrushImage.height * size));
     }
     }
 
 
-    // Update the UI
-    void UpdateDirty()
+    private void UpdateTerrainRaiseLower(Image@ terrainImage, IntVector2 position)
     {
     {
-        if (!dirty)
-            return;
+        uint brushImageWidth = scaledSelectedBrushImage.width;
+        uint brushImageHeight = scaledSelectedBrushImage.height;
 
 
-        CheckBox@ raiseLowerHeight = window.GetChild("RaiseLowerHeight", true);
-        CheckBox@ setHeight = window.GetChild("SetHeight", true);
-        CheckBox@ smoothHeight = window.GetChild("SmoothHeight", true);
-        CheckBox@ paintBrush = window.GetChild("PaintBrush", true);
-        CheckBox@ paintTrees = window.GetChild("PaintTrees", true);
-        CheckBox@ paintFoliage = window.GetChild("PaintFoliage", true);
-
-        raiseLowerHeight.checked = (editMode == TERRAIN_EDITMODE_RAISELOWERHEIGHT) ? true : false;
-        setHeight.checked = (editMode == TERRAIN_EDITMODE_SETHEIGHT) ? true : false;
-        smoothHeight.checked = (editMode == TERRAIN_EDITMODE_SMOOTHHEIGHT) ? true : false;
-        paintBrush.checked = (editMode == TERRAIN_EDITMODE_PAINTBRUSH) ? true : false;
-        paintTrees.checked = (editMode == TERRAIN_EDITMODE_PAINTTREES) ? true : false;
-        paintFoliage.checked = (editMode == TERRAIN_EDITMODE_PAINTFOLIAGE) ? true : false;
+        // lower or raise (respectively), multiply this by the brush opacity
+        float opacity = brushOpacitySlider.value / 25;
+        float modifier = (input.keyDown[KEY_SHIFT] ? -opacity : opacity) * 0.05;
 
 
-        ListView@ terrainBrushes = window.GetChild("BrushesContainer", true);
-
-        for (uint i = 0; i < terrainBrushes.numItems; ++i)
+        // Iterate over the entire brush image
+        for (int y = 0; y < brushImageHeight; ++y)
         {
         {
-            if (terrainBrushes.items[i] !is selectedBrush)
+            for (int x = 0; x < brushImageWidth; ++x)
             {
             {
-                CheckBox@ checkbox = cast<CheckBox>(terrainBrushes.items[i]);
-                checkbox.checked = false;
+                // Get the current terrain height at that position (centred to the brush's size)
+                IntVector2 pos = IntVector2(position.x + x - (brushImageWidth / 2), position.y + y - (brushImageHeight / 2));
+                Color newColor = terrainImage.GetPixel(pos.x, pos.y);
+                Color brushColor = scaledSelectedBrushImage.GetPixel(x, y);
+                // Now apply the brush to the terrain (we only use the alpha)
+                newColor.r += brushColor.a * modifier;
+                newColor.g += brushColor.a * modifier;
+                newColor.b += brushColor.a * modifier;
+                terrainImage.SetPixel(pos.x, pos.y, newColor);
             }
             }
         }
         }
 
 
-        dirty = false;
+        // Smooth the terrain a bit
+        UpdateTerrainSmooth(terrainImage, position);
     }
     }
 
 
-    // The next 7 functions are callbacks for the Terrain Editors UI
-
-    void EditModeRaiseLowerHeight(StringHash eventType, VariantMap& eventData)
+    private void UpdateTerrainSmooth(Image@ terrainImage, IntVector2 position)
     {
     {
-        CheckBox@ edit = eventData["Element"].GetPtr();
-        if (edit.checked){
-            editMode = TERRAIN_EDITMODE_RAISELOWERHEIGHT;
-            currentToolDescText.text = "Raise or lower terrain";
-        }
-        dirty = true;
-    }
+        uint brushImageWidth = scaledSelectedBrushImage.width;
+        uint brushImageHeight = scaledSelectedBrushImage.height;
 
 
-    void EditModeSetHeight(StringHash eventType, VariantMap& eventData)
-    {
-        CheckBox@ edit = eventData["Element"].GetPtr();
-        if (edit.checked){
-            editMode = TERRAIN_EDITMODE_SETHEIGHT;
-            currentToolDescText.text = "Set height to specified height";
+        // Iterate over the entire brush image
+        for (int y = 0; y < brushImageHeight; ++y)
+        {
+            for (int x = 0; x < brushImageWidth; ++x)
+            {
+                Color brushColor = scaledSelectedBrushImage.GetPixel(x, y);
+
+                // Only take opaque pixels into consideration for now
+                if (brushColor.a == 0)
+                    continue;
+
+                // Get the current terrain height at that position (centred to the brush's size)
+                IntVector2 pos = IntVector2(position.x + x - (brushImageWidth / 2), position.y + y - (brushImageHeight / 2));
+
+                // Make sure the pixel we're working on is atleast one pixel inside the terrainImage
+                // as we need an adjacent pixel on all sides for the smoothing algorithm to work
+                if (pos.x < 0 || pos.y < 0 || pos.x > terrainImage.width - 1 || pos.y > terrainImage.height - 1)
+                    continue;
+
+                Color brp = terrainImage.GetPixel(pos.x + 1, pos.y + 1); // bottomRightPixel
+                Color rp = terrainImage.GetPixel(pos.x + 1, pos.y);      // rightPixel
+                Color trp = terrainImage.GetPixel(pos.x + 1, pos.y - 1); // topRightPixel
+                Color blp = terrainImage.GetPixel(pos.x - 1, pos.y + 1); // bottomLeftPixel
+                Color lp = terrainImage.GetPixel(pos.x - 1, pos.y);      // leftPixel
+                Color tlp = terrainImage.GetPixel(pos.x - 1, pos.y - 1); // topLeftPixel
+                Color bp = terrainImage.GetPixel(pos.x, pos.y + 1);      // bottomPixel
+                Color cp = terrainImage.GetPixel(pos.x, pos.y);          // centerPixel
+                Color tp = terrainImage.GetPixel(pos.x, pos.y - 1);      // topPixel
+
+                Color avgColor = Color(
+                    ((brp.r + rp.r + trp.r + blp.r + lp.r + tlp.r + bp.r + cp.r + tp.r) / 9),
+                    ((brp.g + rp.g + trp.g + blp.g + lp.g + tlp.g + bp.g + cp.g + tp.g) / 9),
+                    ((brp.b + rp.b + trp.b + blp.b + lp.b + tlp.b + bp.b + cp.b + tp.b) / 9)
+                );
+
+                Color newColor = Color(
+                    (Difference(cp.r, avgColor.r) * brushColor.a) + Smaller(cp.r, avgColor.r),
+                    (Difference(cp.g, avgColor.g) * brushColor.a) + Smaller(cp.g, avgColor.g),
+                    (Difference(cp.b, avgColor.b) * brushColor.a) + Smaller(cp.b, avgColor.b)
+                );
+
+                terrainImage.SetPixel(position.x + x - (brushImageWidth / 2), position.y + y - (brushImageHeight / 2), avgColor);
+            }
         }
         }
-        dirty = true;
     }
     }
 
 
-    void EditModeSmoothHeight(StringHash eventType, VariantMap& eventData)
+    private void UpdateTerrainSetHeight(Image@ terrainImage, IntVector2 position)
     {
     {
-        CheckBox@ edit = eventData["Element"].GetPtr();
-        if (edit.checked){
-            editMode = TERRAIN_EDITMODE_SMOOTHHEIGHT;
-            currentToolDescText.text = "Smooth the terrain";
-        }
-        dirty = true;
-    }
+        uint brushImageWidth = scaledSelectedBrushImage.width;
+        uint brushImageHeight = scaledSelectedBrushImage.height;
 
 
-    void EditModePaintBrush(StringHash eventType, VariantMap& eventData)
-    {
-        CheckBox@ edit = eventData["Element"].GetPtr();
-        if (edit.checked){
-            editMode = TERRAIN_EDITMODE_PAINTBRUSH;
-            currentToolDescText.text = "Paint textures onto the terrain";
-        }
-        dirty = true;
-    }
+        float targetHeight = brushHeightSlider.value / 25;
 
 
-    void EditModePaintTrees(StringHash eventType, VariantMap& eventData)
-    {
-        CheckBox@ edit = eventData["Element"].GetPtr();
-        if (edit.checked){
-            editMode = TERRAIN_EDITMODE_PAINTTREES;
-            currentToolDescText.text = "Paint trees onto the terrain";
+        // Iterate over the entire brush image
+        for (int y = 0; y < brushImageHeight; ++y)
+        {
+            for (int x = 0; x < brushImageWidth; ++x)
+            {
+                // Get the current terrain height at that position (centred to the brush's size)
+                IntVector2 pos = IntVector2(position.x + x - (brushImageWidth / 2), position.y + y - (brushImageHeight / 2));
+                Color newColor = terrainImage.GetPixel(pos.x, pos.y);
+                Color brushColor = scaledSelectedBrushImage.GetPixel(x, y);
+                // Ease the height to the target height (using the brush alpha as the 'speed'), making sure the alpha isn't 0
+                newColor.r += (targetHeight - newColor.r) * brushColor.a;
+                newColor.g += (targetHeight - newColor.g) * brushColor.a;
+                newColor.b += (targetHeight - newColor.b) * brushColor.a;
+                // Set it to target if its close enough
+                if (Difference(targetHeight, newColor.r) < 0.01f) newColor.r = targetHeight;
+                if (Difference(targetHeight, newColor.g) < 0.01f) newColor.g = targetHeight;
+                if (Difference(targetHeight, newColor.b) < 0.01f) newColor.b = targetHeight;
+                terrainImage.SetPixel(pos.x, pos.y, newColor);
+            }
         }
         }
-        dirty = true;
     }
     }
 
 
-    void EditModePaintFoliage(StringHash eventType, VariantMap& eventData)
+    private void UpdateTerrainSetConstantHeight(Image@ terrainImage, float height)
     {
     {
-        CheckBox@ edit = eventData["Element"].GetPtr();
-        if (edit.checked){
-            editMode = TERRAIN_EDITMODE_PAINTFOLIAGE;
-            currentToolDescText.text = "Paint foliage onto the terrain";
+        height = Clamp(height, 0.0, 1.0);
+        Color newColor = Color(height, height, height);
+        // Iterate over the entire brush image
+        for (int y = 0; y < terrainImage.height; ++y)
+        {
+            for (int x = 0; x < terrainImage.width; ++x)
+            {
+                terrainImage.SetPixel(x, y, newColor);
+            }
         }
         }
-        dirty = true;
-    }
-
-    void BrushSelected(StringHash eventType, VariantMap& eventData)
-    {
-        CheckBox@ checkbox = cast<CheckBox>(eventData["Element"].GetPtr());
-        if (checkbox.checked == false)
-            return;
-        selectedBrush = checkbox;
-        selectedBrushImage = GetBrushImage(selectedBrush.name);
-        Print("Selected Brush: " + selectedBrush.name);
-        dirty = true;
     }
     }
 }
 }

+ 108 - 30
bin/Data/UI/EditorTerrainWindow.xml

@@ -7,7 +7,7 @@
     <attribute name="Layout Spacing" value="4" />
     <attribute name="Layout Spacing" value="4" />
     <attribute name="Layout Border" value="6 6 6 6" />
     <attribute name="Layout Border" value="6 6 6 6" />
 
 
-    <!-- the drag bar -->
+    <!-- drag bar -->
     <element>
     <element>
         <attribute name="Name" value="Title Bar" />
         <attribute name="Name" value="Title Bar" />
         <attribute name="Min Size" value="184 16" />
         <attribute name="Min Size" value="184 16" />
@@ -15,10 +15,6 @@
         <attribute name="Layout Mode" value="Horizontal" />
         <attribute name="Layout Mode" value="Horizontal" />
         <attribute name="Layout Spacing" value="8" />
         <attribute name="Layout Spacing" value="8" />
         <element type="Text">
         <element type="Text">
-            <attribute name="Top Left Color" value="0.85 0.85 0.85 1" />
-            <attribute name="Top Right Color" value="0.85 0.85 0.85 1" />
-            <attribute name="Bottom Left Color" value="0.85 0.85 0.85 1" />
-            <attribute name="Bottom Right Color" value="0.85 0.85 0.85 1" />
             <attribute name="Text" value="Terrain Editor" />
             <attribute name="Text" value="Terrain Editor" />
             <attribute name="Auto Localizable" value="false" />
             <attribute name="Auto Localizable" value="false" />
         </element>
         </element>
@@ -28,46 +24,48 @@
     </element>
     </element>
     <element type="BorderImage" style="EditorDivider" />
     <element type="BorderImage" style="EditorDivider" />
 
 
-    <!-- the actions bar -->
+    <!-- actions bar -->
     <element type="Text">
     <element type="Text">
         <attribute name="Name" value="ActionsText" />
         <attribute name="Name" value="ActionsText" />
-        <attribute name="Top Left Color" value="0.85 0.85 0.85 1" />
-        <attribute name="Top Right Color" value="0.85 0.85 0.85 1" />
-        <attribute name="Bottom Left Color" value="0.85 0.85 0.85 1" />
-        <attribute name="Bottom Right Color" value="0.85 0.85 0.85 1" />
         <attribute name="Text" value="Actions" />
         <attribute name="Text" value="Actions" />
         <attribute name="Text Alignment" value="Left" />
         <attribute name="Text Alignment" value="Left" />
         <attribute name="Auto Localizable" value="false" />
         <attribute name="Auto Localizable" value="false" />
     </element>
     </element>
-    <element type="Button">
-        <attribute name="Name" value="CreateTerrainButton" />
-        <attribute name="Position" value="104 0" />
+    <element>
         <attribute name="Layout Mode" value="Horizontal" />
         <attribute name="Layout Mode" value="Horizontal" />
-        <attribute name="Layout Border" value="1 1 1 1" />
-        <element type="Text">
-            <attribute name="Top Left Color" value="0.85 0.85 0.85 1" />
-            <attribute name="Top Right Color" value="0.85 0.85 0.85 1" />
-            <attribute name="Bottom Left Color" value="0.85 0.85 0.85 1" />
-            <attribute name="Bottom Right Color" value="0.85 0.85 0.85 1" />
-            <attribute name="Text" value="Create new terrain" />
-            <attribute name="Text Alignment" value="Center" />
-            <attribute name="Auto Localizable" value="false" />
+        <attribute name="Min Size" value="0 34" />
+        <attribute name="Max Size" value="999999 34" />
+        <element type="LineEdit">
+            <attribute name="Name" value="CreateTerrainValue" />
+            <attribute name="Min Size" value="40 20" />
+            <attribute name="Max Size" value="40 20" />
+        </element>
+        <element type="Button">
+            <attribute name="Name" value="CreateTerrainButton" />
+            <attribute name="Position" value="104 0" />
+            <attribute name="Layout Mode" value="Horizontal" />
+            <attribute name="Layout Border" value="1 1 1 1" />
+            <attribute name="Min Size" value="0 20" />
+            <attribute name="Max Size" value="130 20" />
+            <element type="Text">
+                <attribute name="Text" value="Create Terrain" />
+                <attribute name="Text Alignment" value="Center" />
+                <attribute name="Auto Localizable" value="false" />
+            </element>
         </element>
         </element>
     </element>
     </element>
 
 
-    <!-- The tool bar -->
+    <!-- toolbar -->
     <element type="Text">
     <element type="Text">
         <attribute name="Name" value="ToolsText" />
         <attribute name="Name" value="ToolsText" />
-        <attribute name="Top Left Color" value="0.85 0.85 0.85 1" />
-        <attribute name="Top Right Color" value="0.85 0.85 0.85 1" />
-        <attribute name="Bottom Left Color" value="0.85 0.85 0.85 1" />
-        <attribute name="Bottom Right Color" value="0.85 0.85 0.85 1" />
         <attribute name="Text" value="Tools" />
         <attribute name="Text" value="Tools" />
         <attribute name="Text Alignment" value="Left" />
         <attribute name="Text Alignment" value="Left" />
         <attribute name="Auto Localizable" value="false" />
         <attribute name="Auto Localizable" value="false" />
     </element>
     </element>
     <element>
     <element>
         <attribute name="Layout Mode" value="Horizontal" />
         <attribute name="Layout Mode" value="Horizontal" />
+        <attribute name="Min Size" value="0 34" />
+        <attribute name="Max Size" value="999999 34" />
         <element type="Checkbox" style="TerrainEditorCheckbox">
         <element type="Checkbox" style="TerrainEditorCheckbox">
             <attribute name="Name" value="RaiseLowerHeight" />
             <attribute name="Name" value="RaiseLowerHeight" />
             <element type="BorderImage">
             <element type="BorderImage">
@@ -98,7 +96,8 @@
                 <attribute name="Max Size" value="30 30" />
                 <attribute name="Max Size" value="30 30" />
             </element>
             </element>
         </element>
         </element>
-        <element type="Checkbox" style="TerrainEditorCheckbox">
+        <!-- Disable these for now -->
+        <!--<element type="Checkbox" style="TerrainEditorCheckbox">
             <attribute name="Name" value="PaintBrush" />
             <attribute name="Name" value="PaintBrush" />
             <element type="BorderImage">
             <element type="BorderImage">
                 <attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
                 <attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
@@ -112,7 +111,6 @@
             <attribute name="Name" value="PaintTrees" />
             <attribute name="Name" value="PaintTrees" />
             <element type="BorderImage">
             <element type="BorderImage">
                 <attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
                 <attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
-                <!--<attribute name="Image Rect" value="128 224 158 254" />--><!-- TODO: Make icon for this -->
                 <attribute name="Image Rect" value="96 224 126 254" />
                 <attribute name="Image Rect" value="96 224 126 254" />
                 <attribute name="Position" value="2 2" />
                 <attribute name="Position" value="2 2" />
                 <attribute name="Min Size" value="30 30" />
                 <attribute name="Min Size" value="30 30" />
@@ -123,13 +121,93 @@
             <attribute name="Name" value="PaintFoliage" />
             <attribute name="Name" value="PaintFoliage" />
             <element type="BorderImage">
             <element type="BorderImage">
                 <attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
                 <attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
-                <!--<attribute name="Image Rect" value="160 224 190 254" />--><!-- TODO: Make icon for this -->
                 <attribute name="Image Rect" value="96 224 126 254" />
                 <attribute name="Image Rect" value="96 224 126 254" />
                 <attribute name="Position" value="2 2" />
                 <attribute name="Position" value="2 2" />
                 <attribute name="Min Size" value="30 30" />
                 <attribute name="Min Size" value="30 30" />
                 <attribute name="Max Size" value="30 30" />
                 <attribute name="Max Size" value="30 30" />
             </element>
             </element>
+        </element>-->
+    </element>
+
+    <!-- current tool description -->
+    <element type="BorderImage" style="EditorToolBar">
+        <attribute name="Name" value="CurrentToolDesc" />
+        <attribute name="Layout Mode" value="Horizontal" />
+        <attribute name="Layout Spacing" value="4" />
+        <attribute name="Horizontal Alignment" value="Center" />
+        <attribute name="Min Size" value="0 30" />
+        <attribute name="Max Size" value="999999 30" />
+        <element type="Text">
+            <attribute name="Name" value="CurrentToolDescText" />
+            <attribute name="Text" value="Raise or lower terrain using the current brush" />
+            <attribute name="Auto Localizable" value="false" />
+            <attribute name="Position" value="16 16" />
         </element>
         </element>
     </element>
     </element>
 
 
+    <!-- brushes -->
+    <element type="Text">
+        <attribute name="Name" value="Brushes" />
+        <attribute name="Text" value="Brushes" />
+        <attribute name="Text Alignment" value="Left" />
+        <attribute name="Auto Localizable" value="false" />
+    </element>
+    <element type="ListView" style="ListView">
+        <attribute name="Name" value="BrushesContainer" />
+    </element>
+
+    <!-- settings -->
+    <element type="Text">
+        <attribute name="Text" value="Settings" />
+        <attribute name="Text Alignment" value="Left" />
+        <attribute name="Auto Localizable" value="false" />
+        <attribute name="Position" value="0 16" />
+    </element>
+    <element type="BorderImage" style="EditorToolBar">
+        <attribute name="Name" value="SettingsArea" />
+        <attribute name="Layout Mode" value="Vertical" />
+        <attribute name="Layout Spacing" value="4" />
+        <attribute name="Horizontal Alignment" value="Center" />
+        <element type="Text">
+            <attribute name="Name" value="BrushSizeLabel" />
+            <attribute name="Text" value="Brush Size" />
+            <attribute name="Text Alignment" value="Left" />
+            <attribute name="Auto Localizable" value="false" />
+        </element>
+        <element type="Slider">
+            <attribute name="Name" value="BrushSize" />
+            <attribute name="Min Size" value="100 10" />
+            <attribute name="Max Size" value="999999 10" />
+            <attribute name="Range" value="25" />
+            <attribute name="Value" value="12.5" />
+        </element>
+        <element type="Text">
+            <attribute name="Name" value="BrushOpacityLabel" />
+            <attribute name="Text" value="Brush Opacity" />
+            <attribute name="Text Alignment" value="Left" />
+            <attribute name="Auto Localizable" value="false" />
+        </element>
+        <element type="Slider">
+            <attribute name="Name" value="BrushOpacity" />
+            <attribute name="Min Size" value="100 10" />
+            <attribute name="Max Size" value="999999 10" />
+            <attribute name="Range" value="25" />
+            <attribute name="Value" value="12.5" />
+            <attribute name="Visible" value="false" />
+        </element>
+        <element type="Text">
+            <attribute name="Name" value="BrushHeightLabel" />
+            <attribute name="Text" value="Brush Height" />
+            <attribute name="Text Alignment" value="Left" />
+            <attribute name="Auto Localizable" value="false" />
+        </element>
+        <element type="Slider">
+            <attribute name="Name" value="BrushHeight" />
+            <attribute name="Min Size" value="100 10" />
+            <attribute name="Max Size" value="999999 10" />
+            <attribute name="Range" value="25" />
+            <attribute name="Value" value="12.5" />
+            <attribute name="Visible" value="false" />
+        </element>
+    </element>
 </element>
 </element>