Skip to content

Commit

Permalink
Fix terrain tree handling
Browse files Browse the repository at this point in the history
  • Loading branch information
msz-rai authored and Jakub-Krakowiak committed Aug 24, 2023
1 parent 2991ab1 commit 5537cf3
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 140 deletions.
41 changes: 28 additions & 13 deletions Assets/RGLUnityPlugin/Scripts/LowLevelWrappers/RGLMeshObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -315,31 +315,46 @@ public RGLTerrainObject(Terrain terrain) :
terrainSubObjects = new List<IRGLObject>();
var treePrototypes = terrain.terrainData.treePrototypes;

List<Renderer>[] treePrototypesRenderes = new List<Renderer>[treePrototypes.Length];
bool[] treePrototypesHasLODGroup = new bool[treePrototypes.Length];

for (var i = 0; i < treePrototypes.Length; i++)
{
var gameObjects = RendererUtilities.GetAllGameObjectsOfPrefab(treePrototypes[i].prefab);
var renderers = RendererUtilities.GetUniqueRenderersInGameObjects(gameObjects);
foreach (var renderer in renderers)
treePrototypesRenderes[i] = new List<Renderer>();
var treePrefab = treePrototypes[i].prefab;
if (treePrefab.TryGetComponent<LODGroup>(out var lodGroup))
{
var treeIndex = i;
if (renderer is SkinnedMeshRenderer smr)
var lod = lodGroup.GetLODs()[0];
foreach (var renderer in lod.renderers)
{
terrainSubObjects.Add(new RGLSkinnedMeshRendererObject(smr,() =>
terrain.transform.localToWorldMatrix *
TerrainUtilities.GetTreePose(terrain, treeIndex) *
smr.localToWorldMatrix));
if (renderer.gameObject.TryGetComponent<MeshFilter>(out _))
{
treePrototypesRenderes[i].Add(renderer);
}
}
treePrototypesHasLODGroup[i] = true;
} else if (treePrefab.TryGetComponent<MeshRenderer>(out var mr) && treePrefab.TryGetComponent<MeshFilter>(out _))
{
treePrototypesRenderes[i].Add(mr);
treePrototypesHasLODGroup[i] = false;
}
}

for (var treeIdx = 0; treeIdx < terrain.terrainData.treeInstanceCount; treeIdx++)
{
int prototypeIdx = terrain.terrainData.GetTreeInstance(treeIdx).prototypeIndex;
foreach (var renderer in treePrototypesRenderes[prototypeIdx])
{
var treePose = TerrainUtilities.GetTreePose(terrain, treeIdx, treePrototypesHasLODGroup[prototypeIdx]);
if (renderer is MeshRenderer mr)
{
terrainSubObjects.Add(new RGLMeshRendererObject(mr,() =>
terrain.transform.localToWorldMatrix *
TerrainUtilities.GetTreePose(terrain, treeIndex) *
mr.localToWorldMatrix));
terrain.transform.localToWorldMatrix * treePose * mr.localToWorldMatrix));
}
}
}
}

protected override Matrix4x4 GetLocalToWorld()
{
return terrainTransform.localToWorldMatrix;
Expand Down
64 changes: 61 additions & 3 deletions Assets/RGLUnityPlugin/Scripts/SceneManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ private static IEnumerable<IRGLObject> IntoRGLObjectsUsingColliders(IEnumerable<
private static IEnumerable<IRGLObject> IntoRGLObjectsHybrid(IEnumerable<GameObject> gameObjects)
{
var collidersToYield = new HashSet<Collider>();
foreach (var renderer in RendererUtilities.GetUniqueRenderersInGameObjects(gameObjects))
foreach (var renderer in GetUniqueRenderersInGameObjects(gameObjects))
{
if (renderer is SkinnedMeshRenderer smr)
{
Expand Down Expand Up @@ -316,7 +316,7 @@ private static IEnumerable<IRGLObject> IntoRGLObjectsHybrid(IEnumerable<GameObje
/// </summary>
private static IEnumerable<IRGLObject> IntoRGLObjectsUsingMeshes(IEnumerable<GameObject> gameObjects)
{
foreach (var renderer in RendererUtilities.GetUniqueRenderersInGameObjects(gameObjects))
foreach (var renderer in GetUniqueRenderersInGameObjects(gameObjects))
{
if (renderer is MeshRenderer mr)
{
Expand Down Expand Up @@ -346,7 +346,65 @@ private static IEnumerable<IRGLObject> IntoRGLTerrainObjects(IEnumerable<GameObj
}
}
}


/// <summary>
/// Some prefabs have LOD Group component (Level Of Detail)
/// which implies using different renders based on the distance from the camera.
/// For raytracing purposes, the best available LOD is used (index: 0).
/// Some prefabs may not use LOD and use mesh renderers directly.
/// This function takes the aforementioned into account to output exactly one Renderer per game object.
/// It is guaranteed that yielded meshes are enabled and are not attached to a disabled LOD.
/// </summary>
private static IEnumerable<Renderer> GetUniqueRenderersInGameObjects(IEnumerable<GameObject> gameObjects)
{
var lodGroups = new HashSet<LODGroup>();
var smrs = new HashSet<SkinnedMeshRenderer>();
var mrs = new HashSet<MeshRenderer>();

foreach (var gameObject in gameObjects)
{
if (gameObject.TryGetComponent<LODGroup>(out var lodGroup))
{
lodGroups.Add(lodGroup);
}

if (gameObject.TryGetComponent<SkinnedMeshRenderer>(out var smr) && smr.enabled)
{
smrs.Add(smr);
}

bool hasMeshRenderer = gameObject.TryGetComponent<MeshRenderer>(out var mr) && mr.enabled;
bool hasMeshFilter = gameObject.TryGetComponent<MeshFilter>(out _); // Mesh filter can't be disabled
if (hasMeshRenderer && hasMeshFilter)
{
mrs.Add(mr);
}
}

foreach (var lodGroup in lodGroups)
{
for (int lodIndex = 0; lodIndex < lodGroup.lodCount; ++lodIndex)
{
var lod = lodGroup.GetLODs()[lodIndex];
foreach (var renderer in lod.renderers)
{
// In theory it is possible that the renderer-containing game object is not a descendant of the LoD
// therefore we need to additionally check if renderer.gameObject.activeInHierarchy
if (lodGroup.enabled && lodIndex == 0 && renderer.enabled && renderer.gameObject.activeInHierarchy)
{
yield return renderer;
}

smrs.Remove(renderer as SkinnedMeshRenderer);
mrs.Remove(renderer as MeshRenderer);
}
}
}

foreach (var smr in smrs) yield return smr;
foreach (var mr in mrs) yield return mr;
}

private static bool IsNotActiveOrParentHasLidar(GameObject gameObject)
{
return !gameObject.activeInHierarchy || gameObject.GetComponentsInParent<LidarSensor>().Length != 0;
Expand Down
82 changes: 0 additions & 82 deletions Assets/RGLUnityPlugin/Scripts/Utilities/RendererUtilities.cs

This file was deleted.

This file was deleted.

59 changes: 20 additions & 39 deletions Assets/RGLUnityPlugin/Scripts/Utilities/TerrainUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public static Mesh GetTerrainMesh(Terrain terrain)
var solidSurfaceTiles = terrainData.GetHoles(0, 0, holesResolution, holesResolution);
var scale = terrainData.heightmapScale;
var vertices = new Vector3[heightmapResolution * heightmapResolution];

if (!correctHolesResolution)
{
Debug.LogWarning($"Terrain {terrain.GetInstanceID()} holes resolution is incorrect, holes will be ignored by RGL");
Expand All @@ -35,8 +35,7 @@ public static Mesh GetTerrainMesh(Terrain terrain)
// this is the number of squares alongside each axis of the terrain
// e.g. if you have a 3x3 grid of points you can fill the space between them using a 2x2 square grid
var tileResolution = heightmapResolution - 1;



// count solid terrain tiles (not holes)
var tileCount = 0;
foreach (var solidSurface in solidSurfaceTiles)
Expand All @@ -51,7 +50,7 @@ public static Mesh GetTerrainMesh(Terrain terrain)
{
tileCount = tileResolution * tileResolution;
}

// there are 2 triangles per square tile, so 6 indices
var triangles = new int[tileCount * 2 * 3];

Expand All @@ -60,32 +59,31 @@ public static Mesh GetTerrainMesh(Terrain terrain)
{
for (var x = 0; x < tileResolution; x++)
{

if (correctHolesResolution && !solidSurfaceTiles[z, x])
{
continue;
}

var sampleBase = x * heightmapResolution + z;

// first triangle of tile
triangles[trianglesIndex++] = sampleBase;
triangles[trianglesIndex++] = sampleBase + heightmapResolution;
triangles[trianglesIndex++] = sampleBase + heightmapResolution + 1;

// second triangle of tile
triangles[trianglesIndex++] = sampleBase;
triangles[trianglesIndex++] = sampleBase + 1;
triangles[trianglesIndex++] = sampleBase + 1 + heightmapResolution;
}
}

var uv = new Vector2[vertices.Length];
for (var i = 0; i < vertices.Length; i++)
{
uv[i] = new Vector2(vertices[i].x / (tileResolution * scale.x), vertices[i].z / (tileResolution * scale.z));
}

var heightmapMesh = new Mesh();
heightmapMesh.indexFormat = IndexFormat.UInt32;
heightmapMesh.vertices = vertices;
Expand All @@ -95,38 +93,13 @@ public static Mesh GetTerrainMesh(Terrain terrain)
return heightmapMesh;
}

// public static Mesh GetTreeMesh(Terrain terrain, int treeIndex)
// {
// var terrainData = terrain.terrainData;
//
// if (treePrefab.TryGetComponent(out LODGroup lodGroup))
// {
// if (lodGroup.GetLODs()[0].renderers[0].TryGetComponent(out MeshFilter meshFilter))
// {
// return meshFilter.sharedMesh;
// }
//
// Debug.LogWarning($"Tree[{treeIndex}] \"{treePrefab.name}\" of terrain {terrain.GetInstanceID()} has LODGroup component with no MeshFilter component, it will be ignored by RGL");
// return null;
// }
// else if (treePrefab.TryGetComponent(out MeshFilter meshFilter))
// {
// Debug.LogWarning($"Tree[{treeIndex}] \"{treePrefab.name}\" of terrain {terrain.GetInstanceID()} has no LODGroup component, but it has a MeshFilter component. " +
// $"The tree's rotation and scale is not going o match those in the Unity Editor, " +
// $"since Unity Editor has a bug and does not rotate or scale trees without LODGroup components");
// return meshFilter.sharedMesh;
// }
// Debug.LogWarning($"Tree[{treeIndex}] \"{treePrefab.name}\" of terrain {terrain.GetInstanceID()} has no LODGroup or MeshFilter component, it will be ignored by RGL");
// return null;
// }

public static Matrix4x4 GetTreePose(Terrain terrain, int treeIndex)
public static Matrix4x4 GetTreePose(Terrain terrain, int treeIndex, bool applyRotationAndScale)
{
var terrainPosition = terrain.transform.position;
var terrainData = terrain.terrainData;
var resolution = terrainData.heightmapResolution;
var heightmapScale = terrainData.heightmapScale;
var treeInstance = terrainData.treeInstances[treeIndex];
var treeInstance = terrainData.GetTreeInstance(treeIndex);
var treePosition = treeInstance.position;

var translation = new Vector3(treePosition.x, 0, treePosition.z);
Expand All @@ -135,9 +108,17 @@ public static Matrix4x4 GetTreePose(Terrain terrain, int treeIndex)
var samplePose = new Vector3(terrainPosition.x + translation.x, 0, terrainPosition.z + translation.z);
translation.y = terrain.SampleHeight(samplePose);

var rotation = Quaternion.AngleAxis(treeInstance.rotation * Mathf.Rad2Deg, Vector3.up);
var rotation = Quaternion.identity;
if (applyRotationAndScale)
{
rotation = Quaternion.AngleAxis(treeInstance.rotation * Mathf.Rad2Deg, Vector3.up);
}

var scale = new Vector3(treeInstance.widthScale, treeInstance.heightScale, treeInstance.widthScale);
var scale = Vector3.one;
if (applyRotationAndScale)
{
scale = new Vector3(treeInstance.widthScale, treeInstance.heightScale, treeInstance.widthScale);
}

return Matrix4x4.TRS(translation, rotation, scale);
}
Expand Down

0 comments on commit 5537cf3

Please sign in to comment.