Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

Using Unity Terrain with DOTS workflow

Discussion in 'Entity Component System' started by desertGhost_, Oct 3, 2019.

  1. desertGhost_

    desertGhost_

    Joined:
    Apr 12, 2018
    Posts:
    261
    For this to work in 0.50 you need to add a
    PhysicsWorldIndex
    shared component in the authoring component for the terrain. Thank you TRS6123 and Iclemens.
     
  2. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    765
    Yes, our hack works if we add PhysicsWorldIndex (and make sure the terrain isn't in a subscene anymore because AddHybridComponent() was removed).

    The question I am asking is - if there is a dedicated dots terrain engine in 0.50 since Joachim said they were working on it in October 2019 (2.5 years ago). If there is, I haven't found it or figured out how to use it yet.
     
    Last edited: Mar 21, 2022
    BigRookGames, Deleted User and bb8_1 like this.
  3. desertGhost_

    desertGhost_

    Joined:
    Apr 12, 2018
    Posts:
    261
    I am also curious to know the status of the DOT terrain engine. Maybe it's part of 1.0 (seems very useful for open world games)?
     
    Neiist, lclemens and (deleted member) like this.
  4. alexandre-fiset

    alexandre-fiset

    Joined:
    Mar 19, 2012
    Posts:
    716
    Create your world with a terrain editor, then split and convert it to standard meshes that you can hybrid-render. This way you don't have to wait years for Unity to do something about it.

    Also, Unity does have a card on their roadmap about scene streaming and sectioning, just no mention of terrains.

    I'm curious about what they are showing at GDC as they had multiple sessions on DOTS yesterday and are revealing a new URP sample as well as a mysterious flashship demo on Thursday.
     
    lclemens and bb8_1 like this.
  5. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,689
    bb8_1 and alexandre-fiset like this.
  6. ZbigniewBrzozowski

    ZbigniewBrzozowski

    Joined:
    Jan 26, 2019
    Posts:
    4
    hello! Can you please tell me how to convert terrain to standard mesh?
    Can i use this script? Do you think its safe and legit? Thanks in advance!
     
  7. alexandre-fiset

    alexandre-fiset

    Joined:
    Mar 19, 2012
    Posts:
    716
  8. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    765
    How would you rate the workflow for that? Say for example you want to flatten a small area in the terrain or paint it... how much work is it to do that and then rebuild the mesh whenever you need to make a change to the terrain?
     
  9. DreamersINC

    DreamersINC

    Joined:
    Mar 4, 2015
    Posts:
    133
    If its anything like github I used to do, PITA and would only do it prior to build for platforms
     
  10. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Would like to know a status on conversion for terrains. Currently I have GO terrain, along side the DOTS world with hacked collider from this thread. I understand DOTS team are busy. What is the scope of the DOTS terrain? Will it be an authoring port of existing gameobject terrain or something more, that can stream / better performance?

    Same question for the vegetation detail rendering with terrains, will this be accelerated with hybrid renderer? Just a few questions to anticipate, not asking when ofc ; )
     
  11. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    765
    Hey everyone - hope you are all doing well.

    I decided to try rendering the mesh on an entity instead of just making the collision mesh and using Convert-and-Inject to render the terrain as a game-object. From what I've been reading and personal experience, the UnityEngine.Terrain game-object runs horribly on mobile platforms.

    Also, I think 1.0 won't support convert-and-inject, which will make the terrain disappear since it's currently relying on that.

    Also, point lights which are broken in 0.51 and 1.0 experimental, will supposedly be fixed in 1.0 release, so I'll be able to have point lights working on entities again.

    Using the code in the unity physics example that @Antypodish linked to, I modified the MapUtils static class with a couple new functions.

    Code (CSharp):
    1. // utilities for the map and terrain
    2.  
    3. using Unity.Physics;
    4. using Unity.Mathematics;
    5. using Unity.Collections;
    6. using Unity.Entities;
    7. using System;
    8. using System.Collections;
    9. using System.Collections.Generic;
    10. using System.Reflection;
    11. using UnityEngine;
    12. using Collider = Unity.Physics.Collider;
    13. using Mesh = UnityEngine.Mesh;
    14.  
    15. public class MapUtils
    16. {
    17.     public static PhysicsCollider CreateTerrainCollider(TerrainData terrainData, CollisionFilter filter, Unity.Physics.TerrainCollider.CollisionMethod method)
    18.     {
    19.         PhysicsCollider physicsCollider = new PhysicsCollider();
    20.         Vector3 scale = terrainData.heightmapScale;
    21.  
    22.         NativeArray<float> colliderHeights = new NativeArray<float>(terrainData.heightmapResolution * terrainData.heightmapResolution, Allocator.TempJob);
    23.         var terrainHeights = terrainData.GetHeights(0, 0, terrainData.heightmapResolution, terrainData.heightmapResolution);
    24.  
    25.         for (int j = 0; j < terrainData.heightmapResolution; j++) {
    26.             for (int i = 0; i < terrainData.heightmapResolution; i++) {
    27.                 colliderHeights[j + i * terrainData.heightmapResolution] = terrainHeights[i, j];
    28.             }
    29.         }
    30.         physicsCollider.Value = Unity.Physics.TerrainCollider.Create(colliderHeights, new int2(terrainData.heightmapResolution, terrainData.heightmapResolution), scale, method, filter);
    31.         colliderHeights.Dispose();
    32.         return physicsCollider;
    33.     }
    34.  
    35.     // THIS IS FOR 0.51
    36.     static readonly Type k_DrawComponent = typeof(Unity.Physics.Authoring.DisplayBodyColliders).GetNestedType("DrawComponent", BindingFlags.NonPublic);
    37.     static readonly MethodInfo k_DrawComponent_BuildDebugDisplayMesh = k_DrawComponent.GetMethod("BuildDebugDisplayMesh", BindingFlags.Static | BindingFlags.NonPublic, null, new[] { typeof(BlobAssetReference<Collider>) }, null);
    38.     static readonly Type k_DisplayResult = k_DrawComponent.GetNestedType("DisplayResult");
    39.     static readonly FieldInfo k_DisplayResultsMesh = k_DisplayResult.GetField("Mesh");
    40.     static readonly PropertyInfo k_DisplayResultsTransform = k_DisplayResult.GetProperty("Transform");
    41.     public static Mesh CreateMeshFromCollider(BlobAssetReference<Collider> collider)
    42.     {
    43.         var mesh = new Mesh { hideFlags = HideFlags.DontSave };
    44.         var instances = new List<CombineInstance>(8);
    45.         var numVertices = 0;
    46.         foreach (var displayResult in (IEnumerable)k_DrawComponent_BuildDebugDisplayMesh.Invoke(null, new object[] { collider })) {
    47.             var instance = new CombineInstance {
    48.                 mesh = k_DisplayResultsMesh.GetValue(displayResult) as Mesh,
    49.                 transform = (float4x4)k_DisplayResultsTransform.GetValue(displayResult)
    50.             };
    51.             instances.Add(instance);
    52.             numVertices += mesh.vertexCount;
    53.         }
    54.         mesh.indexFormat = numVertices > UInt16.MaxValue ? UnityEngine.Rendering.IndexFormat.UInt32 : UnityEngine.Rendering.IndexFormat.UInt16;
    55.         mesh.CombineMeshes(instances.ToArray());
    56.         mesh.RecalculateBounds();
    57.         return mesh;
    58.     }
    59.  
    60.     // THIS IS FOR 1.0
    61.     //    static readonly Type k_DrawComponent = Type.GetType(Assembly.CreateQualifiedName("Unity.Physics.Hybrid", "Unity.Physics.Authoring.AppendMeshColliders")).GetNestedType("GetMeshes", BindingFlags.Public);
    62.     //    static readonly MethodInfo k_DrawComponent_BuildDebugDisplayMesh = k_DrawComponent.GetMethod("BuildDebugDisplayMesh", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(BlobAssetReference<Collider>), typeof(float) }, null);
    63.     //    static readonly Type k_DisplayResult = k_DrawComponent.GetNestedType("DisplayResult");
    64.     //    static readonly FieldInfo k_DisplayResultsMesh = k_DisplayResult.GetField("Mesh");
    65.     //    static readonly PropertyInfo k_DisplayResultsTransform = k_DisplayResult.GetProperty("Transform");
    66.     //    public static Mesh CreateMeshFromCollider(in PhysicsCollider collider)
    67.     //    {
    68.     //        var mesh = new Mesh { hideFlags = HideFlags.DontSave };
    69.     //        var instances = new List<CombineInstance>(8);
    70.     //        var numVertices = 0;
    71.     //        foreach (var displayResult in (IEnumerable)k_DrawComponent_BuildDebugDisplayMesh.Invoke(null, new object[] { collider, 1.0f })) {
    72.     //            var instance = new CombineInstance {
    73.     //                mesh = k_DisplayResultsMesh.GetValue(displayResult) as Mesh,
    74.     //                transform = (float4x4)k_DisplayResultsTransform.GetValue(displayResult)
    75.     //            };
    76.     //            instances.Add(instance);
    77.     //            numVertices += mesh.vertexCount;
    78.     //        }
    79.     //        mesh.indexFormat = numVertices > UInt16.MaxValue ? UnityEngine.Rendering.IndexFormat.UInt32 : UnityEngine.Rendering.IndexFormat.UInt16;
    80.     //        mesh.CombineMeshes(instances.ToArray());
    81.     //        mesh.RecalculateBounds();
    82.     //        return mesh;
    83.     //    }
    84. }
    And then in PhysicsTerrainAuthoring.cs, I make these calls:

    Code (CSharp):
    1.  
    2. PhysicsCollider collider = MapUtils.CreateTerrainCollider(terrain.terrainData, collisionFilter, m_collisionMethod);
    3. dstManager.AddComponentData(entity, collider);
    4. var mesh = MapUtils.CreateMeshFromCollider(collider.Value);
    5. RenderMeshUtility.AddComponents(entity, dstManager, new RenderMeshDescription(mesh, m_material, UnityEngine.Rendering.ShadowCastingMode.On, true));
    (I also added public UnityEngine.Material m_material; so the designer can set the material to use in the editor).

    So this at least displays something, which is good, however, it's not what I expected to see.

    It should look like this (the game-object):

    upload_2022-11-13_21-47-33.png

    However, it actually looks like this (the entity):

    upload_2022-11-13_21-47-59.png

    The DOTS Inspector shows this:

    upload_2022-11-13_21-57-21.png

    Somehow the texture UV is not getting wrapped the same way on the mesh as it does in the game-object. This is not really my area of expertise... So I'm hoping that maybe one of you has an idea of something I could try?
     
    Last edited: Nov 14, 2022
    bb8_1 likes this.
  12. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,126
    In Entities version 0.17, my client had a mobile game with a huge map divided into multiple terrain areas, which makes it impossible to run on low/mid devices.
    I had to add terrain support to my custom hybrid renderer which converts all terrains to meshes and renders them with a custom terrain shader.
    This shader supports batching terrains with similar meshes (useful for flat terrains) as they all use the same material with layers contained in a SpriteAtlas/TextureArray. ( minimizes setpass calls even for non batched terrains )
    The only limitation I had was that each peace of neighbor terrain was forced to have a maximum of 16 layers as they are indexed by float4x4.

    I hope this can helps you :)
     
    Last edited: Nov 14, 2022
  13. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,126
    It looks like the terrain details texture is using a very Low resolution.
     
  14. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    765
    I don't think I want to go the custom renderer route if I can avoid it - that's a bit over my head at the moment. I would be happy for now with just a single texture and no custom shader.

    The material and texture that I used is the exact same one for both screenshots - with the same resolution. It's as if, the entire mesh on the entity was mapped with just a single color from the texture.
     
  15. Arnold_2013

    Arnold_2013

    Joined:
    Nov 24, 2013
    Posts:
    287
    Not using 1.0, so this might not work. But would it not be possible to have a convert and destroy(or inject) Terrain to create the DOTS physics object and for visuals place a normal (duplicate) GameObject terrain?
    This would be the same result as the 'convert and inject' with only requiring support for GameObjects in ECS.

    I am currently using microsplat with my terrain and the shaders are not DOTS_Instancing compatible, so I am stuck on a "GameObject solution". So I hope this wont block me from ECS 1.0.
     
    lclemens likes this.
  16. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,126
    AFAIK, you can't just render a terrain material like a normal material, I think you have to pass some material properties from the terrain inspector to the material shader in order to take into account the parameters that make you see what you see in the GameObject version.
    if you are using the official Graphics package, it already supports material properties , you just need to create them.
    Also, if I remember correctly, the gameobject version is storing every 4 layers within the RGBA of a SplatMap and rendered into a separate shader pass.
    eg: if your terrain peace uses between 1 and 4 layers it will be drawn with a single drawcall but if you have between 5 and 8 layers it will use 2 draw calls, one for the first 4 layers and a second for the others ...
    this is how Unity gives you the possibility to draw as many layers as you want.
     
    Last edited: Nov 14, 2022
    lclemens likes this.
  17. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,126
    another option may meet your needs, but it has its limitations.
    you may be able to export the terrain meshes and textures to render them as objects. but it will make you use a lot of high resolution textures in order to get closer to the terrain version.
    Also, you won't have any runtime interaction with the terrain, like drawing snow... but you can use decals instead.
     
    lclemens likes this.
  18. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    765
    I'm still on 0.51, but from how I understand 1.0 works, I think your idea is sound. I just figured it would be one less workaround I'd have to mess with if I can get the terrain to render as an entity.
     
  19. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    765
    Hmmm... I wonder what the particular material properties are that I'll need. Currently I'm using the URP-Simple-Lit shader instead of the URP terrain shader on the game-object, so I thought it would be okay to not worried about the shader. In fact it makes angry exclamation points at me like this:
    upload_2022-11-14_12-18-11.png

    I'll dig around and see if I can figure out what material properties are needed, and how to get/set them. I had no idea it would be this difficult to render the terrain as a mesh on an entity. Hopefully the "We are working on a dedicated dots terrain engine" statement indicates that something is just around the corner, but I have not seen any mention of it, so I'm skeptical of the truthfulness of that statement.
     
    Opeth001 likes this.
  20. Arnold_2013

    Arnold_2013

    Joined:
    Nov 24, 2013
    Posts:
    287
    I override my terrain material with a normal shadergraph shader for the unexplored terrain. This also gives this warning, but I have not had any issues with it. (rendered as a gameobject, and not as an entity.)

    Alternatively I tried Microsplat Mesh terrain. This converts terrains to multiple patches of mesh, while keeping the same shading. But you lose the terrain LOD system, and in high res the mesh terrain was too heavy. Also I could still not convert it to ECS because the shader does not have a DOTS_INSTANCING_ON or something.
     
    lclemens likes this.
  21. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    765
    Were you able to get the shader working under Hybrid Renderer? I bought it today and at first I was getting some errors and then I tried to build the shader, and that crashed unity and caused it to run out of memory, and now it's saying giving me this error:

    SRP Batcher not compatible
    Material property is found in another cbuffer than "UnityPerMaterial" (_T2M_Layer_1_uvScaleOffset)

    I can fix the error by adding float4 _T2M_Layer_1_uvScaleOffset to the CBUFFER_START(UnityPerMaterial) block inside of Varaibles.cginc, however, when I do that, I then get an error saying "redefinition of '_T2M_Layer_1_uvScaleOffset'" .

    So it's a catch-22... If I define it in the cbuffer then it complains that it's being redefined (no idea where the old definition is), and if I don't define it, then it complains that the material property is found in another cbuffer.

    I can't disable SRP Batcher because from what I can tell, SRP Batcher is necessary for HRV2 and when I tried disabling it, all kinds of other problems arise.

    The shader has #pragma multi_compile _ DOTS_INSTANCING_ON ... so that indicates that maybe it was intended for DOTS?

    Was there anything special you had to do to get the shader working in an HRV2+SRP Batcher environment?
     
  22. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    765
    In the microsplat shader, you might be able to add

    Code (CSharp):
    1. #pragma multi_compile _ DOTS_INSTANCING_ON
    I've only ever written one DOTS compatible shader, and I remember that I had to have that in there.
     
  23. alexandre-fiset

    alexandre-fiset

    Joined:
    Mar 19, 2012
    Posts:
    716
    We keep the original terrains for work, and split them using editor tools we've made, and simplify the meshes for LODs using UnityMeshSimplifier. We did that for Kona and still are doing it for Kona II. All the terrains are made by a single artist, so I think it's quite productive this way.

    No, it was too late for us to adopt the Hybrid Renderer when V2 came out, and we still aren't using Build Configurations. We look forward to our next project to use DOTS 1.0 and HR as it will make streaming our world much smoother, hopefully.

    I did try to add the package this week by curiosity and it threw a lot of errors in our own terrain shaders (custom HDRP layered lit tesselation for handling snow trails). I would suggest you create the shader you want in Shader Graph or to simply use Unity Layered Lit if you are on HDRP or if that exists on URP.

    upload_2022-11-16_8-36-56.png
     
    bb8_1 likes this.
  24. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    765
    So here's a recap about what I've learned in the past two weeks when trying to convert my terrain mesh to an entity instead of keeping it as a game-object via convert-and-inject.

    Method 1: So the CreateMeshFromCollider() code from this post https://forum.unity.com/threads/using-unity-terrain-with-dots-workflow.755105/page-2#post-8583556 sorta-kinda worked. It creates the mesh just fine, but the texture UV is all screwed up. So I abandoned that idea.

    Method 2: Opeth001 sent me his terrain shader and it worked quite well. It's fast, works on mobile devices, and supports 16 layers!

    Method 3: I bought the Terrain-to-Mesh asset for $20 (half off) and the shader didn't work under HRV2, but there was an alternate mode where it would create a mesh and bake a static texture and I could just display the terrain using the URP Simple Lit or Lit shader. It worked pretty well. There was no layering, so the terrain looked pretty bland, but it was not too bad and way better than Method 1.

    Method 4: I sent the shader error information to the Terrain-to-Mesh author and a week later, he released an update that fixed it. In just 3 lines of code, I was able to get my terrain displaying in the entity world as a mesh with a fully-splat-mapped material that looked identical to the original terrain. The mesh and material is created from terrain and assigned during conversion, so any change I make to the terrain in the editor is reflected in the mesh at runtime with no extra conversion steps. I haven't tried it yet, but I saw functions in the API to automatically break up the terrain into multiple meshes so I think you can work with a single terrain and have it broken into multiple meshes during conversion/baking. Take that with a grain of salt though - I'm not 100% sure that's how it works.

    Method 5: Wait until the DOTS terrain vaporware materializes. Riiiiiiight.

    ------------------------------------------------------------------------------------------------
    Opeth001 said terrain with over 30k vertices should be broken up for mobile devices because there won't be any culling on it - the whole terrain will be rendered even if the player is only viewing a small piece of it. So at some point I'll investigate breaking up the terrain, but for now I'm just happy to have several methods of converting the terrain mesh, material, and collider to entity with no game-object injection.

    I haven't tried any of this under DOTS 1.0 (I only tested with 0.51). I'm a little worried that some of it might not work under 1.0 because I'm not sure if baking will allow the creation and assignment of a new mesh and material. I hope it will.

    If anyone is interested, here is what the terrain authoring looks like using Terrain-to-Mesh...

    Code (CSharp):
    1.  
    2. // This is a simple authoring class that converts a terrain gameobject into a terrain entity
    3. // Attach this script to the terrain object
    4. // Set belongsTo to 0:Terrain. collidesWith is typically set to everything.
    5. // The terrain object should have convert-to-entity on it or be in a subscene.
    6. // You will need MapUtils.cs (and optionally TerrainHeightData.cs if you want to sample terrain height at runtime),
    7.  
    8. using UnityEngine;
    9. using Unity.Entities;
    10. using Unity.Physics;
    11. using Unity.Physics.Authoring;
    12. using AmazingAssets.TerrainToMesh;
    13. using Unity.Rendering;
    14.  
    15. [ConverterVersion("TerrainAuthoring", 4)]
    16. public class TerrainAuthoring : MonoBehaviour, IConvertGameObjectToEntity
    17. {
    18.     [Tooltip("The physics categories that the terrain belongs to (usually a terrain category).")]
    19.     public PhysicsCategoryTags m_belongsTo;
    20.  
    21.     [Tooltip("The physics categories that the terrain collides with (usually everything).")]
    22.     public PhysicsCategoryTags m_collidesWith = PhysicsCategoryTags.Everything;
    23.  
    24.     [Tooltip("I'm not really sure what the group index is. (default 0).")]
    25.     public int m_groupIndex = 0;
    26.  
    27.     [Tooltip("The method to use when creating a physics collider. (default Triangles).")]
    28.     public Unity.Physics.TerrainCollider.CollisionMethod m_collisionMethod = Unity.Physics.TerrainCollider.CollisionMethod.Triangles;
    29.  
    30.     public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    31.     {
    32.         // fetch the terrain monobehaviour
    33.         Terrain terrain = GetComponent<Terrain>();
    34.         if (terrain == null) { return; }
    35.         // setup a collision filter using the parameters the user specified.
    36.         CollisionFilter collisionFilter = new CollisionFilter
    37.         {
    38.             BelongsTo = m_belongsTo.Value,
    39.             CollidesWith = m_collidesWith.Value,
    40.             GroupIndex = m_groupIndex
    41.         };
    42.  
    43.         // terrain entities should always have a terrain tag so it can easily be retrieved from jobs.
    44.         dstManager.AddComponent<TerrainTag>(entity);
    45.  
    46.         // create the physics terrain collider and add it to the entity
    47.         // see https://forum.unity.com/threads/using-unity-terrain-with-dots-workflow.755105
    48.         PhysicsCollider collider = MapUtils.CreateTerrainCollider(terrain.terrainData, collisionFilter, m_collisionMethod);
    49.         dstManager.AddComponentData(entity, collider);
    50.  
    51.         // This uses Amazing Assets to create a mesh from the terrain.
    52.         Mesh mesh = terrain.terrainData.TerrainToMesh().ExportMesh(256, 256, Normal.CalculateFromMesh, null);
    53.  
    54.         // This creates a material for the mesh which uses the Amazing Assets splat map shader
    55.         UnityEngine.Material material = terrain.terrainData.TerrainToMesh().ExportSplatmapMaterial(false);
    56.  
    57.         // Use the rendermesh utililty to add the mesh and mesh renderer to the entity.
    58.         RenderMeshUtility.AddComponents(entity, dstManager, new RenderMeshDescription(mesh, material, UnityEngine.Rendering.ShadowCastingMode.On, true));
    59.  
    60.         // This will create a component containing a blob array of terrain height data and add it
    61.         // to the terrain entity so that sampling terrain heights from jobs without raycasting is possible.
    62.         dstManager.AddComponentData(entity, new TerrainHeightData(terrain));
    63.  
    64.         // this must be added or else bad stuff happens
    65.         dstManager.AddComponent<PhysicsWorldIndex>(entity);
    66.  
    67.         #if UNITY_EDITOR
    68.             dstManager.SetName(entity, this.name);
    69.         #endif
    70.     }
    71. }
    72.  
     
  25. PolarTron

    PolarTron

    Joined:
    Jun 21, 2013
    Posts:
    95
    Your sacrifices have not gone unnoticed. Thank you for the summary.

    I decided to wait with the terrain stuff in our game until Unity releases their open world terrain workflow. We have a full DOTS 1.0 + Netcode game so I would like it to just all work together without me doing a lot of janky custom work to make things work. Other than what was shown in the Unite video I don't know much about what's planned but I would really like to get some kind of update soon.
     
    Skjalg and lclemens like this.
  26. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    765
    I don't know much about the status either. If you look on page 1 of this thread, Joachim said "We are working on a dedicated dots terrain engine that will fill the hole." in October 2019. Over the past three years, several of us have asked about the DOTS terrain timeline in this thread, but the only news so far is crickets.

    I'm pretty happy with solutions 2 and 4. They were easy enough to get running and the workflow is very convenient (I can edit the terrain game-object as much as I want and it's automatically reflected in the mesh entity at runtime). So overall I wouldn't describe it as "janky", but of course, I understand your point of view. I haven't tested with DOTS 1.0, but I'll be testing that in a few months. I hope it still works with baking instead of conversion.
     
  27. TheOtherMonarch

    TheOtherMonarch

    Joined:
    Jul 28, 2012
    Posts:
    887
    When the official DOTS terrain comes out keeping the graphics separated from the actual collider needs to remain possible. Without separating graphics and colliders multi-worlds end up with needless overhead.

    Which collider type are you using Mesh, Convex Hull or TerrainCollider (height map collider)? I know earlier in the thread people were having issues with the TerrainCollider. Right now the TerrainCollider shows up in the API but not in the manual. Height map collider will give you better performance.
     
    Last edited: Dec 3, 2022
    Neiist likes this.
  28. Avol

    Avol

    Joined:
    May 27, 2016
    Posts:
    98
    Sharing 1.0 code that works. If anything amiss feel free to share :)

    Code (CSharp):
    1.     public class DOTSTerrain : MonoBehaviour
    2.     {
    3.         [SerializeField] PhysicsCategoryTags belongsTo;
    4.         [SerializeField] PhysicsCategoryTags collidesWith;
    5.         [SerializeField] int groupIndex;
    6.  
    7.         /// <summary>
    8.         ///
    9.         /// </summary>
    10.         void Awake()
    11.         {
    12.             if (!TryGetComponent<Terrain>(out var terrain))
    13.             {
    14.                 Debug.LogError("No terrain found!");
    15.                 return;
    16.             }
    17.  
    18.             CollisionFilter collisionFilter = new CollisionFilter
    19.             {
    20.                 BelongsTo = belongsTo.Value,
    21.                 CollidesWith = collidesWith.Value,
    22.                 GroupIndex = groupIndex
    23.             };
    24.  
    25.             PhysicsCollider collider = _CreateTerrainCollider(terrain.terrainData, collisionFilter);
    26.  
    27.  
    28.        
    29.             Entity entity = World.DefaultGameObjectInjectionWorld.EntityManager.CreateEntity();
    30.  
    31.             World.DefaultGameObjectInjectionWorld.EntityManager.AddComponent<PhysicsCollider>(entity);
    32.             World.DefaultGameObjectInjectionWorld.EntityManager.SetComponentData(entity, collider);
    33.  
    34.             World.DefaultGameObjectInjectionWorld.EntityManager.AddComponent<LocalToWorld>(entity);
    35.             World.DefaultGameObjectInjectionWorld.EntityManager.AddComponent<Rotation>(entity);
    36.             World.DefaultGameObjectInjectionWorld.EntityManager.AddComponent<PhysicsWorldIndex>(entity);
    37.         }
    38.  
    39.         /// <summary>
    40.         ///
    41.         /// </summary>
    42.         /// <param name="terrainData"></param>
    43.         /// <param name="filter"></param>
    44.         /// <returns></returns>
    45.         private PhysicsCollider _CreateTerrainCollider(TerrainData terrainData, CollisionFilter filter)
    46.         {
    47.             int            resolution        = terrainData.heightmapResolution;
    48.             int2        size            = new int2(resolution, resolution);
    49.             Vector3        scale            = terrainData.heightmapScale;
    50.  
    51.             NativeArray<float>        colliderHeights        = new NativeArray<float>(resolution * resolution, Allocator.TempJob);
    52.             float[,]                terrainHeights        = terrainData.GetHeights(0, 0, resolution, resolution);
    53.  
    54.             for (int j = 0; j < size.y; j++)
    55.                 for (int i = 0; i < size.x; i++)
    56.                 {
    57.                     var h = terrainHeights[i, j];
    58.                     colliderHeights[j + i * size.x] = h;
    59.                 }
    60.  
    61.  
    62.             PhysicsCollider physicsCollider = new PhysicsCollider
    63.             {
    64.                 Value = Unity.Physics.TerrainCollider.Create(colliderHeights, size, scale, Unity.Physics.TerrainCollider.CollisionMethod.Triangles, filter)
    65.             };
    66.  
    67.             colliderHeights.Dispose();
    68.  
    69.             return physicsCollider;
    70.         }
    71.     }
     
  29. Sunstrace

    Sunstrace

    Joined:
    Dec 15, 2019
    Posts:
    40
  30. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    765
    I'm using the same physics collider that this thread was discussing on page 1, and it is separate from the graphics mesh. I have no idea what the official DOTS terrain will do for separation of collider and graphics since currently it's currently in mythological status. If you look in my code above, it makes a call to MapUtils.CreateTerrainCollider(). I put the code below (the guys on page 1 wrote it and I just made a few minor tweaks). There are two versions of it. Some people are using the V2 version of it because they were having problems with gravity pulling things through flat terrain, however, I'm just using the original version because I haven't ran into that issue. These versions do not handle tree colliders, but I saw that someone posted a version on page 1 that supposedly handles trees (haven't tried it personally).

    Code (CSharp):
    1. // utilities for the map and terrain
    2. // see https://forum.unity.com/threads/using-unity-terrain-with-dots-workflow.755105
    3.  
    4. using Unity.Physics;
    5. using Unity.Mathematics;
    6. using Unity.Collections;
    7. using UnityEngine;
    8.  
    9. public class MapUtils
    10. {
    11.     // creates a terrain collider from the given terrain-data gameobject.
    12.     // the collider will have the specified collision filter and collision method.
    13.     public static PhysicsCollider CreateTerrainCollider(TerrainData terrainData, CollisionFilter filter, Unity.Physics.TerrainCollider.CollisionMethod method)
    14.     {
    15.         PhysicsCollider physicsCollider = new PhysicsCollider();
    16.         Vector3 scale = terrainData.heightmapScale;
    17.  
    18.         NativeArray<float> colliderHeights = new NativeArray<float>(terrainData.heightmapResolution * terrainData.heightmapResolution, Allocator.TempJob);
    19.         var terrainHeights = terrainData.GetHeights(0, 0, terrainData.heightmapResolution, terrainData.heightmapResolution);
    20.  
    21.         for (int j = 0; j < terrainData.heightmapResolution; j++) {
    22.             for (int i = 0; i < terrainData.heightmapResolution; i++) {
    23.                 colliderHeights[j + i * terrainData.heightmapResolution] = terrainHeights[i, j];
    24.             }
    25.         }
    26.         physicsCollider.Value = Unity.Physics.TerrainCollider.Create(colliderHeights, new int2(terrainData.heightmapResolution, terrainData.heightmapResolution), scale, method, filter);
    27.         colliderHeights.Dispose();
    28.         return physicsCollider;
    29.     }
    30.  
    31.     // supposedly the other version has some sort of issue with physics things falling through it, but i've never noticed the issue.
    32.     public static PhysicsCollider CreateTerrainColliderV2(TerrainData terrainData, CollisionFilter filter, Unity.Physics.TerrainCollider.CollisionMethod method)
    33.     {
    34.         PhysicsCollider physicsCollider = new PhysicsCollider();
    35.         Vector3 scale = terrainData.heightmapScale;
    36.  
    37.         NativeArray<float> colliderHeights = new NativeArray<float>(terrainData.heightmapResolution * terrainData.heightmapResolution, Allocator.TempJob);
    38.         float[,] terrainHeights = terrainData.GetHeights(0, 0, terrainData.heightmapResolution, terrainData.heightmapResolution);
    39.  
    40.         // NOTE: Solves an issue with perfectly flat terrain failing to collide with objects.
    41.         float heightmapScale = terrainData.size.z;
    42.         float smallestOffset = 0.01f; // 1 cm offset, works with 2048 resolution terrain
    43.         float heightmapValuePerMeterInWorldSpace = 0.5f / heightmapScale;
    44.         float inHeightMapUnits = smallestOffset * heightmapValuePerMeterInWorldSpace;
    45.  
    46.         for (int j = 0; j < terrainData.heightmapResolution; j++) {
    47.             for (int i = 0; i < terrainData.heightmapResolution; i++) {
    48.                 int checkerboard = (i + j) % 2;
    49.                 colliderHeights[j + (i * terrainData.heightmapResolution)] = terrainHeights[i, j] + inHeightMapUnits * checkerboard; // Note : assumes terrain neighboars are never 1 cm difference from eachother
    50.             }
    51.         }
    52.  
    53.         // Note: Heightmap is between 0 and 0.5f (https://forum.unity.com/threads/terraindata-heightmaptexture-float-value-range.672421/)
    54.  
    55.         physicsCollider.Value = Unity.Physics.TerrainCollider.Create(colliderHeights, new int2(terrainData.heightmapResolution, terrainData.heightmapResolution), scale, method, filter);
    56.  
    57.         colliderHeights.Dispose();
    58.         return physicsCollider;
    59.     }
    60. }
     
  31. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    765
    Neiist likes this.
  32. Sunstrace

    Sunstrace

    Joined:
    Dec 15, 2019
    Posts:
    40
    The upgrade for Dot 1.0 is already submitted and pending review. Other new features will also be added to the subsequent upgrades. Regarding the price, it seems to require a Unity invitation to discount.
     
    lclemens likes this.
  33. Dechichi01

    Dechichi01

    Joined:
    Jun 5, 2016
    Posts:
    39
  34. Sunstrace

    Sunstrace

    Joined:
    Dec 15, 2019
    Posts:
    40
    This resource package was created entirely in the HDRP environment. Once I have created a URP project and simply imported the resource package to view the results, just simply set the shadergraph's Active Target to urp to work. But I didn't further examine the terrain data for the differences between URP and HDRP that can lead to unexpected results.
    Official support for URP is already planned, but not in several recent version updates.
     
  35. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    765
    That is one way to do it. By doing it in Awake(), it means that the terrain conversion will happen at runtime every time a terrain is loaded in a scene/subscene. That may be fine in many cases, but it is less efficient than the previous solution of doing it in IConvertGameObjectToEntity which happened at bake-time instead of run-time. Moving it to a Baker is trivial.

    I also wanted to generate the mesh and material in the Baker - and that turned out to be a huge mess since RenderMeshUtility requires EntityManager, which can't be used inside a Baker. I was able to find a hacky way around it by using a dummy MeshRenderer and a dummy MeshFilter. The mesh, material, and colliders are all created at bake-time. Unfortunately, it is less efficient in the editor because it is drawing both the terrain game-object and the baked entity mesh at the same time. It's also inconvenient because I have to remember to enable and disable the Mesh Renderer each time I modify the terrain. It's rather disappointing considering that I now have a worse solution in 1.0 after having spent several days trying to replicate what I had in 0.51. However, it's usable.

    Here is my code so far:
    EDIT: This "dummy mesh" method is problematic. I will post a better version below.

    Code (CSharp):
    1. using UnityEngine;
    2. using Unity.Entities;
    3. using Unity.Physics;
    4. using Unity.Physics.Authoring;
    5. using AmazingAssets.TerrainToMesh;
    6. using Unity.Transforms;
    7.  
    8. public class TerrainMono : MonoBehaviour
    9. {
    10.     [Tooltip("The physics categories that the terrain belongs to (usually a terrain category).")]
    11.     public PhysicsCategoryTags BelongsTo;
    12.  
    13.     [Tooltip("The physics categories that the terrain collides with (usually everything).")]
    14.     public PhysicsCategoryTags CollidesWith = PhysicsCategoryTags.Everything;
    15.  
    16.     [Tooltip("I'm not really sure what the group index is. (default 0).")]
    17.     public int GroupIndex = 0;
    18.  
    19.     [Tooltip("The method to use when creating a physics collider. (default Triangles).")]
    20.     public Unity.Physics.TerrainCollider.CollisionMethod CollisionMethod = Unity.Physics.TerrainCollider.CollisionMethod.Triangles;
    21. }
    22.  
    23. public class TerrainBaker : Baker<TerrainMono>
    24. {
    25.     public override void Bake(TerrainMono authoring)
    26.     {
    27.         // fetch the terrain monobehaviour
    28.         Terrain terrain = authoring.GetComponent<Terrain>();
    29.         if (terrain == null) { UnityEngine.Debug.Log($"terrain game object not found"); return; }
    30.  
    31.         // This keeps the Terrain game-object synchronized with the baked mesh.
    32.         DependsOn(terrain);
    33.  
    34.         AddComponent<TerrainTag>();
    35.  
    36.         // setup a collision filter using the parameters the user specified.
    37.         CollisionFilter collisionFilter = new CollisionFilter {
    38.             BelongsTo = authoring.BelongsTo.Value,
    39.             CollidesWith = authoring.CollidesWith.Value,
    40.             GroupIndex = authoring.GroupIndex
    41.         };
    42.  
    43.         // create the physics terrain collider and add it to the baked entity
    44.         // see https://forum.unity.com/threads/using-unity-terrain-with-dots-workflow.755105
    45.         PhysicsCollider collider = MapUtils.CreateTerrainCollider(terrain.terrainData, collisionFilter, authoring.CollisionMethod);
    46.         if (!collider.IsValid) { UnityEngine.Debug.Log($"collider is invalid"); return; }
    47.         AddComponent(collider);
    48.  
    49.         // This uses Amazing Assets to create a mesh from the terrain.
    50.         Mesh mesh = terrain.terrainData.TerrainToMesh().ExportMesh(256, 256, Normal.CalculateFromMesh, null);
    51.         if (mesh == null) { UnityEngine.Debug.Log($"mesh is null"); return; }
    52.         GetComponent<MeshFilter>().sharedMesh = mesh;
    53.  
    54.         // This uses Amazing Assets to create a material for the mesh which uses the Amazing Assets splat map shader
    55.         UnityEngine.Material material = terrain.terrainData.TerrainToMesh().ExportSplatmapMaterial(false);
    56.         if (material == null) { UnityEngine.Debug.Log($"material is null"); return; }
    57.         GetComponent<MeshRenderer>().sharedMaterial = material;
    58.  
    59.         // This will create a component containing a blob array of terrain height data and add it
    60.         // to the terrain entity so that sampling terrain heights from jobs is super fast with no raycasting.
    61.         AddComponent(new TerrainHeightData(terrain));
    62.  
    63.         //AddComponent<PhysicsWorldIndex>();  // this fails to compile
    64.     }
    65. }
    I haven't tested the collider yet because I'm still converting the rest of my game, but there may be a problem because I had to remove AddComponent<PhysicsWorldIndex>(); . The compiler complains that "The type 'Unity.Physics.PhysicsWorldIndex' cannot be used as type parameter 'T' in the generic type or method 'IBaker.AddComponent<T>()'. There is no boxing conversion from 'Unity.Physics.PhysicsWorldIndex' to 'Unity.Entities.IComponentData'." So I suspect I'll need an initialization system to add PhysicsWorldIndex at runtime.... but who knows, maybe PhysicsWorldIndex is no longer needed in 1.0? I did notice that the resulting entity has a PhysicsCollider and a LocalToWorld on it, so that's a good sign.
     
    Last edited: Jan 10, 2023
  36. Luxxuor

    Luxxuor

    Joined:
    Jul 18, 2019
    Posts:
    89
    They changed it to a SharedComponent.
    Regarding the mesh: Did you try out to add a "RenderMesh" component? Supposedly it adds all the necessary components in a baking system but I haven't tried it out.
     
    lclemens likes this.
  37. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    765
    Thanks for the SharedComponent tip - that works now. I was adding a "dummy" MeshRenderer MB Component, which in turn would add a RenderMeshArray, which I think is similar to the RenderMesh component you're referring to.

    So good news.... I now have a much better way of converting terrain to mesh in a Baker. I found 3 ways actually... I'll link to my post about it here in case anyone wants to know the 3 options: https://forum.unity.com/threads/ren...ept-entitycommandbuffer.1370682/#post-8711046

    I am currently using the option with RenderMeshUtility and _State.World.EntityManager and I'm pretty happy with it. It's much cleaner than that dummy renderer method I was using before.

    So here is the final Baker code:

    Code (CSharp):
    1.  
    2. // The TerrainMono class can be placed on game-objects in a subscene to
    3. // bake the terrain into a rendered mesh entity with a physics collider and a terrain height sampler.
    4. // It uses Amazing Assets Terrain-to-Mesh to perform the conversion.
    5.  
    6. using UnityEngine;
    7. using Unity.Entities;
    8. using Unity.Physics;
    9. using Unity.Physics.Authoring;
    10. using AmazingAssets.TerrainToMesh;
    11. using Unity.Rendering;
    12.  
    13. public class TerrainMono : MonoBehaviour
    14. {
    15.     [Tooltip("The physics categories that the terrain belongs to (usually a terrain category).")]
    16.     public PhysicsCategoryTags BelongsTo;
    17.  
    18.     [Tooltip("The physics categories that the terrain collides with (usually everything).")]
    19.     public PhysicsCategoryTags CollidesWith = PhysicsCategoryTags.Everything;
    20.  
    21.     [Tooltip("I'm not really sure what the group index is. (default 0).")]
    22.     public int GroupIndex = 0;
    23.  
    24.     [Tooltip("The method to use when creating a physics collider. (default Triangles).")]
    25.     public Unity.Physics.TerrainCollider.CollisionMethod CollisionMethod = Unity.Physics.TerrainCollider.CollisionMethod.Triangles;
    26. }
    27.  
    28. public class TerrainBaker : Baker<TerrainMono>
    29. {
    30.     public override void Bake(TerrainMono authoring)
    31.     {
    32.         // fetch the terrain monobehaviour
    33.         Terrain terrain = authoring.GetComponent<Terrain>();
    34.         if (terrain == null) { UnityEngine.Debug.Log($"terrain game object not found"); return; }
    35.  
    36.         // This keeps the Terrain game-object synchronized with the baked mesh.
    37.         // But it doesn't really work like it's supposed to. Often I have to Reimport the scene anyway.
    38.         DependsOn(terrain);
    39.  
    40.         AddComponent<TerrainTag>();
    41.  
    42.         // setup a collision filter using the parameters the user specified.
    43.         CollisionFilter collisionFilter = new CollisionFilter {
    44.             BelongsTo = authoring.BelongsTo.Value,
    45.             CollidesWith = authoring.CollidesWith.Value,
    46.             GroupIndex = authoring.GroupIndex
    47.         };
    48.  
    49.         // create the physics terrain collider and add it to the baked entity
    50.         // see https://forum.unity.com/threads/using-unity-terrain-with-dots-workflow.755105
    51.         PhysicsCollider collider = MapUtils.CreateTerrainCollider(terrain.terrainData, collisionFilter, authoring.CollisionMethod);
    52.         if (!collider.IsValid) { UnityEngine.Debug.Log($"collider is invalid"); return; }
    53.         AddComponent(collider);
    54.  
    55.         // This uses Amazing Assets to create a mesh from the terrain.
    56.         Mesh mesh = terrain.terrainData.TerrainToMesh().ExportMesh(256, 256, Normal.CalculateFromMesh, null);
    57.         if (mesh == null) { UnityEngine.Debug.Log($"mesh is null"); return; }
    58.  
    59.         // This uses Amazing Assets to create a material for the mesh which uses the Amazing Assets splat map shader
    60.         UnityEngine.Material material = terrain.terrainData.TerrainToMesh().ExportSplatmapMaterial(false);
    61.         if (material == null) { UnityEngine.Debug.Log($"material is null"); return; }
    62.  
    63.         // Setup the render mesh array
    64.         UnityEngine.Material[] mats = new UnityEngine.Material[1];
    65.         mats[0] = material;
    66.         Mesh[] meshes = new Mesh[1];
    67.         meshes[0] = mesh;
    68.         var renderMeshArray = new RenderMeshArray(mats, meshes);
    69.         // Note that this does not work! : RenderMeshArray renderMeshArray = new RenderMeshArray(new[] { material }, new[] { mesh });
    70.         // This also works: RenderMeshArray renderMeshArray = new RenderMeshArray(new List<UnityEngine.Material> { material }.ToArray(), new List<Mesh> { mesh }.ToArray());
    71.        
    72.         // Make render mesh description with shadows enabled
    73.         RenderMeshDescription renderMeshDesc = new RenderMeshDescription(UnityEngine.Rendering.ShadowCastingMode.On, true);
    74.  
    75.         // It is important to set the material mesh info
    76.         MaterialMeshInfo matMeshInfo = MaterialMeshInfo.FromRenderMeshArrayIndices(0, 0);
    77.  
    78.         // Use RenderMeshUtility to setup the new mesh and material for rendering
    79.         RenderMeshUtility.AddComponents(GetEntity(), _State.World.EntityManager, renderMeshDesc, renderMeshArray, matMeshInfo);
    80.  
    81.         // This will create a component containing a blob array of terrain height data and add it
    82.         // to the terrain entity so that sampling terrain heights from jobs without raycasting is possible.
    83.         AddComponent(new TerrainHeightData(terrain));
    84.  
    85.         // This is needed for the collider to be registered properly in the physics world
    86.         AddSharedComponent(new PhysicsWorldIndex());
    87.     }
    88. }
    89.  
     
    yhd4711499, Ausfaller and Luxxuor like this.
  38. Luxxuor

    Luxxuor

    Joined:
    Jul 18, 2019
    Posts:
    89
    Please note though that your approach will not correctly track changes to the terrain and you will get issues with the baking not being correct when you access the EntityManager like you do.
     
  39. Sunstrace

    Sunstrace

    Joined:
    Dec 15, 2019
    Posts:
    40
    Basically the RenderMeshUtility is just a wrapper that adds components to the entity. So in Baker just make sure to attach the right components. Only a few components are internal and are not necessary.
     
  40. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    765
    I am using DependsOn(terrain). Doesn't that solve the problems with baking synchronization?
     
  41. Luxxuor

    Luxxuor

    Joined:
    Jul 18, 2019
    Posts:
    89
    It is missing the calls to AddTrackingForComponent, just take a look at the implementation of AddComponent<T> to see what I mean.
    Also in the case of the terrain I think what you actually want to be tracked is not only the Terrain component but also the TerrainData, which contains the actual height map data. Additionally you should not call GetComponent on the Authoring but instead this.GetComponent<T>(authoring) to make sure the component gets tracked, no need to then call DependsOn explicitly for the component.
     
  42. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    765
    What is the point of the authoring parameter in the Bake() override if we can just use "this"?
     
  43. Luxxuor

    Luxxuor

    Joined:
    Jul 18, 2019
    Posts:
    89
    this in that case refers to the baker, not the authoring component.
     
  44. lilacsky824

    lilacsky824

    Joined:
    May 19, 2018
    Posts:
    172
    Trying to add some tree colliders by using lclemens and Lukas Kastern method together. :oops:
    Code (CSharp):
    1.  
    2. /// Thanks lclemens, Lukas Kastern
    3. /// https://github.com/DOTS-Discord/Unity-DOTS-Discord/wiki/Authoring-to-create-DOTS-Physics-colliders-from-standard-unity-terrains
    4. /// https://forum.unity.com/threads/using-unity-terrain-with-dots-workflow.755105/page-2#post-8640969
    5. /// https://forum.unity.com/threads/to-change-scale-and-collider-radius-in-ecs-physic.722462/
    6. ///
    7. using Unity.Collections;
    8. using Unity.Collections.LowLevel.Unsafe;
    9. using Unity.Entities;
    10. using Unity.Mathematics;
    11. using Unity.Physics;
    12. using Unity.Transforms;
    13. using Collider = Unity.Physics.Collider;
    14. using TerrainData = UnityEngine.TerrainData;
    15. using TreeInstance = UnityEngine.TreeInstance;
    16.  
    17. [RequireMatchingQueriesForUpdate]
    18. public partial class TerrainColliderSystem : SystemBase
    19. {
    20.     protected override unsafe void OnUpdate()
    21.     {
    22.         EntityCommandBuffer ecb = new EntityCommandBuffer(Allocator.Temp);
    23.  
    24.         Entities.ForEach(
    25.                 (Entity terrainEntity, in LocalToWorld terrainTransform, in TerrainDataComponent data) =>
    26.                 {
    27.                     PhysicsCollider terrainCollider = CreateTerrainCollider(data.Data, data.Filter);
    28.                     ecb.AddComponent(terrainEntity, terrainCollider);
    29.                     ecb.AddSharedComponent(terrainEntity, new PhysicsWorldIndex(0));
    30.  
    31.                     var treeColliders = CreateTreeColliderInstances(in data);
    32.  
    33.                     int blobSize = data.TreeCompoundColliderSplitSize;
    34.                     int blobsToCreate = (int)math.ceil(treeColliders.Length / (float)blobSize);
    35.  
    36.                     for (int i = 0; i < blobsToCreate; i++)
    37.                     {
    38.                         Entity treeEntity = ecb.CreateEntity();
    39.  
    40.                         int amountOfTreeColliders = math.min(blobSize, treeColliders.Length - i * blobSize);
    41.  
    42.                         var blobInstances =
    43.                             new NativeArray<CompoundCollider.ColliderBlobInstance>(amountOfTreeColliders, Allocator.Temp);
    44.  
    45.                         byte* source = (byte*)treeColliders.GetUnsafePtr() +
    46.                                      i * blobSize * UnsafeUtility.SizeOf<CompoundCollider.ColliderBlobInstance>();
    47.  
    48.                         UnsafeUtility.MemCpy(blobInstances.GetUnsafePtr(), source,
    49.                             blobInstances.Length * UnsafeUtility.SizeOf<CompoundCollider.ColliderBlobInstance>());
    50.  
    51. #if UNITY_EDITOR
    52.                         ecb.AddComponent(treeEntity, new TreeColliderDebugComponent(treeColliders.Length, blobSize));
    53.                         ecb.SetName(treeEntity, "TreeCollider");
    54. #endif
    55.                         ecb.AddSharedComponent(treeEntity, new PhysicsWorldIndex(0));
    56.                         ecb.AddComponent(treeEntity, terrainTransform);
    57.                         ecb.AddComponent(treeEntity, new PhysicsCollider()
    58.                         {
    59.                             Value = CompoundCollider.Create(blobInstances)
    60.                         });
    61.                     }
    62.  
    63.                     foreach (Entity e in data.TreePrototypes)
    64.                     {
    65.                         ecb.DestroyEntity(e);
    66.                     }
    67.                     ecb.RemoveComponent<TerrainDataComponent>(terrainEntity);
    68.                     treeColliders.Dispose();
    69.                 }
    70.             ).WithoutBurst().Run();
    71.  
    72.         ecb.Playback(this.EntityManager);
    73.         ecb.Dispose();
    74.     }
    75.  
    76.     private PhysicsCollider CreateTerrainCollider(TerrainData terrainData, CollisionFilter filter)
    77.     {
    78.         int resolution = terrainData.heightmapResolution;
    79.         int2 size = new int2(resolution, resolution);
    80.         float3 scale = terrainData.heightmapScale;
    81.  
    82.         NativeArray<float> colliderHeights = new NativeArray<float>(resolution * resolution, Allocator.Temp);
    83.         float[,] terrainHeights = terrainData.GetHeights(0, 0, resolution, resolution);
    84.  
    85.         for (int j = 0; j < size.y; j++)
    86.             for (int i = 0; i < size.x; i++)
    87.             {
    88.                 var h = terrainHeights[i, j];
    89.                 colliderHeights[j + i * size.x] = h;
    90.             }
    91.  
    92.         PhysicsCollider physicsCollider = new PhysicsCollider
    93.         {
    94.             Value = TerrainCollider.Create(colliderHeights, size, scale, TerrainCollider.CollisionMethod.Triangles, filter)
    95.         };
    96.  
    97.         colliderHeights.Dispose();
    98.  
    99.         return physicsCollider;
    100.     }
    101.  
    102.     private NativeList<CompoundCollider.ColliderBlobInstance> CreateTreeColliderInstances(in TerrainDataComponent data)
    103.     {
    104.         NativeList<CompoundCollider.ColliderBlobInstance> instances =
    105.             new NativeList<CompoundCollider.ColliderBlobInstance>(data.Data.treeInstances.Length,
    106.                 Allocator.Temp);
    107.  
    108.         foreach (TreeInstance tree in data.Data.treeInstances)
    109.         {
    110.             Entity targetTreePrototype = data.TreePrototypes[tree.prototypeIndex];
    111.  
    112.             if (!EntityManager.HasComponent<PhysicsCollider>(targetTreePrototype))
    113.                 continue;
    114.  
    115.             PhysicsCollider collider =
    116.                 EntityManager.GetComponentData<PhysicsCollider>(targetTreePrototype);
    117.  
    118.             if (!collider.IsValid)
    119.                 continue;
    120.  
    121.             float3 treeScale = new float3(tree.widthScale, tree.heightScale, tree.widthScale);
    122.             float3 treePosition = (float3)tree.position * data.Data.size;
    123.  
    124.             instances.Add(new CompoundCollider.ColliderBlobInstance()
    125.             {
    126.                 Collider = ScaleTreeInstanceCollider(collider.Value, treeScale),
    127.                 CompoundFromChild = new RigidTransform(quaternion.Euler(0, tree.rotation, 0), treePosition)
    128.             });
    129.         }
    130.  
    131.         return instances;
    132.     }
    133.  
    134.     /// Only support uniform scaling.
    135.     /// Not support CompoundCollider.
    136.     unsafe private BlobAssetReference<Collider> ScaleTreeInstanceCollider(BlobAssetReference<Collider> templateCol, float3 scale)
    137.     {
    138.         BlobAssetReference<Collider> col = templateCol.Value.Clone();
    139.  
    140.         if (col.Value.Type == ColliderType.Box)
    141.         {
    142.             var boxCollider = (BoxCollider*)col.GetUnsafePtr();
    143.  
    144.             var geometry = boxCollider->Geometry;
    145.             geometry.Center *= scale;
    146.             geometry.Size *= scale;
    147.  
    148.             boxCollider->Geometry = geometry;
    149.         }
    150.         else if (col.Value.Type == ColliderType.Capsule)
    151.         {
    152.             var capsuleCollider = (CapsuleCollider*)col.GetUnsafePtr();
    153.  
    154.             var geometry = capsuleCollider->Geometry;
    155.             geometry.Radius *= scale.x;
    156.             geometry.Vertex0 *= scale;
    157.             geometry.Vertex1 *= scale;
    158.  
    159.             capsuleCollider->Geometry = geometry;
    160.         }
    161.         else if (col.Value.Type == ColliderType.Sphere)
    162.         {
    163.             var sphereCollider = (SphereCollider*)col.GetUnsafePtr();
    164.  
    165.             var geometry = sphereCollider->Geometry;
    166.             geometry.Center *= scale;
    167.             geometry.Radius *= scale.x;
    168.  
    169.             sphereCollider->Geometry = geometry;
    170.         }
    171.  
    172.         return col;
    173.     }
    174.  
    175. #if UNITY_EDITOR
    176.     public struct TreeColliderDebugComponent : IComponentData
    177.     {
    178.         public int Count;
    179.         public int Capacity;
    180.  
    181.         public TreeColliderDebugComponent(int count, int capacity)
    182.         {
    183.             Count = count;
    184.             Capacity = capacity;
    185.         }
    186.     }
    187. #endif
    188. }
    189.  
    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using Unity.Entities;
    5. using Unity.Physics;
    6. using Unity.Physics.Authoring;
    7. using UnityEngine;
    8.  
    9. public class DOTSTerrainColliderAuthoring : MonoBehaviour
    10. {
    11.     public int TreeCompoundColliderSplitSize => _treeCompoundColliderSplitSize;
    12.  
    13.     [SerializeField]
    14.     [Range(10, 2000), Tooltip("Trees are split into Compound Colliders containing this amount of instances")]
    15.     private int _treeCompoundColliderSplitSize = 1024;
    16.     [SerializeField]
    17.     private PhysicsCategoryTags _belongsTo;
    18.     [SerializeField]
    19.     private PhysicsCategoryTags _collidesWith;
    20.     [SerializeField]
    21.     private int _groupIndex;
    22.  
    23.     public CollisionFilter GetCollisionFilter()
    24.     {
    25.         return new CollisionFilter
    26.         {
    27.             BelongsTo = _belongsTo.Value,
    28.             CollidesWith = _collidesWith.Value,
    29.             GroupIndex = _groupIndex
    30.         };
    31.     }
    32.  
    33.     public class TerrianBaker : Baker<DOTSTerrainColliderAuthoring>
    34.     {
    35.         public override void Bake(DOTSTerrainColliderAuthoring authoring)
    36.         {
    37.             if (!authoring.isActiveAndEnabled)
    38.                 return;
    39.          
    40.             Terrain terrain = GetComponent<Terrain>();
    41.  
    42.             GameObject[] treePrototypes = terrain.terrainData.treePrototypes.Select(i => i.prefab).ToArray();
    43.             List<Entity> entities = new List<Entity>();
    44.  
    45.             foreach (GameObject prototype in treePrototypes)
    46.             {
    47.                 Entity prefab = GetEntity(prototype);
    48.                 if (prefab == Entity.Null)
    49.                     Debug.LogException(new Exception("Failed to resolve Tree Prototype"));
    50.  
    51.                 entities.Add(prefab);
    52.             }
    53.  
    54.             TerrainDataComponent terrainData = new TerrainDataComponent(terrain.terrainData, authoring.GetCollisionFilter(), entities.ToArray(), authoring.TreeCompoundColliderSplitSize);
    55.             AddComponentObject(terrainData);
    56.  
    57.             DependsOn(terrain);
    58.             DependsOn(terrain.terrainData);
    59.         }
    60.     }
    61. }
    Code (CSharp):
    1. using Unity.Entities;
    2. using Unity.Physics;
    3. using UnityEngine;
    4.  
    5. public class TerrainDataComponent : IComponentData
    6. {
    7.     public TerrainData Data;
    8.     public CollisionFilter Filter;
    9.     public Entity[] TreePrototypes;
    10.     public int TreeCompoundColliderSplitSize;
    11.  
    12.     public TerrainDataComponent(){ }
    13.  
    14.     public TerrainDataComponent(TerrainData data, CollisionFilter filter, Entity[] treePrototypes, int treeCompoundColliderSplitSize)
    15.     {
    16.         TreeCompoundColliderSplitSize = treeCompoundColliderSplitSize;
    17.         Data = data;
    18.         TreePrototypes = treePrototypes;
    19.         Filter = filter;
    20.     }
    21. }
    22.  
     
    lclemens likes this.
  45. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    765
    In case anyone is wondering why PhysicsCategoryTags stopped working in 1.0.8... you have to go to the package manager, select the physics package, and then install the samples (it's not really samples... it's just some utils for physics authoring).
     
  46. TerraUnity

    TerraUnity

    Joined:
    Aug 3, 2012
    Posts:
    1,274
    Terrain was never receiving love from Unity, for example in the same product board page, Shader Graph Integration is under "In Progress" and it was from March 2021 :D Not to mention other items under "Planned" or "Under Consideration"...
     
    Neiist likes this.
  47. Zardify

    Zardify

    Joined:
    Jul 15, 2015
    Posts:
    20
    Any ... news...? I just read all of this. Thanks for all the contributions though. I'll try in 1.2.0-pre.6...
     
  48. DreamersINC

    DreamersINC

    Joined:
    Mar 4, 2015
    Posts:
    133
    No changes
     
  49. Zardify

    Zardify

    Joined:
    Jul 15, 2015
    Posts:
    20
    The solutions here do seem to work for 1.2.0-pre.6 ... not ideal *at all* but at least something. What I did was to have a duplicated terrain, one outside the subscene, the other inside. The one inside gets the authoring component I cut together from @Iclemens (special thanks) without TerrainToMesh, so it'll only build a PhysicsCollider and nothing else.

    Now onto the next Unity errors and workarounds.... ...

    EDIT: Here's the full Authoring script I currently use with instructions (1.2.0-pre.6)
     
    Last edited: Mar 12, 2024
    lclemens and tmonestudio like this.
  50. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    765
    I know for sure that Unity is working on a new terrain editor package because their employees have said so. However, we don't have confirmation if it'll support DOTS or not... because Unity employees have been silent for 4 years on the topic.