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

Changed rendering type of trees completely. Now much smoother, but not finished (WIP).

parent 11587a97
Branches
No related tags found
No related merge requests found
...@@ -9,7 +9,7 @@ GameObject: ...@@ -9,7 +9,7 @@ GameObject:
serializedVersion: 6 serializedVersion: 6
m_Component: m_Component:
- component: {fileID: 8102445642857089983} - component: {fileID: 8102445642857089983}
- component: {fileID: 5786002106602377772} - component: {fileID: 591969842533917131}
m_Layer: 0 m_Layer: 0
m_Name: TreeStreamer m_Name: TreeStreamer
m_TagString: Untagged m_TagString: Untagged
...@@ -32,7 +32,7 @@ Transform: ...@@ -32,7 +32,7 @@ Transform:
m_Children: [] m_Children: []
m_Father: {fileID: 0} m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &5786002106602377772 --- !u!114 &591969842533917131
MonoBehaviour: MonoBehaviour:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0} m_CorrespondingSourceObject: {fileID: 0}
...@@ -41,21 +41,23 @@ MonoBehaviour: ...@@ -41,21 +41,23 @@ MonoBehaviour:
m_GameObject: {fileID: 742251135161503034} m_GameObject: {fileID: 742251135161503034}
m_Enabled: 1 m_Enabled: 1
m_EditorHideFlags: 0 m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: acbdecd6bc179f647b5703a55ffdafe7, type: 3} m_Script: {fileID: 11500000, guid: 4643b554a54fe66479884b76c4ca9d39, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
activeTreeCount: 0
originRDX: 220929.6988732884 originRDX: 220929.6988732884
originRDY: 450504.48700884776 originRDY: 450504.48700884776
tileSize: 860.16 tileSize: 860.16
treeRoot: {fileID: 0} treeRoot: {fileID: 0}
areaName: DeMarke areaName: DeMarke
targetCam: {fileID: 0} targetCam: {fileID: 0}
numRings: 5
wgs84Origin: {x: 52.03894, y: 6.348124} wgs84Origin: {x: 52.03894, y: 6.348124}
numRings: 4 requiredTime: 1
requiredTime: 3 removeDelay: 1
removeDelay: 7 enableDisableKey: 49
maxTrees: 10000 types:
treePrefab: {fileID: 9046900170786976767, guid: 65926329d063fb84ea4ac2e63a6fcaa4, - type: {fileID: 5556296651694026700, guid: 19df7a28074cb284a8aa8562718310f5, type: 3}
type: 3} maxTrees: 20000
treenGenerator: {fileID: -3667069338063608550, guid: ac835d606e3e19a48bdf1d863acfcfd0, - type: {fileID: 9046900170786976767, guid: 65926329d063fb84ea4ac2e63a6fcaa4, type: 3}
type: 3} maxTrees: 20000
using Codice.CM.Client.Differences;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Unity.Collections; using Unity.Collections;
using UnityEditor; using UnityEditor;
using UnityEditor.Graphs;
using UnityEngine; using UnityEngine;
using UnityEngine.AddressableAssets; using UnityEngine.AddressableAssets;
...@@ -17,20 +20,21 @@ namespace Wander ...@@ -17,20 +20,21 @@ namespace Wander
{ {
public float originTime; public float originTime;
public float lastTime; public float lastTime;
public bool spawned;
public List<Vector3> treeDb = new(); public List<Vector3> treeDb = new();
public int spawnIndex;
public Terrain terrain; public Terrain terrain;
public bool triedToObtainTerrain; public bool triedToObtainTerrain;
public List<int> spawnedTrees = new(); public int lod;
public Vector3 worldPos;
public Dictionary<int, (int offset, int num)> offsetAndNumPerType;
} }
[Serializable]
public class TreeType public class TreeType
{ {
public GameObject type; public GameObject type;
public int maxTrees; public int maxTrees;
internal List<int> freeSlots; internal int [] currentCount;
internal NativeArray<Matrix4x4> positions; internal NativeArray<Matrix4x4> [] positions;
} }
[ReadOnly] public int activeTreeCount; [ReadOnly] public int activeTreeCount;
...@@ -41,28 +45,28 @@ namespace Wander ...@@ -41,28 +45,28 @@ namespace Wander
[ReadOnly] public string areaName = "Steenbergen"; [ReadOnly] public string areaName = "Steenbergen";
public Camera targetCam; public Camera targetCam;
public bool asTreeInstances = true;
public int numRings = 5; public int numRings = 5;
public Vector2 wgs84Origin; public Vector2 wgs84Origin;
public float requiredTime = 2; public float requiredTime = 2;
public float removeDelay = 1; public float removeDelay = 1;
public KeyCode enableDisableKey = KeyCode.Alpha1;
public List<TreeType> types = new(); public List<TreeType> types = new();
private bool allowSpawning = true; private bool allowSpawning = true;
private Dictionary<Vector2Int, TreeTile> treeDatabase = new(); private Dictionary<Vector2Int, TreeTile> treeDatabase = new();
private Dictionary<Vector2Int, TreeTile> activeTiles = new(); private Dictionary<Vector2Int, TreeTile> activeTiles = new();
private List<int> freeTreeSlots = new();
private Task updateMatricesTask; private Task updateMatricesTask;
private bool isDone; private bool isDone;
// Variables copied from mainthread so can be accessed in other thread. // Variables copied from mainthread so can be accessed in other thread.
Vector3 cameraPosition; private Vector3 cameraPosition;
float unityTime; private float unityTime;
private System.Random random;
public void SetGPSCoord( Dictionary<string, string> boundaryData ) public void SetGPSCoord( Dictionary<string, string> boundaryData )
{ {
var wgs84Lat = double.Parse( boundaryData["gpsx"] ); var wgs84Lat = double.Parse( boundaryData["gpsx"] );
var wgs84Lon = double.Parse( boundaryData["gpsy"] ); var wgs84Lon = double.Parse( boundaryData["gpsy"] );
var boundsSize = float.Parse( boundaryData["boundsSize"] ); var boundsSize = float.Parse( boundaryData["boundsSize"] );
wgs84Origin = new Vector2( (float)wgs84Lat, (float)wgs84Lon ); wgs84Origin = new Vector2( (float)wgs84Lat, (float)wgs84Lon );
tileSize = float.Parse( boundaryData["tileSize"] ); tileSize = float.Parse( boundaryData["tileSize"] );
...@@ -84,6 +88,18 @@ namespace Wander ...@@ -84,6 +88,18 @@ namespace Wander
await updateMatricesTask; await updateMatricesTask;
updateMatricesTask = null; updateMatricesTask = null;
} }
foreach (var type in types)
{
if (type.positions == null)
continue;
for (int i = 0;i < 4;i++)
{
if (type.positions[i] != null && type.positions[i].IsCreated)
{
type.positions[i].Dispose();
}
}
}
} }
private void Start() private void Start()
...@@ -91,6 +107,8 @@ namespace Wander ...@@ -91,6 +107,8 @@ namespace Wander
if (!Application.isPlaying) if (!Application.isPlaying)
return; return;
random = new System.Random();
// In case no camera can be found, we have at least a valid position in other thread. // In case no camera can be found, we have at least a valid position in other thread.
cameraPosition = transform.position; cameraPosition = transform.position;
...@@ -98,7 +116,23 @@ namespace Wander ...@@ -98,7 +116,23 @@ namespace Wander
if ( targetCam == null ) if ( targetCam == null )
{ {
targetCam = Camera.main; targetCam = Camera.main;
} }
// Initially each slot in the matrix arrea is free.
foreach (var type in types)
{
Debug.Assert( type.type != null, "No type specified." );
type.currentCount = new int[4];
type.positions = new NativeArray<Matrix4x4>[4];
for (int i = 0;i<4;i++)
{
type.positions[i] = new NativeArray<Matrix4x4>( type.maxTrees, Allocator.Persistent );
for (int j = 0;j<type.positions[i].Length;j++)
{
type.positions[i][j] = Matrix4x4.TRS( Vector3.zero, Quaternion.identity, Vector3.zero );
}
}
}
// Load tree database. // Load tree database.
Addressables.LoadAssetAsync<TextAsset>( "Assets/" + areaName + "/trees.csv" ).Completed += (operation) => Addressables.LoadAssetAsync<TextAsset>( "Assets/" + areaName + "/trees.csv" ).Completed += (operation) =>
...@@ -110,17 +144,21 @@ namespace Wander ...@@ -110,17 +144,21 @@ namespace Wander
} }
// Cannot acccess .text in other thread, so obtain ptr first. // Cannot acccess .text in other thread, so obtain ptr first.
var text = operation.Result.text; var text = operation.Result.text;
Task.Run( () => updateMatricesTask = Task.Run( () =>
{ {
// Read tree database. try
ReadTreeDb( text ); {
// Read tree database.
// Initially all matrix slots are free. ReadTreeDb( text );
InitializeFreeMatriceSlots();
// Update matrices in the background. // Update matrices in the background.
UpdateLoopOtherThread(); UpdateLoopOtherThread();
}
catch (Exception e)
{
Debug.LogException( e );
}
} ); } );
}; };
...@@ -131,7 +169,7 @@ namespace Wander ...@@ -131,7 +169,7 @@ namespace Wander
if (!Application.isPlaying) if (!Application.isPlaying)
return; return;
if (Input.GetKeyDown( KeyCode.Alpha1 )) if (Input.GetKeyDown( enableDisableKey ))
{ {
allowSpawning = !allowSpawning; allowSpawning = !allowSpawning;
} }
...@@ -141,6 +179,40 @@ namespace Wander ...@@ -141,6 +179,40 @@ namespace Wander
unityTime = Time.time; unityTime = Time.time;
cameraPosition = targetCam.transform.position; cameraPosition = targetCam.transform.position;
} }
RenderTrees();
}
void RenderTrees()
{
for (int i = 0;i < types.Count;i++)
{
for (int lod = 0;lod < 4;lod++)
{
if (types[i].type.transform.childCount == lod)
break;
var tr = types[i].type.transform.GetChild(lod); // LOD0;
var mesh = tr.GetComponent<MeshFilter>().sharedMesh;
var materials = tr.GetComponent<MeshRenderer>().sharedMaterials;
int totalNum = types[i].currentCount[lod];
int offset = 0;
while (totalNum > 0)
{
int numToRender = Math.Min( 1023, totalNum );
for (int j = 0;j<mesh.subMeshCount;j++)
{
RenderParams rp = new RenderParams(materials[j]);
Graphics.RenderMeshInstanced( rp, mesh, j, types[i].positions[lod], numToRender, offset );
}
totalNum -= numToRender;
offset += numToRender;
}
}
}
} }
void ReadTreeDb(string text) void ReadTreeDb(string text)
...@@ -169,36 +241,38 @@ namespace Wander ...@@ -169,36 +241,38 @@ namespace Wander
Debug.LogError( "On Line " + line ); Debug.LogError( "On Line " + line );
} }
} }
} }
void InitializeFreeMatriceSlots() private void UpdateLoopOtherThread()
{ {
// Initially each slot in the matrix arrea is free. while (!isDone)
foreach (var type in types)
{ {
type.freeSlots = new(); LoadDesiredTiles();
for (int i = 0;i < type.maxTrees;i++) SpawnTrees();
DespawnTrees();
int newActiveCount = 0;
types.ForEach( t =>
{ {
freeTreeSlots.Add( i ); for (int i = 0;i <4;i++)
} {
newActiveCount += t.currentCount[i];
}
});
activeTreeCount = newActiveCount;
Thread.Sleep( 100 );
} }
} }
private void UpdateLoopOtherThread() int CalcLod( Vector3 pos )
{ {
LoadDesiredTiles(); float dist = Vector3.Distance( cameraPosition, pos );
SpawnTrees(); int lod = Mathf.RoundToInt( dist / 400 );
DespawnTrees(); return lod;
activeTreeCount = 0;
types.ForEach( t => {
activeTreeCount += t.maxTrees - t.freeSlots.Count;
} );
} }
void LoadIfIsNewTile( Vector2Int centre, int dx, int dy )
void LoadIfIsNewTile( Vector2Int centre, int dx, int dy, float time)
{ {
var coord = centre + new Vector2Int(dx, dy); var coord = centre + new Vector2Int(dx, dy);
if (!activeTiles.TryGetValue( coord, out var activeTile )) if (!activeTiles.TryGetValue( coord, out var activeTile ))
...@@ -207,15 +281,22 @@ namespace Wander ...@@ -207,15 +281,22 @@ namespace Wander
{ {
activeTiles.Add( coord, dbTile ); activeTiles.Add( coord, dbTile );
activeTile = dbTile; activeTile = dbTile;
activeTile.spawnedTrees.Clear(); activeTile.originTime = unityTime;
activeTile.spawnIndex = 0; activeTile.worldPos = new Vector3( dx, 0, dy )*tileSize + new Vector3( tileSize, 0, tileSize ) *0.5f;
activeTile.spawned = true; activeTile.lod = -1;
activeTile.originTime = time; if (activeTile.offsetAndNumPerType == null)
{
activeTile.offsetAndNumPerType = new Dictionary<int, (int offset, int num)>();
}
else
{
activeTile.offsetAndNumPerType.Clear();
}
} }
} }
if (activeTile != null) if (activeTile != null)
{ {
activeTile.lastTime = time; activeTile.lastTime = unityTime;
} }
} }
...@@ -257,73 +338,112 @@ namespace Wander ...@@ -257,73 +338,112 @@ namespace Wander
{ {
float tpx = cameraPosition.x; float tpx = cameraPosition.x;
float tpy = cameraPosition.z; float tpy = cameraPosition.z;
float time = unityTime;
var rdX = Mathf.FloorToInt( (float)(( originRDX + tpx ) * 0.01f )); var rdX = Mathf.FloorToInt( (float)(( originRDX + tpx ) * 0.01f ));
var rdY = Mathf.FloorToInt( (float)(( originRDY + tpy ) * 0.01f )); var rdY = Mathf.FloorToInt( (float)(( originRDY + tpy ) * 0.01f ));
var centreTile = new Vector2Int(rdX, rdY); var centreTile = new Vector2Int(rdX, rdY);
LoadIfIsNewTile( centreTile, 0, 0, time ); LoadIfIsNewTile( centreTile, 0, 0 );
for (int c = 1;c <= numRings;c++) for (int c = 1;c <= numRings;c++)
{ {
for (int y2 = -c;y2 <= c;y2++) for (int y2 = -c;y2 <= c;y2++)
{ {
LoadIfIsNewTile( centreTile, -c, y2, time ); LoadIfIsNewTile( centreTile, -c, y2 );
} }
for (int y2 = -c;y2 <= c;y2++) for (int y2 = -c;y2 <= c;y2++)
{ {
LoadIfIsNewTile( centreTile, c, y2, time ); LoadIfIsNewTile( centreTile, c, y2 );
} }
for (int x2 = -c+1;x2 <= c-1 ;x2++) for (int x2 = -c+1;x2 <= c-1 ;x2++)
{ {
LoadIfIsNewTile( centreTile, x2, c, time ); LoadIfIsNewTile( centreTile, x2, c );
} }
for (int x2 = -c+1;x2 <= c-1; x2++) for (int x2 = -c+1;x2 <= c-1; x2++)
{ {
LoadIfIsNewTile( centreTile, x2, -c, time ); LoadIfIsNewTile( centreTile, x2, -c );
} }
} }
} }
void RemoveTileFromTypeArrays( TreeTile tile, int oldLod )
{
if (oldLod == -1)
return;
foreach (var spawnees in tile.offsetAndNumPerType)
{
var type = spawnees.Key;
var offset = spawnees.Value.offset;
var numSpawn = spawnees.Value.num;
var numToMove = types[type].currentCount[oldLod] - (offset+numSpawn);
for (int i = 0;i < numToMove;i++)
{
types[type].positions[oldLod][offset+i] =
types[type].positions[oldLod][offset+i+numSpawn];
}
types[type].currentCount[oldLod] -= numSpawn;
}
tile.offsetAndNumPerType.Clear();
}
void SpawnTrees() void SpawnTrees()
{ {
foreach (var kvp in activeTiles) foreach (var kvp in activeTiles)
{ {
var tile = kvp.Value; var tile = kvp.Value;
if (Time.time - tile.originTime < requiredTime)
// Only start spawning trees from tile, if certain time tile is requested.
if (unityTime - tile.originTime < requiredTime)
{ {
continue; 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 ); // Only change trees, when lod has changed of tile.
if (!(treeType >= 0 && treeType < treenGenerator.types.Count)) int newLod = Mathf.Clamp( CalcLod( tile.worldPos ), 0, 3 );
continue; if (newLod == tile.lod)
{
continue;
}
int oldLod = tile.lod;
tile.lod = newLod;
RemoveTileFromTypeArrays( tile, oldLod );
var treePrefab = treenGenerator.types[treeType].trees.Random(); // For each tree in tile, see if the type of tree can be spawned as there are max tree counts per type.
if (treePrefab == null) for (int i = 0; i< tile.treeDb.Count; i++)
continue; {
var treeDb = tile.treeDb[i];
var type = Mathf.RoundToInt( treeDb.z );
var treePos = new Vector3( (float)(treeDb.x-originRDX), 0, (float)(treeDb.y-originRDY) ); // If type is invalid, continue to next. Don't spawn this one.
if (type < 0 || type >= types.Count)
{
continue;
}
// See if free slot available.
if ( HeightFromTerrains( tile, treePos, out float height )) if (types[type].currentCount[newLod] < types[type].positions[newLod].Length)
{ {
var slot = freeTreeSlots.Last(); // Find out offset on type per lod.
freeTreeSlots.RemoveAt( freeTreeSlots.Count-1 ); int offset = types[type].currentCount[newLod]++;
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 );
} // Obtain tree position.
var treePos = new Vector3( (float)(treeDb.x-originRDX), 20, (float)(treeDb.y-originRDY) );
// Set position on type per lod, using offset.
types[type].positions[newLod][offset] = Matrix4x4.TRS( treePos, Quaternion.Euler( 0, random.Next()%360, 0 ), Vector3.one );
// Remember the offset and num spawned per type. Because need to move per type num away, to avoid rendering instances that are not visible (vertex processor is invoked).
if (!tile.offsetAndNumPerType.TryGetValue( type, out var offsetAndType ))
{
tile.offsetAndNumPerType.Add( type, (offset, 1) );
}
else
{
offsetAndType.num++;
}
}
} }
} }
} }
...@@ -334,25 +454,23 @@ namespace Wander ...@@ -334,25 +454,23 @@ namespace Wander
foreach (var kvp in activeTiles) foreach (var kvp in activeTiles)
{ {
var tile = kvp.Value; var tile = kvp.Value;
if (tile.lastTime - Time.time < -removeDelay)
// Only despawn tile if certain time not desired anymore. This to avoid flipping in and out.
if (tile.lastTime - unityTime < -removeDelay)
{ {
if ( tile.spawnedTrees.Count != 0 ) // Remove trees for type arrays.
{ RemoveTileFromTypeArrays( tile, tile.lod );
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) // Initialize remove list if was not used yet.
removeList = new(); if (removeList==null)
removeList.Add( kvp.Key ); removeList = new();
kvp.Value.spawned = false;
} // Add this tile to remove list.
removeList.Add( kvp.Key );
} }
} }
// Remove actual inactive tiles.
removeList?.ForEach( rm => removeList?.ForEach( rm =>
{ {
activeTiles.Remove( rm ); activeTiles.Remove( rm );
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment