Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Setting the world position on a child entity using the Transforms System

Discussion in 'Entity Component System' started by maxxa05, Feb 6, 2021.

  1. maxxa05

    maxxa05

    Joined:
    Nov 17, 2012
    Posts:
    186
    I want to set the world position (think transform.position) on an entity with the DOTS Transform system that has parents, but it seems there's no easy way to do so. I can't directly set LocalToWorld since it's overwritten by the builtin systems, so I probably shouldn't do that. I can set Translation, but it's in local space. I'd like to avoid simply removing the child from its parent. I guess I have to find a way to do this by getting the parent entity's LocalToWorld component, and use that method every time I want to do this. Is there any other way I'm missing?
     
  2. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,292
    Use inverse transform to bring world position into local coordinate space, e.g. like this.
    Then apply local position to Translation. Should be pretty simple.
     
  3. maxxa05

    maxxa05

    Joined:
    Nov 17, 2012
    Posts:
    186
    This is what I thought I needed to do, but it seemed a bit complicated to be the only solution for this somewhat frequent use case. I guess I'll make a generic system to do this quickly everytime I need this myself.
     
  4. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,292
    As an alternative - extension method should also work just fine.
     
  5. maxxa05

    maxxa05

    Joined:
    Nov 17, 2012
    Posts:
    186
    Thing is, if I want this to do what I want it to do, I'd have to get a ComponentDataFromEntity for LocalToWorld, get a ComponentDataFromEntity for Parent and feed that to my extension method. It will still be a bunch of boilerplate code. But yeah, that would be possible too I guess.
     
  6. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,292
    Why would you need Parent for it? LocalToWorld matrix is plenty as you need to modify local position to be as end result world position.

    Use something like:
    Code (CSharp):
    1. float4x4 trsData = GetComponent<LocalToWorld>(entity).Value;
    2. float3 localWorldPos = trsData.InverseTransform(...);
    3. // Apply results to Translation afterwards
    Where .InverseTransform would be inverse transform logic link to which I've posted above.
    Just in case, GetComponent<T> replaced under the hood by CDFE fetch for you by codegen when used in .Schedule(Parallel) / with jobs. (otherwise its EntityManager direct call if performed via .Run)

    So there's minimal boilerplate.
     
  7. maxxa05

    maxxa05

    Joined:
    Nov 17, 2012
    Posts:
    186
    since the Translation is the local position relative to the parent, I'm pretty sure you'd need to use the parent's LocalToWorld, no? You could maybe do without using both LocalToWorld and LocalToParent, but I can't think of a way you could determine the parents' rotation and everything using only the child's LocalToWorld.
     
  8. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,292
    LocalToWorld matrix defines space in which current "transform" is positioned, oriented and scaled in.
    So, inversed LocalToWorld matrix should effectively be identical to WorldToLocal matrix.
    Which is what math.inverse does.

    Multiplying position on TRS matrix transforms that position into required space, but it will also rotate and scale.
    Where as math.transform is basically a matrix on matrix multiplication, but it only uses first column, which represents translation.

    Here's what math.transform does:
    Code (CSharp):
    1. /// <summary>Return the result of transforming a double3 point by a double4x4 matrix</summary>
    2.         [MethodImpl(MethodImplOptions.AggressiveInlining)]
    3.         public static double3 transform(double4x4 a, double3 b)
    4.         {
    5.             return (a.c0 * b.x + a.c1 * b.y + a.c2 * b.z + a.c3).xyz;
    6.         }
    So if you're looking for a way to set position in world space ("transform.position = value") this is it.

    This might be useful:
    https://docs.unity3d.com/Packages/com.unity.entities@0.17/manual/transform_system.html

    I could be wrong though regarding data layout. If so, then its not LocalToWorld, but rather LocalToParent matrix (child has it already). Which can be converted identically to "WorldToParent" parent space. Transforming against it should bring point to the parent space. Assigning transformed point to the child Translation should maintain child rotation / scale and parent position / rotation / scale relation.

    (tbh haven't used hybrid transforms in a while, relying on my own solution instead, but manual should provide proper info about data layout)
     
    Last edited: Feb 10, 2021
  9. maxxa05

    maxxa05

    Joined:
    Nov 17, 2012
    Posts:
    186
    So, if someone stumbles on this thread in the future, I ended up making 2 systems to do what I wanted, each running before TransformSystemGroup. It works great!

    If the entity has no parent:
    Code (CSharp):
    1.             Entities.WithChangeFilter<WorldLocationSetter>()
    2.                 .WithNone<Parent>()
    3.                 .ForEach((ref Translation translation, ref Rotation rotation, in WorldLocationSetter setter) =>
    4.                 {
    5.                     translation.Value = setter.Position;
    6.                     rotation.Value = setter.Rotation;
    7.                 }).Schedule();
    And if it does:
    Code (CSharp):
    1.             Entities.WithChangeFilter<WorldLocationSetter>()
    2.                 .ForEach((ref Translation translation, ref Rotation rotation,
    3.                     in WorldLocationSetter setter, in Parent parent) =>
    4.                 {
    5.                     var parentLocalToWorld = GetComponent<LocalToWorld>(parent.Value);
    6.                     var inverseParentLocalToWorld = math.inverse(parentLocalToWorld.Value);
    7.                     var wantedLocalToWorld = float4x4.TRS(setter.Position, setter.Rotation, new float3(1, 1, 1));
    8.                     var localToParent = math.mul(inverseParentLocalToWorld, wantedLocalToWorld);
    9.                    
    10.                     translation.Value = new float3(localToParent.c3.x, localToParent.c3.y, localToParent.c3.z);
    11.                     rotation.Value = new quaternion(localToParent);
    12.                 }).Schedule();
     
    Rangerz132 likes this.
  10. Rangerz132

    Rangerz132

    Joined:
    Feb 3, 2019
    Posts:
    38
    Hi @maxxa05!

    I'm currently experimenting with ECS DOTS and I have some issue with the parenting system.

    I would like to do a game similar to Knife Smash. The goal is quite simple, you click on the screen and that will throw a knife through a log. When the knife collides with the log, I want the knife to stop moving and become a child of the log. The goal is to throw all your knives while avoiding the other knives already thrown to the log.

    Everything seems to work fine with the translation and scale. However, the rotation seems to be a bit off.. The knife should face the log. Here's a screenshot to help you understand.

    Take not that the knifes (projectile) have a default rotation value of 90 degree in the X axes. It is the exact same thing for the log. However, the log is always rotating in the Z axes.

    Code (CSharp):
    1. Entities.WithNone<ProjectileStuck>().ForEach((ref Entity entity, ref Projectile projectile, ref Rotation rotation, ref Translation translation, ref NonUniformScale nonUniformScale, in Parent parent) =>
    2.                 {
    3.                    
    4.                     var parentLocalToWorld = GetComponent<LocalToWorld>(parent.Value);
    5.                     var inverseParentLocalToWorld = math.inverse(parentLocalToWorld.Value);
    6.                     var wantedLocalToWorld = float4x4.TRS(projectile.position, projectile.rotation, new float3(1, 1, 1));
    7.                     var localToParent = math.mul(inverseParentLocalToWorld, wantedLocalToWorld);
    8.  
    9.                  
    10.                     translation.Value = new float3(localToParent.c3.x, localToParent.c3.y, localToParent.c3.z);
    11.                     rotation.Value = new quaternion(localToParent);
    12.                     nonUniformScale.Value = new float3(1, 1, 1);
    13.  
    14.                     EntityManager.AddComponent<ProjectileStuck>(entity);
    15.  
    16.                     game.gameState = GameState.Log;
    17.                     SetSingleton(game);
    18.  
    19.                 }).WithStructuralChanges().Run();
    Do you have any ideas why?

    Thanks
     

    Attached Files: