Skip to content
Snippets Groups Projects
Commit 11587a97 authored by Knuiman, Bart's avatar Knuiman, Bart
Browse files

Spawning/despawning improved, but not good enough. Enrolling a different...

Spawning/despawning improved, but not good enough. Enrolling a different approach in TreeStreamer2 (WIP).
parent 24a236a4
Branches
No related tags found
No related merge requests found
......@@ -51,7 +51,11 @@ MonoBehaviour:
areaName: DeMarke
targetCam: {fileID: 0}
wgs84Origin: {x: 52.03894, y: 6.348124}
numRings: 2
numRings: 4
requiredTime: 3
removeDelay: 7
maxTrees: 10000
treePrefab: {fileID: 9046900170786976767, guid: 65926329d063fb84ea4ac2e63a6fcaa4,
type: 3}
treenGenerator: {fileID: -3667069338063608550, guid: ac835d606e3e19a48bdf1d863acfcfd0,
type: 3}
......@@ -658,9 +658,9 @@ namespace Wander
Debug.Log( "Done with placing/generating trees." );
}
private void OnDrawGizmos()
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.green;
Gizmos.color = Color.magenta;
float bigTileSize = (float)RDUtils.CalcTileSizeRD(zoom);
Gizmos.DrawWireCube( new Vector3( offsX, 0,offsY ) + new Vector3( bigTileSize, 0, bigTileSize )/2, new Vector3( boundsSize, 100, boundsSize ) );
}
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using UnityEditor;
......@@ -19,13 +20,13 @@ namespace Wander
public float lastTime;
public bool spawned;
public List<Vector3> trees = new();
// public List<GameObject> spawnedTrees = new();
public int spawnIndex;
public Terrain terrain;
public bool triedToObtainTerrain;
public List<int> spawnedTrees = new();
// public NativeArray<RaycastCommand> rayCmds;
}
[ReadOnly] public int activeTreeCount;
[ReadOnly] public double originRDX;
[ReadOnly] public double originRDY;
[ReadOnly] public float tileSize;
......@@ -33,10 +34,11 @@ namespace Wander
[ReadOnly] public string areaName = "Steenbergen";
public Camera targetCam;
public bool asTreeInstances = true;
public int numRings = 5;
public Vector2 wgs84Origin;
public int numRings = 2;
public float requiredTime = 3;
public float removeDelay = 7;
public float requiredTime = 1;
public float removeDelay = 1;
public int maxTrees = 1000;
public GameObject treePrefab;
public DownloadTrees treenGenerator;
......@@ -44,18 +46,18 @@ namespace Wander
private Dictionary<Vector2Int, TreeTile> treeDatabase = new();
private Dictionary<Vector2Int, TreeTile> activeTiles = new();
private bool loadedTrees;
private bool assignedTerrains;
private bool allowSpawning = false;
private bool allowSpawning = true;
private List<int> freeTreeSlots = new();
public void SetGPSCoord( Dictionary<string, string> boundaryData )
{
var wgs84Lat = double.Parse( boundaryData["gpsx"] );
var wgs84Lon = double.Parse( boundaryData["gpsy"] );
wgs84Origin = new Vector2( (float)wgs84Lat, (float) wgs84Lon );
tileSize = float.Parse( boundaryData["tileSize"] );
areaName = boundaryData["areaName"];
var wgs84Lat = double.Parse( boundaryData["gpsx"] );
var wgs84Lon = double.Parse( boundaryData["gpsy"] );
var boundsSize = float.Parse( boundaryData["boundsSize"] );
wgs84Origin = new Vector2( (float)wgs84Lat, (float)wgs84Lon );
tileSize = float.Parse( boundaryData["tileSize"] );
areaName = boundaryData["areaName"];
// Derived
RDUtils.GPS2RD( wgs84Lat, wgs84Lon, out originRDX, out originRDY );
......@@ -77,7 +79,7 @@ namespace Wander
for ( int i = 0; i < maxTrees; i++ )
{
Instantiate( treePrefab, treeRoot.transform ).SetActive( false );
Instantiate( treePrefab, treeRoot.transform ).GetComponent<LODGroup>().enabled = false;
freeTreeSlots.Add( i );
}
......@@ -144,29 +146,12 @@ namespace Wander
transform.position = targetCam.transform.position;
}
AssignTerrains();
LoadDesiredTiles();
SpawnTrees();
DespawnTrees();
}
}
void AssignTerrains()
{
if (assignedTerrains)
return;
assignedTerrains = true;
foreach (var item in treeDatabase)
{
var tile = item.Value;
if ( tile.trees.Count > 0 )
{
var tree = item.Value.trees[0];
var treePos = new Vector3( (float)(tree.x-originRDX), 0, (float)(tree.y-originRDY) );
tile.terrain = TerrainFromWorldPos( treePos );
}
}
activeTreeCount = maxTrees - freeTreeSlots.Count;
}
}
void LoadIfIsNewTile( Vector2Int centre, int dx, int dy, float time)
......@@ -177,10 +162,6 @@ namespace Wander
if (treeDatabase.TryGetValue( coord, out var dbTile ))
{
activeTiles.Add( coord, dbTile );
// if (!activeTile.rayCmds.IsCreated)
{
// activeTile.rayCmds = new NativeArray<RaycastCommand>( activeTile.trees.Count, Allocator.Persistent );
}
activeTile = dbTile;
activeTile.spawnedTrees.Clear();
activeTile.spawnIndex = 0;
......@@ -188,7 +169,6 @@ namespace Wander
activeTile.originTime = time;
}
}
if (activeTile != null)
{
activeTile.lastTime = time;
......@@ -213,11 +193,19 @@ namespace Wander
bool HeightFromTerrains( TreeTile tile, Vector3 worldPos, out float height )
{
height = 0;
if ( tile.terrain == null && !tile.triedToObtainTerrain )
{
tile.triedToObtainTerrain = true;
tile.terrain = TerrainFromWorldPos( worldPos );
}
if ( tile.terrain != null )
{
height = tile.terrain.SampleHeight( worldPos );
return true;
}
return false;
}
......@@ -227,9 +215,10 @@ namespace Wander
float tpy = transform.position.z;
float time = Time.time;
var rdX = Mathf.FloorToInt( (float) originRDX + tpx ) / 100;
var rdY = Mathf.FloorToInt( (float) originRDY + tpy ) / 100;
var rdX = Mathf.FloorToInt( (float)(( originRDX + tpx ) * 0.01f ));
var rdY = Mathf.FloorToInt( (float)(( originRDY + tpy ) * 0.01f ));
var centreTile = new Vector2Int(rdX, rdY);
LoadIfIsNewTile( centreTile, 0, 0, time );
for (int c = 1;c <= numRings;c++)
{
......@@ -241,11 +230,11 @@ namespace Wander
{
LoadIfIsNewTile( centreTile, c, y2, time );
}
for (int x2 = -c+1;x2 <= c-1;x2++)
for (int x2 = -c+1;x2 <= c-1 ;x2++)
{
LoadIfIsNewTile( centreTile, x2, c, time );
}
for (int x2 = -c+1;x2 <= c-1;x2++)
for (int x2 = -c+1;x2 <= c-1; x2++)
{
LoadIfIsNewTile( centreTile, x2, -c, time );
}
......@@ -254,12 +243,6 @@ namespace Wander
void SpawnTrees()
{
var mask = LayerMask.GetMask( "Default" );
QueryParameters qp = new QueryParameters();
qp.hitBackfaces = false;
qp.hitMultipleFaces = false;
qp.hitTriggers = QueryTriggerInteraction.Ignore;
qp.layerMask = mask;
foreach (var kvp in activeTiles)
{
var tile = kvp.Value;
......@@ -284,6 +267,8 @@ namespace Wander
continue;
var treePos = new Vector3( (float)(treeDb.x-originRDX), 0, (float)(treeDb.y-originRDY) );
if ( HeightFromTerrains( tile, treePos, out float height ))
{
var slot = freeTreeSlots.Last();
......@@ -292,51 +277,9 @@ namespace Wander
var tree = treeRoot.transform.GetChild( slot );
tree.transform.position = treePos + new Vector3( 0, height-100, 0 );
//var lodGroup = tree.GetComponent<LODGroup>();
tree.gameObject.SetActive( true );
tree.gameObject.GetComponent<LODGroup>().enabled = true;
}
//LOD [] lods = new LOD[4];
//lods[0].renderers/
//lodGroup.SetLODs
// RaycastCommand rc = new RaycastCommand(treePos, Vector3.down, 1000, mask, 1);
// if ( treePos.RaycastDown( 200, 500, mask, out var hit ))
//if( HeightFromTerrains( treePos, out float h, out Terrain ter ))
//{
//// InstantiateAsync( treePrefab ).completed += (operation) =>
// {
// // if ((operation as AsyncInstantiateOperation<GameObject>).Result.Length <= 0)
// {
// return;
// }
// //TreeInstance ti = new TreeInstance();
// var spawnee = Instantiate(treePrefab);
// // var spawnee = (operation as AsyncInstantiateOperation<GameObject>).Result[0];
// if (treeRoot == null) // Happens when stopped in editor in mean time.
// {
// spawnee.Destroy();
// return;
// }
// // Tile might have been thrown away in the mean time.
// if (tile.spawned)
// {
// spawnee.transform.parent = treeRoot.transform;
// spawnee.transform.position = treePos + new Vector3( 0, h-100, 0 );
// tile.spawnedTrees.Add( spawnee );
// }
// else
// {
// spawnee.Destroy();
// }
// };
//}
}
}
}
......@@ -351,18 +294,13 @@ namespace Wander
{
if ( tile.spawnedTrees.Count != 0 )
{
for( int i = 0; i< freeTreeSlots.Count; i++ )
for( int i = 0; i< tile.spawnedTrees.Count; i++ )
{
treeRoot.transform.GetChild( freeTreeSlots[i] ).gameObject.SetActive( false );
treeRoot.transform.GetChild( tile.spawnedTrees[i] ).gameObject.GetComponent<LODGroup>().enabled = false;
}
freeTreeSlots.AddRange( tile.spawnedTrees );
//tile.spawnedTrees.Last().Destroy();
tile.spawnedTrees.Clear();
//tile.spawnedTrees.RemoveAt( tile.spawnedTrees.Count-1 );
//tile.spawnIndex--;
}
else
{
if (removeList==null)
removeList = new();
removeList.Add( kvp.Key );
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Unity.Collections;
using UnityEditor;
using UnityEngine;
using UnityEngine.AddressableAssets;
namespace Wander
{
[ExecuteInEditMode] // Need this for SendMessage to be reveived without error in Edit mode.
public class TreeStreamer2 : MonoBehaviour
{
public class TreeTile
{
public float originTime;
public float lastTime;
public bool spawned;
public List<Vector3> treeDb = new();
public int spawnIndex;
public Terrain terrain;
public bool triedToObtainTerrain;
public List<int> spawnedTrees = new();
}
public class TreeType
{
public GameObject type;
public int maxTrees;
internal List<int> freeSlots;
internal NativeArray<Matrix4x4> positions;
}
[ReadOnly] public int activeTreeCount;
[ReadOnly] public double originRDX;
[ReadOnly] public double originRDY;
[ReadOnly] public float tileSize;
[ReadOnly] public GameObject treeRoot;
[ReadOnly] public string areaName = "Steenbergen";
public Camera targetCam;
public bool asTreeInstances = true;
public int numRings = 5;
public Vector2 wgs84Origin;
public float requiredTime = 2;
public float removeDelay = 1;
public List<TreeType> types = new();
private bool allowSpawning = true;
private Dictionary<Vector2Int, TreeTile> treeDatabase = new();
private Dictionary<Vector2Int, TreeTile> activeTiles = new();
private List<int> freeTreeSlots = new();
private Task updateMatricesTask;
private bool isDone;
// Variables copied from mainthread so can be accessed in other thread.
Vector3 cameraPosition;
float unityTime;
public void SetGPSCoord( Dictionary<string, string> boundaryData )
{
var wgs84Lat = double.Parse( boundaryData["gpsx"] );
var wgs84Lon = double.Parse( boundaryData["gpsy"] );
var boundsSize = float.Parse( boundaryData["boundsSize"] );
wgs84Origin = new Vector2( (float)wgs84Lat, (float)wgs84Lon );
tileSize = float.Parse( boundaryData["tileSize"] );
areaName = boundaryData["areaName"];
// Derived
RDUtils.GPS2RD( wgs84Lat, wgs84Lon, out originRDX, out originRDY );
#if UNITY_EDITOR
EditorUtility.SetDirty( this );
#endif
}
private async void OnDestroy()
{
if ( updateMatricesTask != null )
{
isDone = true;
await updateMatricesTask;
updateMatricesTask = null;
}
}
private void Start()
{
if (!Application.isPlaying)
return;
// In case no camera can be found, we have at least a valid position in other thread.
cameraPosition = transform.position;
// If no default cam is set, fetch from main.
if ( targetCam == null )
{
targetCam = Camera.main;
}
// Load tree database.
Addressables.LoadAssetAsync<TextAsset>( "Assets/" + areaName + "/trees.csv" ).Completed += (operation) =>
{
if ( !operation.IsDone || !operation.IsValid() || operation.Result == null )
{
Debug.LogWarning( "Trees failed to load." );
return;
}
// Cannot acccess .text in other thread, so obtain ptr first.
var text = operation.Result.text;
Task.Run( () =>
{
// Read tree database.
ReadTreeDb( text );
// Initially all matrix slots are free.
InitializeFreeMatriceSlots();
// Update matrices in the background.
UpdateLoopOtherThread();
} );
};
}
private void Update()
{
if (!Application.isPlaying)
return;
if (Input.GetKeyDown( KeyCode.Alpha1 ))
{
allowSpawning = !allowSpawning;
}
if (targetCam != null)
{
unityTime = Time.time;
cameraPosition = targetCam.transform.position;
}
}
void ReadTreeDb(string text)
{
var lines = text.Split( '\n' );
for (var line = 1;line<lines.Length-1;line++)
{
try
{
var columns = lines[line].Split( ';' );
if (columns.Length != 3)
continue;
var rdx = float.Parse( columns[0] );
var rdy = float.Parse( columns[1] );
int hectoX = Mathf.FloorToInt(rdx/100);
int hectoY = Mathf.FloorToInt(rdy/100);
if (!treeDatabase.TryGetValue( new Vector2Int( hectoX, hectoY ), out var tile ))
{
treeDatabase.Add( new Vector2Int( hectoX, hectoY ), tile=new() );
}
tile.treeDb.Add( new Vector3( rdx, rdy, int.Parse( columns[2] ) ) );
}
catch (Exception e)
{
Debug.LogError( e.Message );
Debug.LogError( "On Line " + line );
}
}
}
void InitializeFreeMatriceSlots()
{
// Initially each slot in the matrix arrea is free.
foreach (var type in types)
{
type.freeSlots = new();
for (int i = 0;i < type.maxTrees;i++)
{
freeTreeSlots.Add( i );
}
}
}
private void UpdateLoopOtherThread()
{
LoadDesiredTiles();
SpawnTrees();
DespawnTrees();
activeTreeCount = 0;
types.ForEach( t => {
activeTreeCount += t.maxTrees - t.freeSlots.Count;
} );
}
void LoadIfIsNewTile( Vector2Int centre, int dx, int dy, float time)
{
var coord = centre + new Vector2Int(dx, dy);
if (!activeTiles.TryGetValue( coord, out var activeTile ))
{
if (treeDatabase.TryGetValue( coord, out var dbTile ))
{
activeTiles.Add( coord, dbTile );
activeTile = dbTile;
activeTile.spawnedTrees.Clear();
activeTile.spawnIndex = 0;
activeTile.spawned = true;
activeTile.originTime = time;
}
}
if (activeTile != null)
{
activeTile.lastTime = time;
}
}
Terrain TerrainFromWorldPos( Vector3 worldPos )
{
var terrains = Terrain.activeTerrains;
for (int i = 0;i < terrains.Length;i++)
{
var t = terrains[i];
if ( worldPos.x > t.transform.position.x && worldPos.x <= transform.position.x + tileSize &&
worldPos.z > t.transform.position.z && worldPos.z <= transform.position.z + tileSize)
{
return t;
}
}
return null;
}
bool HeightFromTerrains( TreeTile tile, Vector3 worldPos, out float height )
{
height = 0;
if ( tile.terrain == null && !tile.triedToObtainTerrain )
{
tile.triedToObtainTerrain = true;
tile.terrain = TerrainFromWorldPos( worldPos );
}
if ( tile.terrain != null )
{
height = tile.terrain.SampleHeight( worldPos );
return true;
}
return false;
}
void LoadDesiredTiles()
{
float tpx = cameraPosition.x;
float tpy = cameraPosition.z;
float time = unityTime;
var rdX = Mathf.FloorToInt( (float)(( originRDX + tpx ) * 0.01f ));
var rdY = Mathf.FloorToInt( (float)(( originRDY + tpy ) * 0.01f ));
var centreTile = new Vector2Int(rdX, rdY);
LoadIfIsNewTile( centreTile, 0, 0, time );
for (int c = 1;c <= numRings;c++)
{
for (int y2 = -c;y2 <= c;y2++)
{
LoadIfIsNewTile( centreTile, -c, y2, time );
}
for (int y2 = -c;y2 <= c;y2++)
{
LoadIfIsNewTile( centreTile, c, y2, time );
}
for (int x2 = -c+1;x2 <= c-1 ;x2++)
{
LoadIfIsNewTile( centreTile, x2, c, time );
}
for (int x2 = -c+1;x2 <= c-1; x2++)
{
LoadIfIsNewTile( centreTile, x2, -c, time );
}
}
}
void SpawnTrees()
{
foreach (var kvp in activeTiles)
{
var tile = kvp.Value;
if (Time.time - tile.originTime < requiredTime)
{
continue;
}
while (tile.spawnIndex < tile.treeDb.Count)
{
if (freeTreeSlots.Count == 0)
break;
var treeDb = tile.treeDb[tile.spawnIndex];
tile.spawnIndex++;
var treeType = Mathf.RoundToInt( treeDb.z );
if (!(treeType >= 0 && treeType < treenGenerator.types.Count))
continue;
var treePrefab = treenGenerator.types[treeType].trees.Random();
if (treePrefab == null)
continue;
var treePos = new Vector3( (float)(treeDb.x-originRDX), 0, (float)(treeDb.y-originRDY) );
if ( HeightFromTerrains( tile, treePos, out float height ))
{
var slot = freeTreeSlots.Last();
freeTreeSlots.RemoveAt( freeTreeSlots.Count-1 );
tile.spawnedTrees.Add( slot );
var tree = treeRoot.transform.GetChild( slot );
tree.transform.position = treePos + new Vector3( 0, height-100, 0 );
var lg = tree.gameObject.GetComponent<LODGroup>();
lg.ForceLOD( -1 );
}
}
}
}
void DespawnTrees()
{
List<Vector2Int> removeList = null;
foreach (var kvp in activeTiles)
{
var tile = kvp.Value;
if (tile.lastTime - Time.time < -removeDelay)
{
if ( tile.spawnedTrees.Count != 0 )
{
for( int i = 0; i< tile.spawnedTrees.Count; i++ )
{
treeRoot.transform.GetChild( tile.spawnedTrees[i] ).gameObject.GetComponent<LODGroup>().ForceLOD( 4 );
}
freeTreeSlots.AddRange( tile.spawnedTrees );
tile.spawnedTrees.Clear();
if (removeList==null)
removeList = new();
removeList.Add( kvp.Key );
kvp.Value.spawned = false;
}
}
}
removeList?.ForEach( rm =>
{
activeTiles.Remove( rm );
} );
}
}
}
\ No newline at end of file
fileFormatVersion: 2
guid: 4643b554a54fe66479884b76c4ca9d39
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment