ECS : same transformation component for both physic and graphic

Started by
6 comments, last by Zipster 6 years, 7 months ago

I encapsulated Physics and Graphics with ECS successfully.

Here is a simplified version :-


Physics Rigid Body = Physic_MassAndInertia + Physic_Transform + Physic_Shape
Graphic Polygon Body = Graphic_Transform + Graphic_Mesh

I usually set their transform via :-


findService<Service_PhysicTransform>()->setPosition(physicEntity,somePos);
findService<Service_GraphicTransform>()->setPosition(graphicEntity,somePos);

It works so nice, and there is no problem in practice, because I always know its "type" (physic/graphic).  

However, I notice that Physic_Transform and Graphic_Transfrom are very similar and duplicate.

For the sake of good practice and maintainability, I consider to group them into Generic_Transform.


findService<Service_Transform>()->setPosition(anyEntity,somePos);  //cool

However, there is a little difficulty.  The physic transformation is quite complex for a child inside a compound body.

Assume that a physic body B is a child of a compound body C.   In this case, B's transformation component currently has no meaning (by design).

If I want to set the child transformation setTransformation(B,(x,y,45deg)), my current physic engine will not set the B's transformation directly - it will set C's transformation that make B's position match (x,y,45deg).

image.png.8d081eca5b014314aee226fc7564697d.png

Thus, it is not so practical to group them, except I code it like (ugly and worse performance):-


class Service_Transform{
    public: void setPosition(Entity B,Vec2 pos){
        bool checkIsPhysic = .... //check if B is physic
        if(checkIsPhysic){//physic
            Entity compound = .... //find C
            ComponentPtr<Transform> cCompound = compound.get<Transform>();
            cCompound->pos=pos*someValue; //calculate some transformation for C
        }else{//graphic
            ComponentPtr<Transform> cTransform=B.get<Transform>();
            cTransform->pos=pos;
        }
    }
}

Should I group them  into 1 type of component?  

I probably should not group them because its meaning and related implementation are very different, but I feel guilty ... I may miss something.

Advice about other things are also appreciated.  Thanks.

Edit: Hmm... I start to think that grouping component is OK, but I should still use 2 function (from different service/system).

Edit2: fix some code (pointed out by 0r0d, thank)

Advertisement

I don't think you should have two different transform components, no. You shouldn't implement the physics using the ECS-transform-component eigther. Physics is a vastly complex  system that requires (or at least should have) a separate implementation. So optimally, you'd just use the transform-component to communicate between the different system, which keeping a separate view of the physics-world:


struct PhysicsComponent
{
  RigidBody* pBody; // pointer to a body aquired from the physics-world upon initialization => could also be queried instead
};

struct PhysicsSystem
{
  
  void Update(double dt) // just to showcase...
  {
    world.Tick(dt);
    
    for(auto entity : EntitiesWithComponents<Physics, Transform>())
    {
      auto& physics = entity.GetComponent<Physics>();
      entity.GetComponent<Transform>().SetTransform(physics.pBody->QueryTransform());
    }
  }    
  
private:
  
  PhysicsWorld world;
};

(This is just some pseudo-code to get the idea across). So essentially Transform becomes a point for communication between different systems. What the physics-system wrote in, you can later read out ie. in the RenderSystem; also your gameplay-systems can just set the transform as well. Entities just register new rigidbodies to the physics-world, but the world doesn't know that an entity even exists, which keeps your physics separated & more flexibel. For example, its pretty easy in this system to exchange your physics with say bullet at some time, while what you originally do creates a sort of tight coupling between those independant modules.

As a general consensus, you should only implement the bare minimum required functionality inside the ECS, if possible. Do not use ECS as a basis for physics, rendering, HUD, ... but instead implemented those systems separately, and use the ECS as the high-level intercommunication-layer. At that, it really excells IMHO, but everything more will just result in many of the reasons why people despise ECS.

Hope that gives you a rough idea.

I'm of the opinion that you should always have distinct, domain-specific representations of information.  Physics and rendering are two different domains, thus two transforms. You can split hairs over where these transforms live, and whether or not rendering/physics is part of the ECS, but regardless of where they are you should have one transform owned by "physics" and one transform owned by "rendering" (at least). Don't get distracted by the fact that they may appear similar or duplicate -- they are completely distinct conceptually. If you allow them to both own the same transform, you'll quickly find them fighting each other over even simple changes to functionality.

Don't think of data as a "point of communication". Communication is a behavior, and in the absence of code the only place behaviors exist are in assumptions and inferences. It may be fine for the rendering code to assume the transform mean one thing, and for the physics code to assume the transform mean something else, but when it comes to communicating that information across domain boundaries, you should't force the rendering code to make assumptions about what the transform means to the physics code and vice versa. This is exactly the type of behavioral coupling you'll come to regret

11 hours ago, hyyou said:

 



findService<Service_Transform>()->setPosition(anyEntity);  //cool

 

I'm not clear what the above code is supposed to do.

15 hours ago, Juliean said:

... but instead implemented those systems separately

Thank Julien. 

