Skip to content

Instantly share code, notes, and snippets.

@bernatgy
Created March 12, 2024 18:45
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/217731ee8074e515e33ce8641ba647d5 to your computer and use it in GitHub Desktop.
Save bernatgy/217731ee8074e515e33ce8641ba647d5 to your computer and use it in GitHub Desktop.
Cut together, less than optimal solution to terrain baking in Unity DOTS. Could probably be improved with chunks, baking systems, etc...
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;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment