Changing terrain after generation

Discuss modding questions and implementation details.
User avatar
Hazelnut
Posts: 3015
Joined: Sat Aug 26, 2017 2:46 pm
Contact:

Re: Changing terrain after generation

Post by Hazelnut »

l3lessed wrote: Tue Jul 27, 2021 8:34 pm That is helpful. I feel for it to be a full building/city creator, you need to be able to raise and lower terrain. I want players to be able to build anywhere they can access. If they can't modify terrain, how will they build a house on a mountain side?
Ah, I should have mentioned that locations are always in the centre of a map pixel so that constraint means that you can't use the standard location building code and world data variants for what you want to achieve. It will be quite hard to do this I think, definitely a challenging project and extremely difficult to have it appear on the travel map etc since that is fundamentally based on 1 map pixel having a single location and no more. It may be possible to make a location not be centred but to allow players to create stuff anywhere won't be compatible. Possible work-around would be to have a different mechanism than the games travel map to allow players to travel there. You may still have extra complexity if someone decides the intersection of 4 map pixel corners is where they want to build as the height data and unity terrain etc is per map pixel square.
See my mod code for examples of how to change various aspects of DFU: https://github.com/ajrb/dfunity-mods

l3lessed
Posts: 1399
Joined: Mon Aug 12, 2019 4:32 pm
Contact:

Re: Changing terrain after generation

Post by l3lessed »

Thanks for mentioning all this. I didn't even think about the travel map, adding a location to the games permanent locations list and fast traveling when it comes to adding buildings and towns.

How about making it so if there are multiple locations within a single pixel, when the location pixel is clicked in the travel map, you get an option that appears to select which location within the individual pixel you want to travel to? This could be done by hi-jackings the UI interface for the travel map and injecting in code to check for added locations, update the pixel, and then add the option on clicking the update map pixel.

So, player builds a house/small town next to a major capital, and within its pixel block. Now, when the travel map it opened, there can be a small indicator or color change for that pixel to indicate it contains multiple travel locations. The player clicks it, UI pops up selecting the specific locations in the area to travel to (AKA the original city and any added areas the player creates). After working on the UI for the inventory and my outfit manager, I think this could be doable for me, if the UI for the travel map can be hooked and injected with code like the inventory UI.

As for the terrain edge/map pixel issue and height data, that can be resolved using a simple code check and not allowing any editing/building within a certain area of the map pixel edge, so players don't create anything that overlaps multiple terrain pixel squares. Just give a few generic immersive messages like, "This terrain is to soft to build on; Find more solid ground." and/or, highlight the terrain within the pixel they can build on.

Also, if they are modifying terrain in-game on the fly, saving it some how, and then it is being restored on save load, I do not see how the map pixel edges become a huge issue. Well, that is if I bypass the built in location creation system the flattens everything and places the location, right? I would prefer, despite the added complexity, to not use the built in building/town flatten creation system and devise my own, and then register that place as a location separate from the flattening RMB block placement system.
My Daggerfall Mod Github: l3lessed DFU Mod Github

My Beth Mods: l3lessed Nexus Page

Daggerfall Unity mods: Combat Overhaul Mod

Enjoy the free work I'm doing? Consider lending your support.

l3lessed
Posts: 1399
Joined: Mon Aug 12, 2019 4:32 pm
Contact:

Re: Changing terrain after generation

Post by l3lessed »

I began messing with some terrain code, but I ran into a few problems.

Code: Select all

using UnityEngine;
using System.Collections;
using DaggerfallWorkshop.Game.Utility.ModSupport;
using DaggerfallWorkshop.Game;
using DaggerfallWorkshop.Game.Utility.ModSupport.ModSettings;

public class TerrainTool : MonoBehaviour
{
    public static Mod mod;
    public static TerrainTool TerrainToolInstance;
    static ModSettings settings;

    public bool TestWithMouse = true;
    public Terrain myTerrain;
    public int SmoothArea;
    private int xResolution;
    private int zResolution;
    private float[,] heights;
    private float[,] heightMapBackup;
    protected const float DEPTH_METER_CONVERT = 0.05f;
    protected const float TEXTURE_SIZE_MULTIPLIER = 1.25f;
    public int DeformationTextureNum = 1;
    protected int alphaMapWidth;
    protected int alphaMapHeight;
    protected int numOfAlphaLayers;
    private float[,,] alphaMapBackup;
    private bool terrainGrabbed;