In a more real case, I structure it like this :-


rocket = HP + physic_owner + graphic_owner + ....
Physic body = physic_ownee + bulletBodyAndShape_holder 
         + cache_physicAttr(pos,v,gravity,mass,mask) + ...
Graphic body = graphic_ownee 
         + ogreBody_holder (Ogre2.1 graphic body)
         + cache_renderAttribute(pos,blend_mode,layer) 
         + mesh_owner
mesh = mesh_ownee + filename 
        + ogreMeshPtr(point to vertex buffer)

The physic_owner / graphic_owner / physic_ownee / graphic_ownee  are just attach-point.  

If I want to create a rocket, I tend to create 1 rocket entity attach to 2-3 new physic body, 2-3 new graphic body.     The graphic body attach to a shared mesh.   Thus, for a single rocket, I create around 5-7 entities.

I copied the idea from https://gamedev.stackexchange.com/questions/31888/in-an-entity-component-system-engine-how-do-i-deal-with-groups-of-dependent-ent , not sure if it smells. ...  

For me, it works pretty good, but I may overlook something.

P.S. Your comment in another topic of mine boosted my engine's overall performance by 5%. Thank a lot.

7 hours ago, Zipster said:

Don't think of data as a "point of communication"

That is enlightening, thank.

@0r0d  You are right. I have edited the post :)

4 hours ago, hyyou said:

Thank Julien. 

In a more real case, I structure it like this :-



rocket = HP + physic_owner + graphic_owner + ....
Physic body = physic_ownee + bulletBodyAndShape_holder 
         + cache_physicAttr(pos,v,gravity,mass,mask) + ...
Graphic body = graphic_ownee 
         + ogreBody_holder (Ogre2.1 graphic body)
         + cache_renderAttribute(pos,blend_mode,layer) 
         + mesh_owner
mesh = mesh_ownee + filename 
        + ogreMeshPtr(point to vertex buffer)

The physic_owner / graphic_owner / physic_ownee / graphic_ownee  are just attach-point.  

If I want to create a rocket, I tend to create 1 rocket entity attach to 2-3 new physic body, 2-3 new graphic body.     The graphic body attach to a shared mesh.   Thus, for a single rocket, I create around 5-7 entities.

Are these all single components? phyiscs_owner, phyiscs_ownee, HP, ...? If so, it does sound needlessly complex, as I laid out in the other post there's really not much point of having stuff like "filename" as a component, but you should see for yourself ;) On the same note, having 5-7 entities for a single rocket sounds like huge overkill. Personally, I would have a rocket-mesh with a Mesh & Physics-component, and thats pretty much it; unless the rocket specifically needs sub-entities like a particle-emitter :D (Thats just what I came to agree on based on 4 years of working with an ECS across different projects; you might come to a different conclusion; though based on your workflow/toolchain, a certain few things like having many components & entities can make creating new content a nightmare).

12 hours ago, Zipster said:

Don't think of data as a "point of communication". Communication is a behavior, and in the absence of code the only place behaviors exist are in assumptions and inferences. It may be fine for the rendering code to assume the transform mean one thing, and for the physics code to assume the transform mean something else, but when it comes to communicating that information across domain boundaries, you should't force the rendering code to make assumptions about what the transform means to the physics code and vice versa. This is exactly the type of behavioral coupling you'll come to regret

I honestly don't agree to that statement. Whats wrong with using a Transform-component to communicate information between different systems, that all might have their own internal transform-structure? You're using data to transfer information anyways at some point, like sending information across a network, or storing and later loading a save-file. There's not code inside the save-file, its just data at that point, but because you agreed on a format, its save to store at one point and load at a later point. Thats how I see it with this example of the transform as well: You agree on a shared "format" for world-transform on a shared "Transform"-component, and systems can read/write to the transform while still being able to internally use their own data. You can still use buisness-logic to make the writing/reading of this transform-data more safe, but I don't see the benefit of code-based communication vs a data-"stream" of sorts.

9 hours ago, Juliean said:

I honestly don't agree to that statement. Whats wrong with using a Transform-component to communicate information between different systems, that all might have their own internal transform-structure?

Actually it seems we do agree, at least on each system having its own internal structure. I just like to think in terms of domains as opposed to systems, since I find the conceptual delineation better for determining when different representations are actually needed. However if your systems are 1:1 with behaviors (physics, rendering, etc.), then it's essentially the same :)

I'm also in full agreement with having a separate component handle the transfer of data between systems, but in that case I don't see a need for any intermediary shared state between them. I'm not even sure what purpose it would have, or if it's feasible in the first place. How would you reconcile an OOBB or capsule used by physics, with a matrix transform used by rendering, with a sphere used by gameplay, all into a single representation that makes sense? And why would you even need it if each system already has sufficient internal state to function? The purpose of the transform component in this case would just be to copy the data from one system to another and perform any necessary conversion. Instead of each system assuming internal details about the other, you've inverted your dependencies, shifted that responsibility to a higher level, and made the data relationship explicit in code for all to see.

This topic is closed to new replies.

Advertisement