<commit_before>
// Urho3D terrain editor

const uint TERRAIN_EDITMODE_RAISELOWERHEIGHT = 0, TERRAIN_EDITMODE_SETHEIGHT = 1, TERRAIN_EDITMODE_SMOOTHHEIGHT = 3,
      TERRAIN_EDITMODE_PAINTBRUSH = 4, TERRAIN_EDITMODE_PAINTTREES = 5, TERRAIN_EDITMODE_PAINTFOLIAGE = 6;

funcdef bool TerrainEditorShowCallback();

class TerrainEditorUpdateChanges {
    IntVector2 offset;
    Image@ oldImage;
    Image@ newImage;
}

class TerrainEditorBrushVisualizer
{
    Node@ node;
    CustomGeometry@ customGeometry;
    private bool addedToOctree = false;

    void Create()
    {
        node = Node();
        customGeometry = node.CreateComponent("CustomGeometry");
        customGeometry.numGeometries = 1;
        customGeometry.material = cache.GetResource("Material", "Materials/VColUnlit.xml");
        customGeometry.occludee = false;
        customGeometry.enabled = true;
    }

    void Hide()
    {
        node.enabled = false;
        addedToOctree = false;
    }

    void Update(Terrain@ terrainComponent, Vector3 position, float radius)
    {
        node.enabled = true;
        node.position = Vector3(position.x, 0, position.z);

        // Generate the circle
        customGeometry.BeginGeometry(0, LINE_STRIP);
        for (uint i = 0; i < 364; i += 4)
        {
            float angle = i * M_PI / 180;
            float x = radius * Cos(angle / 0.0174532925);
            float z = radius * Sin(angle / 0.0174532925);
            float y = terrainComponent.GetHeight(Vector3(position.x + x, 0, position.z + z));
            customGeometry.DefineVertex(Vector3(x, y + 0.25, z));
            customGeometry.DefineColor(Color(0, 1, 0));
        }
        customGeometry.Commit();

        if (editorScene.octree !is null && addedToOctree == false)
        {
            editorScene.octree.AddManualDrawable(customGeometry);
            addedToOctree = true;
        }
    }
}

class TerrainEditor
{
    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 TerrainEditorBrushVisualizer brushVisualizer;

    private Array<Terrain@> terrainsEdited;

    private Color targetColor;
    bool targetColorSelected = false;

    // Create the Terrain Editor window and initialize it
    void Create()
    {
        if (window !is null)
            return;

        window = LoadEditorUI("UI/EditorTerrainWindow.xml");
        ui.root.AddChild(window);
        window.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.contentElement.layoutMode = LM_HORIZONTAL;
        brushesContainer.SetFixedHeight(84);

        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.SetPosition(ui.root.width - 10 - window.width, attributeInspectorWindow.position.y + attributeInspectorWindow.height + 10);

        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("CreateTerrainButton", true), "Released", "CreateTerrain");
        SubscribeToEvent(brushSizeSlider, "DragEnd", "UpdateScaledBrush");

        LoadBrushes();
        Show();

