Terrain Authoring Problem

Hello everyone. I am new to Unity DOTS and I need to learn how to create terrain. After I had seen that Using Unity Terrain with DOTS workflow thread, I tried to do the same thing as Avol done there (2 page). But I got some error which I poorly understand and do not know what to do with it
Here is the full text of it:

AssertionException: Assertion failure. Value was False
Expected: True
UnityEngine.Assertions.Assert.Fail (System.String message, System.String userMessage) (at :0)
UnityEngine.Assertions.Assert.IsTrue (System.Boolean condition, System.String message) (at :0)
UnityEngine.Assertions.Assert.IsTrue (System.Boolean condition) (at :0)
Unity.Physics.Math+ScaledMTransform…ctor (Unity.Mathematics.RigidTransform transform, System.Single uniformScale) (at ./Library/PackageCache/com.unity.physics@1.0.11/Unity.Physics/Base/Math/Physics_Transform.cs:109)
Unity.Physics.RigidBody+RigidBodyUtil.CastRay[T] (Unity.Entities.BlobAssetReference1[T] bodyCollider, Unity.Entities.Entity rbEntity, Unity.Physics.RaycastInput input, T& collector, Unity.Mathematics.RigidTransform worldFromBody, System.Single unifromScale) (at ./Library/PackageCache/com.unity.physics@1.0.11/Unity.Physics/Collision/RigidBody/RigidBody.cs:110) Unity.Physics.RigidBody.CastRay[T] (Unity.Physics.RaycastInput input, T& collector) (at ./Library/PackageCache/com.unity.physics@1.0.11/Unity.Physics/Collision/RigidBody/RigidBody.cs:92) Unity.Physics.Broadphase+BvhLeafProcessor.RayLeaf[T] (Unity.Physics.RaycastInput input, System.Int32 rigidBodyIndex, T& collector) (at ./Library/PackageCache/com.unity.physics@1.0.11/Unity.Physics/Collision/World/Broadphase.cs:624) Unity.Physics.BoundingVolumeHierarchy.Raycast[TProcessor,TCollector] (Unity.Physics.RaycastInput input, TProcessor& leafProcessor, TCollector& collector) (at ./Library/PackageCache/com.unity.physics@1.0.11/Unity.Physics/Collision/Geometry/BoundingVolumeHierarchy.cs:373) Unity.Physics.Broadphase.CastRay[T] (Unity.Physics.RaycastInput input, Unity.Collections.NativeArray1[T] rigidBodies, T& collector) (at ./Library/PackageCache/com.unity.physics@1.0.11/Unity.Physics/Collision/World/Broadphase.cs:534)
Unity.Physics.CollisionWorld.CastRay[T] (Unity.Physics.RaycastInput input, T& collector) (at ./Library/PackageCache/com.unity.physics@1.0.11/Unity.Physics/Collision/World/CollisionWorld.cs:338)
Unity.Physics.QueryWrappers.RayCast[T] (T& target, Unity.Physics.RaycastInput input, Unity.Physics.RaycastHit& closestHit) (at ./Library/PackageCache/com.unity.physics@1.0.11/Unity.Physics/Collision/Queries/Collidable.cs:664)
Unity.Physics.CollisionWorld.CastRay (Unity.Physics.RaycastInput input, Unity.Physics.RaycastHit& closestHit) (at ./Library/PackageCache/com.unity.physics@1.0.11/Unity.Physics/Collision/World/CollisionWorld.cs:318)
MovementJob.Execute (Unity.Transforms.LocalTransform& transform, MovementComponent& movementComponent) (at Assets/Scripts/Player/MovementSystem.cs:72)
MovementJob.Execute (Unity.Entities.ArchetypeChunk& chunk, System.Int32 chunkIndexInQuery, System.Boolean useEnabledMask, Unity.Burst.Intrinsics.v128& chunkEnabledMask) (at Unity.Entities.SourceGen.JobEntityGenerator/Unity.Entities.SourceGen.JobEntity.JobEntityGenerator/Temp/GeneratedCode/Assembly-CSharp/MovementSystem__JobEntity_4119310620.g.cs:34)
MovementJob.Unity.Entities.IJobChunk.Execute (Unity.Entities.ArchetypeChunk& chunk, System.Int32 unfilteredChunkIndex, System.Boolean useEnabledMask, Unity.Burst.Intrinsics.v128& chunkEnabledMask) (at :0)
Unity.Entities.JobChunkExtensions+JobChunkProducer1[T].ExecuteInternal (Unity.Entities.JobChunkExtensions+JobChunkWrapper1[T]& jobWrapper, System.IntPtr bufferRangePatchData, Unity.Jobs.LowLevel.Unsafe.JobRanges& ranges, System.Int32 jobIndex) (at ./Library/PackageCache/com.unity.entities@1.0.11/Unity.Entities/IJobChunk.cs:412)
Unity.Entities.JobChunkExtensions+JobChunkProducer1[T].Execute (Unity.Entities.JobChunkExtensions+JobChunkWrapper1[T]& jobWrapper, System.IntPtr additionalPtr, System.IntPtr bufferRangePatchData, Unity.Jobs.LowLevel.Unsafe.JobRanges& ranges, System.Int32 jobIndex) (at ./Library/PackageCache/com.unity.entities@1.0.11/Unity.Entities/IJobChunk.cs:359)
Unity.Jobs.JobHandle:ScheduleBatchedJobsAndComplete(JobHandle&)
Unity.Jobs.JobHandle:Complete()
MovementSystem:OnUpdate(SystemState&) (at Assets/Scripts/Player/MovementSystem.cs:34)
MovementSystem:__codegen__OnUpdate(IntPtr, IntPtr)
Unity.Entities.<>c__DisplayClass9_0:b__0(IntPtr, IntPtr) (at ./Library/PackageCache/com.unity.entities@1.0.11/Unity.Entities/SystemBaseRegistry.cs:256)
Unity.Entities.UnmanagedUpdate_0000158C$BurstDirectCall:wrapper_native_indirect_000001D9106A1AE0(IntPtr&, Void*)
Unity.Entities.UnmanagedUpdate_0000158C$BurstDirectCall:Invoke(Void*)
Unity.Entities.WorldUnmanagedImpl:UnmanagedUpdate(Void*) (at ./Library/PackageCache/com.unity.entities@1.0.11/Unity.Entities/WorldUnmanaged.cs:828)
Unity.Entities.WorldUnmanagedImpl:UpdateSystem(SystemHandle) (at ./Library/PackageCache/com.unity.entities@1.0.11/Unity.Entities/WorldUnmanaged.cs:894)
Unity.Entities.ComponentSystemGroup:UpdateAllSystems() (at ./Library/PackageCache/com.unity.entities@1.0.11/Unity.Entities/ComponentSystemGroup.cs:729)
Unity.Entities.ComponentSystemGroup:OnUpdate() (at ./Library/PackageCache/com.unity.entities@1.0.11/Unity.Entities/ComponentSystemGroup.cs:693)
Unity.Entities.SystemBase:Update() (at ./Library/PackageCache/com.unity.entities@1.0.11/Unity.Entities/SystemBase.cs:418)
Unity.Entities.DummyDelegateWrapper:TriggerUpdate() (at ./Library/PackageCache/com.unity.entities@1.0.11/Unity.Entities/ScriptBehaviourUpdateOrder.cs:526)