    //starts mod manager on game begin. Grabs mod initializing paramaters.
    //ensures SateTypes is set to .Start for proper save data restore values.
    [Invoke(StateManager.StateTypes.Game, 0)]
    public static void Init(InitParams initParams)
    {
        //Below code blocks set up instances of class/script/mod.\\
        //sets up and runs this script file as the main mod file, so it can setup all the other scripts for the mod.
        GameObject TerrainTool = new GameObject("TerrainToolMod");
        TerrainToolInstance = TerrainTool.AddComponent<TerrainTool>();
        Debug.Log("TERRAIN TOOL STARTED");

        //initiates mod paramaters for class/script.
        mod = initParams.Mod;
        //loads mods settings.
        settings = mod.GetSettings();
        //assets = mod.LoadAllAssetsFromBundle();
        //initiates save paramaters for class/script.
        //mod.SaveDataInterface = instance;
        //after finishing, set the mod's IsReady flag to true.
        mod.IsReady = true;
    }

    void OnApplicationQuit()
    {
        if (Debug.isDebugBuild)
        {
            myTerrain.terrainData.SetHeights(0, 0, heightMapBackup);
            myTerrain.terrainData.SetAlphamaps(0, 0, alphaMapBackup);
        }
    }


    void Update()
    {       
        // This is just for testing with mouse!
        // Point mouse to the Terrain. Left mouse button
        // raises and right mouse button lowers terrain.
        if (TestWithMouse == true)
        {
            if (Input.GetMouseButtonDown(0))
            {

                myTerrain = Terrain.activeTerrain;

                xResolution = myTerrain.terrainData.heightmapWidth;
                zResolution = myTerrain.terrainData.heightmapHeight;
                alphaMapWidth = myTerrain.terrainData.alphamapWidth;
                alphaMapHeight = myTerrain.terrainData.alphamapHeight;
                numOfAlphaLayers = myTerrain.terrainData.alphamapLayers;

                Debug.Log("SAMPLING TERRAIN");

                RaycastHit hit;
                Ray ray = new Ray(GameManager.Instance.MainCamera.transform.position, GameManager.Instance.MainCamera.transform.forward);
                Debug.DrawRay(ray.origin, ray.direction, Color.red,3);
                if (Physics.Raycast(ray, out hit))
                {
                    // area middle point x and z, area width, area height, smoothing distance, area height adjust
                    raiselowerTerrainArea(hit.point, 10, 10, SmoothArea, 0.01f);
                    // area middle point x and z, area size, texture ID from terrain textures
                    TextureDeformation(hit.point, 10 * 2f, DeformationTextureNum);
                }
            }
            if (Input.GetMouseButtonDown(1))
            {
                Debug.Log("SAMPLING TERRAIN");

                myTerrain = Terrain.activeTerrain;

                xResolution = myTerrain.terrainData.heightmapWidth;
                zResolution = myTerrain.terrainData.heightmapHeight;
                alphaMapWidth = myTerrain.terrainData.alphamapWidth;
                alphaMapHeight = myTerrain.terrainData.alphamapHeight;
                numOfAlphaLayers = myTerrain.terrainData.alphamapLayers;

                RaycastHit hit;
                Ray ray = new Ray(GameManager.Instance.MainCamera.transform.position, GameManager.Instance.MainCamera.transform.forward * 10f);
                Debug.DrawRay(ray.origin, ray.direction, Color.red,3);
                if (Physics.Raycast(ray, out hit))
                {
                    // area middle point x and z, area width, area height, smoothing distance, area height adjust
                    raiselowerTerrainArea(hit.point, 10, 10, SmoothArea, -0.01f);
                    // area middle point x and z, area size, texture ID from terrain textures
                    TextureDeformation(hit.point, 10 * 2f, 0);
                }
            }
        }
    }


    private void raiselowerTerrainArea(Vector3 point, int lenx, int lenz, int smooth, float incdec)
    {
        int areax;
        int areaz;
        smooth += 1;
        float smoothing;
        int terX = (int)((point.x / myTerrain.terrainData.size.x) * xResolution);
        int terZ = (int)((point.z / myTerrain.terrainData.size.z) * zResolution);
        lenx += smooth;
        lenz += smooth;
        terX -= (lenx / 2);
        terZ -= (lenz / 2);
        if (terX < 0) terX = 0;
        if (terX > xResolution) terX = xResolution;
        if (terZ < 0) terZ = 0;
        if (terZ > zResolution) terZ = zResolution;
        float[,] heights = myTerrain.terrainData.GetHeights(terX, terZ, lenx, lenz);
        float y = heights[lenx / 2, lenz / 2];
        y += incdec;
        for (smoothing = 1; smoothing < smooth + 1; smoothing++)
        {
            float multiplier = smoothing / smooth;
            for (areax = (int)(smoothing / 2); areax < lenx - (smoothing / 2); areax++)
            {
                for (areaz = (int)(smoothing / 2); areaz < lenz - (smoothing / 2); areaz++)
                {
                    if ((areax > -1) && (areaz > -1) && (areax < xResolution) && (areaz < zResolution))
                    {
                        heights[areax, areaz] = Mathf.Clamp((float)y * multiplier, 0, 1);
                    }
                }
            }
        }
        myTerrain.terrainData.SetHeights(terX, terZ, heights);
    }