        brushVisualizer.Create();
    }

    // Hide the window
    void Hide()
    {
        window.visible = false;
    }

    void HideBrushVisualizer()
    {
        brushVisualizer.Hide();
    }

    void UpdateBrushVisualizer(Terrain@ terrainComponent, Vector3 position)
    {
        if (scaledSelectedBrushImage is null)
        {
            brushVisualizer.Hide();
            return;
        }
        brushVisualizer.Update(terrainComponent, position, scaledSelectedBrushImage.width / 2);
    }

    // Save all the terrains we have edited
    void Save()
    {
        for (uint i = 0; i < terrainsEdited.length; ++i)
        {
            // Make sure its not null (it may have been deleted since last save)
            if (terrainsEdited[i] !is null)
            {
                String fileLocation = fileSystem.programDir + "Data/" + terrainsEdited[i].GetAttribute("Height Map").GetResourceRef().name;

                Array<String> chunks = fileLocation.Split('/');
                Array<String> parts = chunks[chunks.length - 1].Split('.');
                String fileType = parts[1];

                if (fileType == "png")
                {
                    terrainsEdited[i].heightMap.SavePNG(fileLocation);
                }
                else if (fileType == "jpg")
                {
                    // Save with the highest quality
                    terrainsEdited[i].heightMap.SaveJPG(fileLocation, 100);
                }
                else if (fileType == "bmp")
                {
                    terrainsEdited[i].heightMap.SaveBMP(fileLocation);
                }
                else if (fileType == "tga")
                {
                    terrainsEdited[i].heightMap.SaveTGA(fileLocation);
                }
            }
        }
        // Clean up terrainsEdited array by clearing it
        // (this will remove any terrains that may have been deleted)
        terrainsEdited.Clear();
    }

    // Show the window
    bool Show()
    {
        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)
        {
            CheckBox@ checkbox = cast<CheckBox>(terrainBrushes.items[i]);
            checkbox.checked = terrainBrushes.items[i] is selectedBrush;
            checkbox.enabled = !checkbox.checked;
        }

        dirty = false;
    }

    // This gets called when we want to do something to a terrain
    void Work(Terrain@ terrainComponent, Vector3 position)
    {
        // Only work if a brush is selected
        if (selectedBrushImage is null || scaledSelectedBrushImage is null)
            return;

        SetSceneModified();

        // Add that terrain to the terrainsEdited if its not already in there
        if (terrainsEdited.FindByRef(terrainComponent) == -1)
            terrainsEdited.Push(terrainComponent);

        TerrainEditorUpdateChanges@ updateChanges = TerrainEditorUpdateChanges();

        IntVector2 pos = terrainComponent.WorldToHeightMap(position);

        switch (editMode)
        {
        case TERRAIN_EDITMODE_RAISELOWERHEIGHT:
            UpdateTerrainRaiseLower(terrainComponent.heightMap, pos, updateChanges);
            break;
        case TERRAIN_EDITMODE_SETHEIGHT:
            UpdateTerrainSetHeight(terrainComponent.heightMap, pos, updateChanges);
            break;
        case TERRAIN_EDITMODE_SMOOTHHEIGHT:
            UpdateTerrainSmooth(terrainComponent.heightMap, pos, updateChanges);
            break;
        }

        terrainComponent.ApplyHeightMap();

        UpdateBrushVisualizer(terrainComponent, position);

        ModifyTerrainAction action;
        action.Define(terrainComponent, updateChanges.offset, updateChanges.oldImage, updateChanges.newImage);
        SaveEditAction(action);
    }

    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
    private UIElement@ LoadBrush(String fileLocation)
    {
        Array<String> chunks = fileLocation.Split('/');
        Array<String> parts = chunks[chunks.length - 1].Split('.');

        // We use this when editing the terrain
        Image@ image = cache.GetResource("Image", fileLocation);
        image.name = parts[0];
        brushes.Push(image);

        // This is for the brush icon
        Texture2D@ texture = cache.GetResource("Texture2D", fileLocation);

        CheckBox@ brush = CheckBox(parts[0]);
        brush.defaultStyle = uiStyle;
        brush.style = "TerrainEditorCheckbox";
        brush.SetFixedSize(64, 64);
        SubscribeToEvent(brush, "Toggled", "OnBrushSelected");

        BorderImage@ icon = BorderImage("Icon");
        icon.defaultStyle = iconStyle;
        icon.texture = texture;
        icon.imageRect = IntRect(0, 0, texture.width, texture.height);
        icon.SetFixedSize(64, 64);
        brush.AddChild(icon);

        return brush;
    }

    // Loads all the brushes from a hard-coded folder
    private void LoadBrushes()
    {
        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]));
        }
    }

    private void OnEditModeSelected(StringHash eventType, VariantMap& eventData)
    {
        CheckBox@ edit = eventData["Element"].GetPtr();

        if (!edit.checked)<commit_msg>Fix use of fileSystem.programDir in the terrain editor. Use the resource system / scene resource path instead. Closes #1852.<commit_after>
// Urho3D terrain editor

const uint TERRAIN_EDITMODE_RAISELOWERHEIGHT = 0, TERRAIN_EDITMODE_SETHEIGHT = 1, TERRAIN_EDITMODE_SMOOTHHEIGHT = 3,
      TERRAIN_EDITMODE_PAINTBRUSH = 4, TERRAIN_EDITMODE_PAINTTREES = 5, TERRAIN_EDITMODE_PAINTFOLIAGE = 6;

funcdef bool TerrainEditorShowCallback();

class TerrainEditorUpdateChanges {
    IntVector2 offset;
    Image@ oldImage;
    Image@ newImage;
}

class TerrainEditorBrushVisualizer
{
    Node@ node;
    CustomGeometry@ customGeometry;
    private bool addedToOctree = false;

    void Create()
    {
        node = Node();
        customGeometry = node.CreateComponent("CustomGeometry");
        customGeometry.numGeometries = 1;
        customGeometry.material = cache.GetResource("Material", "Materials/VColUnlit.xml");
        customGeometry.occludee = false;
        customGeometry.enabled = true;
    }

    void Hide()
    {
        node.enabled = false;
        addedToOctree = false;
    }

    void Update(Terrain@ terrainComponent, Vector3 position, float radius)
    {
        node.enabled = true;
        node.position = Vector3(position.x, 0, position.z);

        // Generate the circle
        customGeometry.BeginGeometry(0, LINE_STRIP);
        for (uint i = 0; i < 364; i += 4)
        {
            float angle = i * M_PI / 180;
            float x = radius * Cos(angle / 0.0174532925);
            float z = radius * Sin(angle / 0.0174532925);
            float y = terrainComponent.GetHeight(Vector3(position.x + x, 0, position.z + z));
            customGeometry.DefineVertex(Vector3(x, y + 0.25, z));
            customGeometry.DefineColor(Color(0, 1, 0));
        }
        customGeometry.Commit();

        if (editorScene.octree !is null && addedToOctree == false)
        {
            editorScene.octree.AddManualDrawable(customGeometry);
            addedToOctree = true;
        }
    }
}

class TerrainEditor
{
    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 TerrainEditorBrushVisualizer brushVisualizer;

    private Array<Terrain@> terrainsEdited;

    private Color targetColor;
    bool targetColorSelected = false;

    // Create the Terrain Editor window and initialize it
    void Create()
    {
        if (window !is null)
            return;

        window = LoadEditorUI("UI/EditorTerrainWindow.xml");
        ui.root.AddChild(window);
        window.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.contentElement.layoutMode = LM_HORIZONTAL;
        brushesContainer.SetFixedHeight(84);

        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.SetPosition(ui.root.width - 10 - window.width, attributeInspectorWindow.position.y + attributeInspectorWindow.height + 10);

        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("CreateTerrainButton", true), "Released", "CreateTerrain");
        SubscribeToEvent(brushSizeSlider, "DragEnd", "UpdateScaledBrush");

        LoadBrushes();
        Show();

        brushVisualizer.Create();
    }

    // Hide the window
    void Hide()
    {
        window.visible = false;
    }

    void HideBrushVisualizer()
    {
        brushVisualizer.Hide();
    }

    void UpdateBrushVisualizer(Terrain@ terrainComponent, Vector3 position)
    {
        if (scaledSelectedBrushImage is null)
        {
            brushVisualizer.Hide();
            return;
        }
        brushVisualizer.Update(terrainComponent, position, scaledSelectedBrushImage.width / 2);
    }

    // Save all the terrains we have edited
    void Save()
    {
        for (uint i = 0; i < terrainsEdited.length; ++i)
        {
            // Make sure its not null (it may have been deleted since last save)
            if (terrainsEdited[i] !is null)
            {
                String fileLocation = sceneResourcePath + terrainsEdited[i].GetAttribute("Height Map").GetResourceRef().name;

                Array<String> chunks = fileLocation.Split('/');
                Array<String> parts = chunks[chunks.length - 1].Split('.');
                String fileType = parts[1];

                if (fileType == "png")
                {
                    terrainsEdited[i].heightMap.SavePNG(fileLocation);
                }
                else if (fileType == "jpg")
                {
                    // Save with the highest quality
                    terrainsEdited[i].heightMap.SaveJPG(fileLocation, 100);
                }
                else if (fileType == "bmp")
                {
                    terrainsEdited[i].heightMap.SaveBMP(fileLocation);
                }
                else if (fileType == "tga")
                {
                    terrainsEdited[i].heightMap.SaveTGA(fileLocation);
                }
            }
        }
        // Clean up terrainsEdited array by clearing it
        // (this will remove any terrains that may have been deleted)
        terrainsEdited.Clear();
    }

    // Show the window
    bool Show()
    {
        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)
        {
            CheckBox@ checkbox = cast<CheckBox>(terrainBrushes.items[i]);
            checkbox.checked = terrainBrushes.items[i] is selectedBrush;
            checkbox.enabled = !checkbox.checked;
        }

        dirty = false;
    }

    // This gets called when we want to do something to a terrain
    void Work(Terrain@ terrainComponent, Vector3 position)
    {
        // Only work if a brush is selected
        if (selectedBrushImage is null || scaledSelectedBrushImage is null)
            return;

        SetSceneModified();

        // Add that terrain to the terrainsEdited if its not already in there
        if (terrainsEdited.FindByRef(terrainComponent) == -1)
            terrainsEdited.Push(terrainComponent);

        TerrainEditorUpdateChanges@ updateChanges = TerrainEditorUpdateChanges();

        IntVector2 pos = terrainComponent.WorldToHeightMap(position);

        switch (editMode)
        {
        case TERRAIN_EDITMODE_RAISELOWERHEIGHT:
            UpdateTerrainRaiseLower(terrainComponent.heightMap, pos, updateChanges);
            break;
        case TERRAIN_EDITMODE_SETHEIGHT:
            UpdateTerrainSetHeight(terrainComponent.heightMap, pos, updateChanges);
            break;
        case TERRAIN_EDITMODE_SMOOTHHEIGHT:
            UpdateTerrainSmooth(terrainComponent.heightMap, pos, updateChanges);
            break;
        }

        terrainComponent.ApplyHeightMap();

        UpdateBrushVisualizer(terrainComponent, position);

        ModifyTerrainAction action;
        action.Define(terrainComponent, updateChanges.offset, updateChanges.oldImage, updateChanges.newImage);
        SaveEditAction(action);
    }

    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 = sceneResourcePath + 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);

        if (!fileSystem.DirExists(GetPath(fileLocation)))
            fileSystem.CreateDir(GetPath(fileLocation));
        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
    private UIElement@ LoadBrush(String fileLocation)
    {
        Array<String> chunks = fileLocation.Split('/');
        Array<String> parts = chunks[chunks.length - 1].Split('.');

        // We use this when editing the terrain
        Image@ image = cache.GetResource("Image", fileLocation);
        image.name = parts[0];
        brushes.Push(image);

        // This is for the brush icon
        Texture2D@ texture = cache.GetResource("Texture2D", fileLocation);

        CheckBox@ brush = CheckBox(parts[0]);
        brush.defaultStyle = uiStyle;
        brush.style = "TerrainEditorCheckbox";
        brush.SetFixedSize(64, 64);
        SubscribeToEvent(brush, "Toggled", "OnBrushSelected");

        BorderImage@ icon = BorderImage("Icon");
        icon.defaultStyle = iconStyle;
        icon.texture = texture;
        icon.imageRect = IntRect(0, 0, texture.width, texture.height);
        icon.SetFixedSize(64, 64);
        brush.AddChild(icon);

        return brush;
    }

    // Loads all the brushes from a hard-coded folder
    private void LoadBrushes()
    {
        ListView@ terrainBrushes = window.GetChild("BrushesContainer", true);

        Array<String>@ resourceDirs = cache.resourceDirs;
        String brushesFileLocation;
        for (uint i = 0; i < resourceDirs.length; ++i)
        {
            brushesFileLocation = resourceDirs[i] + "Textures/Editor/TerrainBrushes";
            if (fileSystem.DirExists(brushesFileLocation))
                break;
        }

        Array<String> files = fileSystem.ScanDir(brushesFileLocation, "*.*", SCAN_FILES, false);

        for (uint i = 0; i < files.length; ++i)
        {
            terrainBrushes.AddItem(LoadBrush(brushesFileLocation + "/" + files[i]));
        }
    }

    private void OnEditModeSelected(StringHash eventType, VariantMap& eventData)
    {
        CheckBox@ edit = eventData["Element"].GetPtr();

        if (!edit.checked)
{'fraction_non_alphanumeric': 0.10412693843773613, 'fraction_numerical': 0.009678696074551145, 'mean_word_length': 2.4394258136369262, 'pattern_counts': {'":': 0, '<': 39, '<?xml version=': 0, '>': 24, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 4}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7307089', 'n_tokens_mistral': 8249, 'n_tokens_neox': 7663, 'n_words': 1767}