Here is the code where error refers if I double click it

/// <summary>   Constructor. </summary>
///
/// <param name="transform">    The transform. </param>
/// <param name="uniformScale"> The uniform scale. </param>
public ScaledMTransform(RigidTransform transform, float uniformScale)
{
    Transform = new MTransform(transform);
    UnityEngine.Assertions.Assert.IsTrue(uniformScale != 0.0f);

    m_Scale = uniformScale;
}

And here are my own pretty simple code:

public partial struct MovementSystem : ISystem
{
    public void OnCreate(ref SystemState state)
    {
        state.RequireForUpdate<MovementComponent>();
    }

 
    public void OnUpdate(ref SystemState state)
    {
        var moveJob = new MovementJob { time = SystemAPI.Time.DeltaTime, collisionWorld = SystemAPI.GetSingleton<PhysicsWorldSingleton>().CollisionWorld };
        JobHandle a = moveJob.Schedule(state.Dependency);
        a.Complete();
    }

    public void OnDestroy(ref SystemState state)
    {
     
    }
}

public partial struct MovementJob : IJobEntity
{
    public float time;
    public CollisionWorld collisionWorld;
    public void Execute(ref LocalTransform transform, in MovementComponent movementComponent)
    {
        if (math.distancesq(movementComponent.target, transform.Position) < 1f)
            return;
        RaycastInput rayDown = new RaycastInput
        {
            Start = transform.Position,
            End = transform.Position + math.mul(transform.Rotation, new float3(0f, -100f, 0f)),
            Filter = new CollisionFilter
            {
                BelongsTo = ~0u,
                CollidesWith = 1u << 7,
                GroupIndex = 0
            }
        };
        if (collisionWorld.CastRay(rayDown, out Unity.Physics.RaycastHit closestHit))
        {
            transform.Position.y = closestHit.Position.y + 1;
           //transform.Rotation = Quaternion.Euler(closestHit.SurfaceNormal);
           //Debug.Log(closestHit.SurfaceNormal);
        }
        else
        {
            Debug.Log("ERROR: Terrain under unit is not found");
        }
        float3 tempDir = movementComponent.target - transform.Position;
        tempDir.y = 0;
        float3 dir = math.normalize(tempDir);
        transform.Position += dir * time * movementComponent.speed;


    }
}
public class TerrainAuthoring : MonoBehaviour
{
    [SerializeField] LayerMask belongsToLayers;
    [SerializeField] LayerMask collidesWithLayers;
    [SerializeField] int groupIndex;
    private void Awake()
    {
        if (!TryGetComponent(out Terrain terrain))
        {
            Debug.Log("Terrain component was not found");
            return;
        }

        CollisionFilter filter = new CollisionFilter
        {
            BelongsTo = (uint)belongsToLayers.value,
            CollidesWith = (uint)collidesWithLayers.value,
            GroupIndex = groupIndex
        };

        PhysicsCollider terrainCollider = CreateTerrainCollider(terrain.terrainData, filter);


        EntityManager entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
        Entity terrainEntity = entityManager.CreateEntity();
        Debug.Log("Created Entity");
        entityManager.AddComponent<PhysicsCollider>(terrainEntity);
        Debug.Log("Added PhysicsCollider");
        entityManager.SetComponentData<PhysicsCollider>(terrainEntity, terrainCollider);
        Debug.Log("Setted PhysicsCollider");
        entityManager.AddComponent<LocalToWorld>(terrainEntity);
        Debug.Log("Added LocalToWorld");
        entityManager.AddComponent<LocalTransform>(terrainEntity);
        Debug.Log("Added LocalTransform");
        entityManager.AddComponent<PhysicsWorldIndex>(terrainEntity);
        Debug.Log("Added PhysicsWorldIndex");
    }

