Search Unity

terrainData TreeInstances agony

Discussion in 'Scripting' started by Todd-Wasson, Mar 27, 2015.

  1. Todd-Wasson

    Todd-Wasson

    Joined:
    Aug 7, 2014
    Posts:
    1,079
    I just found out the hard way that modifying this structure changes it permanently in the editor and project too. The rendering time in my speedboat simulator scenes are mostly consumed by the trees, so I figured I'd try adding a "tree density" adjustment that players could turn down for higher frame rates. The idea was to go through the trees in the terrains and possibly remove every second or third (or whatever) tree when the scene loaded.

    What I didn't realize is that this data works directly on the data in the editor rather than a copy of it in memory from the original scene when the game is running in preview mode. Why is this? Is it necessary for the editor to work sensibly? I spent ages hand placing every tree in my scene one at a time (hundreds of them) and I've now lost all of them. :mad:



    Seeing as I have no choice now but to start over on the trees, does anyone have any ideas on how I could do the tree density idea without repeating the mistake? Would the idea as I had it work ok on a built version? Perhaps I should do it how I was doing it, but make it skip the routine if it's running in the editor?
     
  2. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    You could treat the terrain as a prefab, and create a clone of the terrain at runtime. That terrain could be edited without modifying the original data.
     
    Todd-Wasson likes this.
  3. protopop

    protopop

    Joined:
    May 19, 2009
    Posts:
    1,560
    I don't know how to change tree density at runtime (although changing grass density is easy) but just a tip, i use Terrain Composer to place my trees in the editor. It's procedural and repeatable. So if anything screws up i just click generate trees again and they all appear in the same place. I know it's not what you're looking for but it may be something you could use for losing trees. I feel your pain. I changed the terrain and lost stuff and i was like "what?!!"
     
  4. ZO5KmUG6R

    ZO5KmUG6R

    Joined:
    Jul 15, 2010
    Posts:
    490
    you could serialize your tree instance data to a file, then reload it when the scene runs
     
  5. Todd-Wasson

    Todd-Wasson

    Joined:
    Aug 7, 2014
    Posts:
    1,079
    Thanks. I came up with a way around it that restores the trees. Will post later.
     
  6. Todd-Wasson

    Todd-Wasson

    Joined:
    Aug 7, 2014
    Posts:
    1,079
    WARNING: THIS CODE IS NOT FULLY TESTED AND MAY DELETE YOUR TREES PERMANENTLY FROM SCENES IN SOME SITUATIONS. POSTED ONLY FOR ILLUSTRATIVE PURPOSES. DON'T USE THIS, IT'S A DANGEROUS FIRST DRAFT!

    Ok, I wrote a class (TreeRemove) that seems to do the trick in my case where I'm switching back and forth between a scene with trees and one with no terrain in it. This goes on an object in a scene with terrain trees. In Start() it does a deep copy of the trees into treeInstancesOriginal. This is a staggered array so it works with multiple terrains in the same scene. It's used to restore the trees via a call to RestoreTrees() from elsewhere in the code. I call it from my general, abstract game manager class just before a level is loaded as follows:

    Code (csharp):
    1.      public static void LoadLevel(int level)
    2.     {
    3.     TreeRemove treeRemove = FindObjectOfType<TreeRemove>();
    4.         if (treeRemove)
    5.             treeRemove.RestoreTrees();
    6.  
    7.         Application.LoadLevel(level);
    8. }
    9.  

    This is hard coded for the moment to remove every other tree (50%):


    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public class TreeRemove : MonoBehaviour {
    6.     //This removes trees from the terrain as a sort of LOD system to reduce detail on lower end machines.
    7.     private Terrain[] terrains;
    8.  
    9.     private TreeInstance[][] treeInstancesOriginal;  //Jagged array that holds a copy of the original trees when a scene is first loaded.
    10.  
    11.     TreeInstance[] DeepCopyTreeInstances(TreeInstance[] source)
    12.     {
    13.         //A TreeInstance array for all the trees on one of the terrains.
    14.         TreeInstance[] treeInstance = new TreeInstance[source.Length];
    15.  
    16.         //Iterate over each treeInstance in the source (the original trees in the scene) and copy them into the new treeInstance array:
    17.         for (int i = 0; i < source.Length; i++)
    18.         {
    19.             treeInstance[i].color.a = source[i].color.a;
    20.             treeInstance[i].color.r = source[i].color.r;
    21.             treeInstance[i].color.g = source[i].color.g;
    22.             treeInstance[i].color.b = source[i].color.b;
    23.  
    24.             treeInstance[i].heightScale = source[i].heightScale;
    25.        
    26.             treeInstance[i].lightmapColor.a = source[i].lightmapColor.a;
    27.             treeInstance[i].lightmapColor.r = source[i].lightmapColor.r;
    28.             treeInstance[i].lightmapColor.g = source[i].lightmapColor.g;
    29.             treeInstance[i].lightmapColor.b = source[i].lightmapColor.b;
    30.  
    31.             treeInstance[i].position.x = source[i].position.x;
    32.             treeInstance[i].position.y = source[i].position.y;
    33.             treeInstance[i].position.z = source[i].position.z;
    34.      
    35.             treeInstance[i].prototypeIndex = source[i].prototypeIndex;
    36.  
    37.             treeInstance[i].rotation = source[i].rotation;
    38.             treeInstance[i].widthScale = source[i].widthScale;
    39.         }
    40.  
    41.         return treeInstance;
    42.     }
    43.  
    44.     void OnLevelWasLoaded()
    45.     {
    46.         print("ModifyTrees");
    47.         terrains = Terrain.activeTerrains;
    48.  
    49.         //Create the first dimension of the array.  This is indexed by the terrain number.  The second dimension of the array will hold the individual TreeInstances (the trees).
    50.         //This way every terrain in the scene can hold a different number of trees.
    51.         treeInstancesOriginal = new TreeInstance[terrains.Length][];
    52.  
    53.         //Iterate through all terrains
    54.         for (int iTerrain = 0; iTerrain < terrains.Length; iTerrain++)
    55.         {
    56.             //This creates and fills the second dimension of the jagged array for this terrain:
    57.             treeInstancesOriginal[iTerrain] = DeepCopyTreeInstances(terrains[iTerrain].terrainData.treeInstances);
    58.  
    59.             //Create a list that will hold the new trees:
    60.             List<TreeInstance> newTreeInstances = new List<TreeInstance>();
    61.  
    62.             //Go through each tree in this terrain and add it to our new list, skipping every other tree for starters:
    63.             for (int iTree = 0; iTree < treeInstancesOriginal[iTerrain].Length; iTree += 2)
    64.             {
    65.                 newTreeInstances.Add(treeInstancesOriginal[iTerrain][iTree]);
    66.             }
    67.  
    68.             //Point the terrain data to the new list of trees:
    69.             terrains[iTerrain].terrainData.treeInstances = new TreeInstance[newTreeInstances.Count];
    70.             terrains[iTerrain].terrainData.treeInstances = newTreeInstances.ToArray();
    71.         }
    72.  
    73.     }
    74.  
    75.     public void RestoreTrees()
    76.     {
    77.         print("RestoreTrees");
    78.         ////Restore the original trees
    79.         for (int iTerrain = 0; iTerrain < terrains.Length; iTerrain++)
    80.         {
    81.             terrains[iTerrain].terrainData.treeInstances = treeInstancesOriginal[iTerrain];
    82.         }
    83.  
    84.     }
    85.  
    86. }
    87.  
    I haven't tested it much but it seems to do the trick. The main downside is that when the trees are restored you see them pop back into the scene briefly before the level exits, and any tree editing you do in the editor in play mode probably ends up being ignored. It's also likely that if you stop the preview player while in a tree scene, the trees will simply be deleted permanently because the RestoreTrees() function doesn't get called then. So I need to also do that before the level is shut down while the application is "quitting" for it to be safer, at least in preview mode.

    While doing this it became apparent that it would be easy to change the trees too just by changing prototypeIndex in TreeInstance. I'm using a Speedtree package with multiple seasons, so this might be an easy way to change all the trees in the scene to whatever season you want while the level is loading. Granted, you might see summer trees for an instant before the fall or winter versions pop up, but at least it wouldn't be a big stretch to do it.
     
  7. Todd-Wasson

    Todd-Wasson

    Joined:
    Aug 7, 2014
    Posts:
    1,079
    Thanks for the ideas, guys. Good suggestions.

    Protopop: I'm using World Composer, but don't have Terrain Composer. Maybe I should pick that up too, it sounds useful. I'm a little confused on the differences between World Composer and Terrain Composer and how they work together. From what you said, I gather that Terrain Composer will populate it with trees. Will it do the same for houses and other objects (they're all just meshes in the end, after all)? If so, how is it different from the built in Unity stuff for this type of thing aside from the ability to regenerate the trees like you said?

    Maybe I just need to read through their site again now that I have some clue what I'm doing with World Composer. Thanks for the suggestion, I'll check it out again.
     
  8. protopop

    protopop

    Joined:
    May 19, 2009
    Posts:
    1,560
    As an Open World Wilderness designer I find Terrain Composer indispensable. It the same as if you place the trees, grass etc. It just makes it procedural and repeatable. So instead of drawing trees on the hillsides, clearing grass off the snowy mountains, and putting bushes under pine trees, you write rules or use bitmaps to place them, and then you can regenerate them any time you need to, or change parameters so now trees grow on the mountains or whatever. You can also use it to place objects too and place terrain textures according to rules (rocks only on cliffs etc) instead of hand painting.

    I own world composer too but havent used it. From what i understand its for adding real world landscapes to your game.
     
  9. Todd-Wasson

    Todd-Wasson

    Joined:
    Aug 7, 2014
    Posts:
    1,079
    Thanks. I bought it last night and am impressed with it so far. I'm having problems though. I'm trying to import my heightmaps from World Composer for that lake scene. It's giving me some "off by one" error about resolution (expects a 312x312 but it's a 313x313 or similar). What I hope to do is just use the World Composer heightmaps and let TC dress it up with nicer textures and so on. I had manually placed every tree in the scene to match the satellite image data, it'd be nice to perhaps not have to do that a second time. Even if I have to, it'd be enough just to get TC to texture it up for me instead of using the satellite image data.

    So far I'm not having any luck importing heightmaps and there hasn't been a reply in the TC forum yet. Maybe in the next few days he'll find some time to give this TC noob a hand.

    Either way, I think I'll go ahead with a "tree density" slider working similar to what I've posted here. In my sim it looks like the best way to let people crank up their frame rates if they need to.
     
  10. protopop

    protopop

    Joined:
    May 19, 2009
    Posts:
    1,560
    yeah, ive had that import heightmap problem a lot. I dont even remember the fix, its one of those "i tried everything and eventually it workde" thing. It might have to do with the type of raw file or the size or some other setting in there.

    For your trees from satellite images, maybe you could bring the image into photoshop or whatever and mask out everything but the trees. then you can turn it to a black and white image and use it as a tree map. For example i have a lot of red roses around my world that are planted procedurally by Terrain Composer, but there are also a few specifc gardens, so i painted white on a bit map to show here i wanted those, added another rose layer, this one with a bitmap to choose placement, and the roses appear specifcally where i want them.

    Anyways terrain composer is super powerful, i hope you get a lot of use out of it. For my next project im going to try and figure out World Composer:)



    I use this map to specifcally position BrightBob Trees in Nimian Legends : BrightRidge