    private void raiselowerTerrainPoint(Vector3 point, float incdec)
    {
        int terX = (int)((point.x / myTerrain.terrainData.size.x) * xResolution);
        int terZ = (int)((point.z / myTerrain.terrainData.size.z) * zResolution);
        float y = heights[terX, terZ];
        y += incdec;
        float[,] height = new float[1, 1];
        height[0, 0] = Mathf.Clamp(y, 0, 1);
        heights[terX, terZ] = Mathf.Clamp(y, 0, 1);
        myTerrain.terrainData.SetHeights(terX, terZ, height);
    }

    protected void TextureDeformation(Vector3 pos, float craterSizeInMeters, int textureIDnum)
    {
        Vector3 alphaMapTerrainPos = GetRelativeTerrainPositionFromPos(pos, myTerrain, alphaMapWidth, alphaMapHeight);
        int alphaMapCraterWidth = (int)(craterSizeInMeters * (alphaMapWidth / myTerrain.terrainData.size.x));
        int alphaMapCraterLength = (int)(craterSizeInMeters * (alphaMapHeight / myTerrain.terrainData.size.z));
        int alphaMapStartPosX = (int)(alphaMapTerrainPos.x - (alphaMapCraterWidth / 2));
        int alphaMapStartPosZ = (int)((alphaMapTerrainPos.z * -1) - (alphaMapCraterLength / 2));

        Debug.Log(alphaMapTerrainPos + " | " + alphaMapCraterWidth + " | " + alphaMapCraterLength + " | " + alphaMapStartPosX + " | " + alphaMapStartPosZ);

        float[,,] alphas = myTerrain.terrainData.GetAlphamaps(alphaMapStartPosX, alphaMapStartPosZ, alphaMapCraterWidth, alphaMapCraterLength);
        float circlePosX;
        float circlePosY;
        float distanceFromCenter;
        for (int i = 0; i < alphaMapCraterLength; i++) //width
        {
            for (int j = 0; j < alphaMapCraterWidth; j++) //height
            {
                circlePosX = (j - (alphaMapCraterWidth / 2)) / (alphaMapWidth / myTerrain.terrainData.size.x);
                circlePosY = (i - (alphaMapCraterLength / 2)) / (alphaMapHeight / myTerrain.terrainData.size.z);
                distanceFromCenter = Mathf.Abs(Mathf.Sqrt(circlePosX * circlePosX + circlePosY * circlePosY));
                if (distanceFromCenter < (craterSizeInMeters / 2.0f))
                {
                    for (int layerCount = 0; layerCount < numOfAlphaLayers; layerCount++)
                    {
                        //could add blending here in the future
                        if (layerCount == textureIDnum)
                        {
                            alphas[i, j, layerCount] = 1;
                        }
                        else
                        {
                            alphas[i, j, layerCount] = 0;
                        }
                    }
                }
            }
        }
        myTerrain.terrainData.SetAlphamaps(alphaMapStartPosX, alphaMapStartPosZ, alphas);
    }

    protected Vector3 GetNormalizedPositionRelativeToTerrain(Vector3 pos, Terrain terrain)
    {
        Vector3 tempCoord = (pos - terrain.gameObject.transform.position);
        Vector3 coord;
        coord.x = tempCoord.x / myTerrain.terrainData.size.x;
        coord.y = tempCoord.y / myTerrain.terrainData.size.y;
        coord.z = tempCoord.z / myTerrain.terrainData.size.z;
        return coord;
    }

    protected Vector3 GetRelativeTerrainPositionFromPos(Vector3 pos, Terrain terrain, int mapWidth, int mapHeight)
    {
        Vector3 coord = GetNormalizedPositionRelativeToTerrain(pos, terrain);
        return new Vector3((coord.x * mapWidth), 0, (coord.z * mapHeight));
    }
}
The first issue is this line of code.

Code: Select all

float[,,] alphas = myTerrain.terrainData.GetAlphamaps(alphaMapStartPosX, alphaMapStartPosZ, alphaMapCraterWidth, alphaMapCraterLength);
DFU terrain width values are coming out as negatives for some reason, and this does not seem to like a negative input. I can turn the negative into a positive and it seems to stop this error.

However, once I turn the value positive, despite seeing no errors in the log, it doesn't work. The terrain doesn't change at all on mouse click; maybe it is, and maybe it is doing it somewhere else and i'm messing up the math/positioning. The debug message shows the ray is clearly hitting the terrain and activating the deformation code. However, this is where the math gets way above my head.

Anyone who understands the math behind this and how the code is working in precise terms can help me?
My Daggerfall Mod Github: l3lessed DFU Mod Github

My Beth Mods: l3lessed Nexus Page

Daggerfall Unity mods: Combat Overhaul Mod

Enjoy the free work I'm doing? Consider lending your support.

Post Reply