ThomagicRenderer/Runtime/Scripts/TreeRenderer.cs

273 lines
11 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace Assets.ThoMagic.Renderer
{
public class TreeRenderer : TreeStreamer
{
private Terrain terrain;
private TerrainData terrainData;
private List<int> treePrototypesRegistered;
private Dictionary<int, TreeInfo> treeToInstances = new Dictionary<int, TreeInfo>();
private HashSet<int> touchedTrees = new HashSet<int>();
private bool isDirty;
private float maxTreeDistance = 0;
class TreeInfo
{
internal int instanceId;
internal int objectId;
}
public TreeRenderer(Terrain terrain)
: base(terrain)
{
this.terrain = terrain;
terrainData = terrain.terrainData;
}
public void LateUpdate()
{
if (isDirty)
{
isDirty = false;
ApplyChanges();
}
if (Application.isEditor && !Application.isPlaying)
RebuildChangedPrototypes();
}
int GetTreeTerrainInstanceId(TreeInstance tree)
{
return HashCode.Combine(tree.position.x, tree.position.y, tree.position.z, tree.rotation, tree.widthScale, tree.heightScale, tree.prototypeIndex);
}
public void ApplyChanges()
{
Vector3 size = terrainData.size;
Vector3 offset = terrain.transform.position;
var treeInstances = terrainData.treeInstances;
touchedTrees.Clear();
foreach (var treeInstance in treeInstances)
{
if (treeInstance.widthScale == 0)
continue;
var treeTerrainInstanceId = GetTreeTerrainInstanceId(treeInstance);
if (!treeToInstances.ContainsKey(treeTerrainInstanceId))
{
var treeId = treePrototypesRegistered[treeInstance.prototypeIndex];
var prefab = RendererPool.GetObject(treeId);
var quaternion = Quaternion.Euler(0.0f, 57.2957801818848f * treeInstance.rotation, 0.0f);
var instanceId = AddInstance(treeId, new Vector3(treeInstance.position.x * size.x,
treeInstance.position.y * size.y,
treeInstance.position.z * size.z) + offset, quaternion,
treeInstance.widthScale * prefab.transform.localScale.x,
treeInstance.heightScale * prefab.transform.localScale.y
);
treeToInstances[treeTerrainInstanceId] = new TreeInfo { instanceId = instanceId, objectId = treeId };
touchedTrees.Add(treeTerrainInstanceId);
}
else
{
touchedTrees.Add(treeTerrainInstanceId);
}
}
foreach(var treeTerrainInstanceId in treeToInstances.Keys.ToList())
{
if(!touchedTrees.Contains(treeTerrainInstanceId))
{
RemoveInstance(treeToInstances[treeTerrainInstanceId].objectId, treeToInstances[treeTerrainInstanceId].instanceId);
treeToInstances.Remove(treeTerrainInstanceId);
}
}
Build(maxTreeDistance);
}
public void Load()
{
maxTreeDistance = 0;
Vector3 size = terrainData.size;
Vector3 offset = terrain.transform.position;
Recycle();
int ownerHash = GetHashCode();
if (treePrototypesRegistered == null)
treePrototypesRegistered = new List<int>();
var treePrototypes = terrainData.treePrototypes;
if(treePrototypes.Length > 0)
{
foreach(var treePrototype in treePrototypes)
{
if(treePrototype.prefab != null)
{
var settings = GetSettingsOrDefault(treePrototype.prefab);
treePrototypesRegistered.Add(RendererPool.RegisterObject(treePrototype.prefab, settings, this, ownerHash));
if (settings.Settings.RenderDistance == 0)
maxTreeDistance = float.MaxValue;
if(settings.Settings.RenderDistance > maxTreeDistance)
maxTreeDistance = settings.Settings.RenderDistance;
}
}
var treeInstances = terrainData.treeInstances;
foreach (var treeInstance in treeInstances)
{
var treeTerrainInstanceId = GetTreeTerrainInstanceId(treeInstance);
var treeId = treePrototypesRegistered[treeInstance.prototypeIndex];
var prefab = RendererPool.GetObject(treeId);
var quaternion = Quaternion.Euler(0.0f, 57.2957801818848f * treeInstance.rotation, 0.0f);
var instanceId = AddInstance(treeId, new Vector3(treeInstance.position.x * size.x,
treeInstance.position.y * size.y,
treeInstance.position.z * size.z) + offset, quaternion,
treeInstance.widthScale * prefab.transform.localScale.x,
treeInstance.heightScale * prefab.transform.localScale.y);
treeToInstances[treeTerrainInstanceId] = new TreeInfo { instanceId = instanceId, objectId = treeId };
}
}
Build(maxTreeDistance);
}
private void Recycle()
{
if (treePrototypesRegistered == null)
return;
treeToInstances.Clear();
Clear();
var ownerHash = GetHashCode();
for (int index = 0; index < treePrototypesRegistered.Count; ++index)
{
var objectId = treePrototypesRegistered[index];
RendererPool.RemoveObject(objectId, ownerHash);
}
treePrototypesRegistered.Clear();
}
private void RebuildChangedPrototypes()
{
TreePrototype[] treePrototypes = terrainData.treePrototypes;
if (treePrototypes.Length != treePrototypesRegistered.Count)
{
Load();
}
else
{
int ownerHash = GetHashCode();
maxTreeDistance = 0;
for (int index = 0; index < treePrototypesRegistered.Count; ++index)
{
int treeId = treePrototypesRegistered[index];
GameObject prefab = treePrototypes[index].prefab;
GameObject gameObject = RendererPool.GetObject(treeId);
if (prefab != gameObject)
{
RendererPool.RemoveObject(treeId, ownerHash);
if (prefab != null)
{
var settings = GetSettingsOrDefault(prefab);
if (settings.Settings.RenderDistance == 0)
maxTreeDistance = float.MaxValue;
if (settings.Settings.RenderDistance > maxTreeDistance)
maxTreeDistance = settings.Settings.RenderDistance;
treePrototypesRegistered[index] = RendererPool.RegisterObject(prefab, settings, this, ownerHash);
}
}
else if (RendererPool.ContentHashChanged(treeId))
{
RendererPool.RemoveObject(treeId, ownerHash);
if (prefab != null)
{
var settings = GetSettingsOrDefault(prefab);
if (settings.Settings.RenderDistance == 0)
maxTreeDistance = float.MaxValue;
if (settings.Settings.RenderDistance > maxTreeDistance)
maxTreeDistance = settings.Settings.RenderDistance;
treePrototypesRegistered[index] = RendererPool.RegisterObject(prefab, settings, this, ownerHash);
}
}
else if (prefab != null)
{
var settings = GetSettingsOrDefault(prefab);
if (settings.Settings.RenderDistance == 0)
maxTreeDistance = float.MaxValue;
if (settings.Settings.RenderDistance > maxTreeDistance)
maxTreeDistance = settings.Settings.RenderDistance;
RendererPool.SetObjectSettings(prefab, settings);
}
}
}
}
public void Destroy()
{
Recycle();
}
public void OnTerrainChanged(TerrainChangedFlags flags)
{
if(flags.HasFlag(TerrainChangedFlags.TreeInstances))
isDirty = true;
else if (flags.HasFlag(TerrainChangedFlags.DelayedHeightmapUpdate))
isDirty = true;
else if (flags.HasFlag(TerrainChangedFlags.Heightmap))
isDirty = true;
else if (flags.HasFlag(TerrainChangedFlags.HeightmapResolution))
isDirty = true;
else if (flags.HasFlag(TerrainChangedFlags.Holes))
isDirty = true;
else if (flags.HasFlag(TerrainChangedFlags.DelayedHolesUpdate))
isDirty = true;
else if (flags.HasFlag(TerrainChangedFlags.FlushEverythingImmediately))
isDirty = true;
}
private IInstanceRenderSettings GetSettingsOrDefault(GameObject gameObject)
{
IInstanceRenderSettings instanceRenderSettings;
return gameObject.TryGetComponent(out instanceRenderSettings) ? instanceRenderSettings : new DefaultRenderSettings();
}
private class DefaultRenderSettings : IInstanceRenderSettings
{
public InstanceRenderSettings Settings => new InstanceRenderSettings()
{
Render = true,
DensityInDistance = 1f,
DensityInDistanceFalloff = Vector2.zero,
RenderDistance = 0.0f,
ShadowDistance = 0.0f,
Shadows = true,
Supported = true
};
}
}
}