    private PhysicsCollider CreateTerrainCollider (TerrainData terrainData, CollisionFilter collisionFilter)
    {
        int resolution = terrainData.heightmapResolution;
        int2 size = new int2(resolution, resolution);
        Vector3 scale = terrainData.heightmapScale;
        Debug.Log(scale);

        NativeArray<float> heights = new NativeArray<float>(resolution * resolution, Allocator.Temp);
        float[,] temporaryHeightsMatrice = terrainData.GetHeights(0, 0, resolution, resolution);
        for (int j = 0; j < size.y; j++)
            for (int i = 0; i < size.x; i++)
            {
                var h = temporaryHeightsMatrice[i, j];
                heights[j + i * size.x] = h;
            }

        Debug.Log("Check");
        PhysicsCollider collider = new PhysicsCollider
        {
            Value = Unity.Physics.TerrainCollider.Create(heights, size, scale, Unity.Physics.TerrainCollider.CollisionMethod.Triangles)
        };
        Debug.Log("Check2");
        heights.Dispose();
        Debug.Log("Check3");
        return collider;
    }
}

And here is the hierarchy, character components and terrain components (maybe my mistake is there, I don`t know :()
9216315--1286205--upload_2023-8-12_22-12-49.png

Because all debugs in TerrainAuthoring appear in Console, I suggest that terrain entity was at least created. But when MovingSystem is trying to Raycast it, some problem appears. But what is it and how to solve it?..

I think I had a similar error when I set my scale to 0 so double check your scale values to see if that is it.

Your terrain is out of subscene, because (i guess) your doing all the authoring in your TerrainAuthoring Awake and not in a baker. But in your terrain authoring Awake, you only add a localTransform and localToWorld, you don’t set them. You might try to set the localTransform, see if it solves the problem. Check the entities hierarchy to see the localTransform and localToWorld actual values of your terrain.

1 Like

Thanks mgi388, really big thanks Tigrian (again :)). As you said the error was connected with not setting the Components values (By default it thought that Scale equals 0f, which is not supposed to be so in any case). But unfortunately now it is another problem - RayCast on that terrain just doesnt work :frowning: (Again problems with RayCasting). To be more accurate, on the first update RayCast always find the surface (hit in the (0f,0f,0f) position), but from the second update it is no collision with the Ray. Firstly I thought that I messed up with Rotation value on the terrain, but as I understood - no, it is alright. So after a lot of different tries I am out of ideas what to do with it.

public class TerrainAuthoring : MonoBehaviour
{
    [SerializeField] LayerMask belongsToLayers;
    [SerializeField] LayerMask collidesWithLayers;
    [SerializeField] int groupIndex;
    private void Awake()
    {
        if (!TryGetComponent(out Terrain terrain))
        {
            Debug.Log("Terrain component was not found");
            return;
        }

        CollisionFilter filter = new CollisionFilter
        {
            BelongsTo = (uint)belongsToLayers.value,
            CollidesWith = (uint)collidesWithLayers.value,
            GroupIndex = groupIndex
        };

        PhysicsCollider terrainCollider = CreateTerrainCollider(terrain.terrainData, filter);


        EntityManager entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
        Entity terrainEntity = entityManager.CreateEntity();
        entityManager.AddComponent<PhysicsCollider>(terrainEntity);
        entityManager.SetComponentData<PhysicsCollider>(terrainEntity, terrainCollider);;
        entityManager.AddComponent<LocalToWorld>(terrainEntity);
        entityManager.SetComponentData(terrainEntity, new LocalToWorld());
        entityManager.AddComponent<LocalTransform>(terrainEntity);
        entityManager.SetComponentData(terrainEntity, new LocalTransform
        {
            Rotation = quaternion.Euler(0f, 1f, 0f),
            Scale = 1f
        });
        entityManager.AddComponent<PhysicsWorldIndex>(terrainEntity);
    }

    private PhysicsCollider CreateTerrainCollider (TerrainData terrainData, CollisionFilter collisionFilter)
    {
        int resolution = terrainData.heightmapResolution;
        int2 size = new int2(resolution, resolution);
        Vector3 scale = terrainData.heightmapScale;

        NativeArray<float> heights = new NativeArray<float>(resolution * resolution, Allocator.Temp);
        float[,] temporaryHeightsMatrice = terrainData.GetHeights(0, 0, resolution, resolution);
        for (int j = 0; j < size.y; j++)
            for (int i = 0; i < size.x; i++)
            {
                var h = temporaryHeightsMatrice[i, j];
                heights[j + i * size.x] = h;
            }

        PhysicsCollider collider = new PhysicsCollider
        {
            Value = Unity.Physics.TerrainCollider.Create(heights, size, scale, Unity.Physics.TerrainCollider.CollisionMethod.Triangles)
        };
        heights.Dispose();
        return collider;
    }
}

For the rotation, you can use quaternion.identity, (except if rotating by 1 degrees on y axis is what you want). But that should not be the problem. Your code looks good, everything should work. I will try to reproduce it as soon as i get to my computer, see where it can fail.

Alright, I think I found. I reproduced the same steps as you, and got the same result. When you create a terrain, its origin (placed at (0,0,0)) is at the bottom left corner of the terrain. One habit one can have then is to move the terrain to (-500,0, -500), to center its actual mesh center to the world center. Thus, as you set the target to x=-20, z=-8, your unit is going right in the direction where there was no terrain when it was not moved, and because the position field of the localTransform was not set to the transform of the terrain, the actual runtime spawned terrain is in (0,0,0) instead of (-500,0,-500). That is why the unit had one raycast that hit the corner of the terrain, and then it went off terrain.

I noticed that the quaternion.Euler also bugged the terrain, I don’t know why. So doing this should make it work

        entityManager.SetComponentData(terrainEntity, new LocalTransform
        {
            Position = transform.position,
            Rotation = quaternion.identity,
            Scale = 1f
        });
2 Likes

Omg, such a dumby mistake! I must have noticed it by myself.
Anyway, thank you very much!