Skip to content

Instantly share code, notes, and snippets.

@bernatgy
Last active March 12, 2024 18:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bernatgy/be96366a3df4ab349190c292b3c588ed to your computer and use it in GitHub Desktop.
Save bernatgy/be96366a3df4ab349190c292b3c588ed to your computer and use it in GitHub Desktop.
Solution to terrain baking in Unity DOTS. Could be improved with chunks, baking systems, etc... All credits to the lovely folks over at https://forum.unity.com/threads/using-unity-terrain-with-dots-workflow.755105/ I just cut it together, made some small adjustments and made sure it still works with *Entities 1.2.0-pre.6*
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Physics;
using UnityEngine;
using UnityEngine.Serialization;
namespace Custom.Authoring
{
public class TerrainAuthoring : MonoBehaviour
{
[FormerlySerializedAs("CollidesWith")] [SerializeField, Tooltip("A bit mask describing which layers this object can collide with.")]
private LayerMask collidesWith;
[FormerlySerializedAs("GroupIndex")] [SerializeField, Tooltip("An optional override for the bit mask checks. If the value in both objects is equal and positive, the objects always collide. If the value in both objects is equal and negative, the objects never collide.")]
private int groupIndex = 0;
[FormerlySerializedAs("CollisionMethod")] [SerializeField, Tooltip("Triangles works like a mesh, is more accurate but more expensive. VertexSamples works like a terrain, much less accurate but very fast.")]
private Unity.Physics.TerrainCollider.CollisionMethod collisionMethod = Unity.Physics.TerrainCollider.CollisionMethod.Triangles;
class Baker : Baker<TerrainAuthoring>
{
public override void Bake(TerrainAuthoring authoring)
{
// fetch the terrain monobehaviour
if (!authoring.TryGetComponent<Terrain>(out var terrain))
{
Debug.LogWarning($"Terrain game object not found!");
return;
}
// This keeps the Terrain game-object synchronized with the baked mesh.
// But it doesn't really work like it's supposed to. Often I have to Reimport the scene anyway.
this.DependsOn(terrain);
// setup a collision filter using the parameters the user specified.
var collisionFilter = new CollisionFilter
{
BelongsTo = 1u << authoring.gameObject.layer,
CollidesWith = Convert.ToUInt32(authoring.collidesWith.value),
GroupIndex = authoring.groupIndex
};
// create the physics terrain collider and add it to the baked entity
// see https://forum.unity.com/threads/using-unity-terrain-with-dots-workflow.755105
var collider = CreateTerrainColliderV2(terrain.terrainData, collisionFilter, authoring.collisionMethod);
if (!collider.IsValid)
{
Debug.LogWarning($"Collider is invalid!");
return;
}
var ent = this.GetEntity(TransformUsageFlags.Dynamic);
this.AddComponent(ent, collider);
// This is needed for the collider to be registered properly in the physics world
this.AddSharedComponent(ent, new PhysicsWorldIndex());
}
}
private static PhysicsCollider CreateTerrainColliderV2(TerrainData terrainData, CollisionFilter filter, Unity.Physics.TerrainCollider.CollisionMethod method)
{
var physicsCollider = new PhysicsCollider();
var scale = terrainData.heightmapScale;
var colliderHeights = new NativeArray<float>(terrainData.heightmapResolution * terrainData.heightmapResolution, Allocator.TempJob);
var terrainHeights = terrainData.GetHeights(0, 0, terrainData.heightmapResolution, terrainData.heightmapResolution);
// NOTE: Solves an issue with perfectly flat terrain failing to collide with objects.
var heightmapScale = terrainData.size.z;
var smallestOffset = 0.01f; // 1 cm offset, works with 2048 resolution terrain
var heightmapValuePerMeterInWorldSpace = 0.5f / heightmapScale;
var inHeightMapUnits = smallestOffset * heightmapValuePerMeterInWorldSpace;
for (var j = 0; j < terrainData.heightmapResolution; j++)
{
for (var i = 0; i < terrainData.heightmapResolution; i++)
{
var checkerboard = (i + j) % 2;
colliderHeights[j + (i * terrainData.heightmapResolution)] = terrainHeights[i, j] + inHeightMapUnits * checkerboard; // Note: assumes terrain neighboars are never 1 cm difference from eachother
}
}
// Note: Heightmap is between 0 and 0.5f (https://forum.unity.com/threads/terraindata-heightmaptexture-float-value-range.672421/)
physicsCollider.Value = Unity.Physics.TerrainCollider.Create(colliderHeights, new int2(terrainData.heightmapResolution, terrainData.heightmapResolution), scale, method, filter);
colliderHeights.Dispose();
return physicsCollider;
}
}
}
@bernatgy
Copy link
Author

bernatgy commented Mar 12, 2024

Instructions

  • Duplicate your Terrain, put one of them outside your Subscene, one of them inside the Subscene.
  • Remove Terrain Collider from both of them.
  • For the one outside, make sure Drawing is enabled.
    image
  • For the one inside, disable Draw and put this Authoring script on.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment