Entity Spatial Relations

Hi all,

im adding a method, well actually a couple, in the AISystem.



The most important is "constructEntitySpatialRelations()"; Whereby in this method, the AI system will search through the entire supplied EntityNode and constructs two Hashmap. The first hashmap has the entity as the key and the spatial as the value. So you can obtain a spatial from an entity. The second is the opposite, the spatial is the key and the entity is the value.



The second hashmap will prove useful if your trying to obtain an Entity from a Ray-Spatial collision detection. This collision will prove extremely useful for providing "eyes" for the agents to study their surrounding environment and act accordingly. I am yet to create a method whereby to illiminate the ugly process where 2 Rays are cast soooo close together, that they obtain the same entity and have to process the same thing twice!



That, coupled with the messaging system in the AISystem class, the upcoming Neural Network, and the AgentActions will prove very useful in creating compeling AI. Ofcourse Pathfinding will play a role too. And there is always a special skill for creating realist AI…



DP

Oh yeah, one more thing.



constructEntitySpatialRelations() is an expensive process that only occurs once. So if your game doesn’t need Ray-Spatial Collision-obtainEntity, then you dont need to call it.



If you do need it, then its preferable you call it straight after the AISystem has been initialised.



There will be ofcourse a way to change the hashmaps individually if the relations change.



Also, the constructEntitySpatialRelations() process will use some Ram to store those two hashmaps. Or do you want to keep only 1 ( the spatial key, entity value) hashmap?



I think thats it…



DP

You could make it configurable in the AI system.



Eg, only contruct one specific one by default… but provide a method to call on the AI system BEFORE the construction method is called to let the user define what he wants to do:



public class AISystem {

    public static final int BOTH_SPATIAL_RELATIONS = 1;
    public static final int ENTITY_KEY_SPATIAL_RELATION = 2;
    public static final int SPATIAL_KEY_SPATIAL_RELATION = 3;

    private int mySpatialRelationSetting = ENTITY_KEY_SPATIAL_RELATION;

    /**
     *  Specifies which mappings the
     *  <code>constructEntitySpatialRelations</code>
     *  should create.  Valid options are:
     *  <UL>
     *    <LI>AISystem.BOTH_SPATIAL_RELATIONS
     *    <LI>AISystem.ENTITY_KEY_SPATIAL_RELATIONS
     *    <LI>AISystem.SPATIAL_KEY_SPATIAL_RELATIONS
     *  </UL>
     *
     *  It is suggested that you only create the mappings that
     *  are needed for your application to conserve memory.
     *
     *  @param relationship One of the spatial relationships
     */
    public void setEntitySpatialRelationConstruction(int relationship) {
        mySpatialRelationSetting = relationship;
    }

    ...

}



Or, you could just make the relationship an enum.. whatever. You get the idea. (Was providing the code and docs so you could cut and paste. Just trying to be helpful. :) )


I do have one question, though.. can the spatial relationships change? It seems like they could. Ie, the map will be incomplete if new ones are added. Would there be a way to dynamically update the mapping?

Also.. and maybe more importantly.. If spatial relationships are removed, the map will still reference them. This will cause a memory leak.. the garbage collector will not be able to clean up the unused objects because they'll be referenced by the map. (HashMaps are notorious for causing this kind of problem.) So.. could you remove items from the map automatically if the entity/spatial/whatever is removed?


--K

what happens is that a spatial must be set in entity for the construction to occur. For spatial changes in entity, il provide a method to check all spatials and update references automagically. Removing unused ones, adding new spatials…etc.



Would this solve your problem about the memory leak?



Another interesting idea is to pass the rootRenderingNode as an argument to one of the AISystem’s methods. This method would loop through the entire graphics scene and compare the spatials that are in the rootRenderingNode and the hashmap. If some spatials are in the hashmap and not in the rootRenderingNode, the reference is removed from the hashmap. Again, its just an idea.



DP

yes, calling this method will automagically clean up both hashmaps.



However, both methods are equally as expensive if not the first being more expensive because:



The first method:

Loop through the entire Entity tree and call “getSpatial()” for the Entities. Then see if that spatial is contained in the hashmaps (one of them is an easy get(spatial) the other is a complicated loop). If a spatial exists in the hashmap then flag that spatial as OK. If there are some spatials that are still in the hashmaps that have not been flagged as being in an entity, they will be removed. If some entities have spatials that are not referenced in the hashmap, then add.



The second method:

Loop through the entire Rendering Tree and obtain all Spatials. Compare the spatials in the tree with the hashmap, again, the comparison is dependant on the hashmap, one is an easy get(spatial), the other is a complex loop. If a reference in the hashmap exists that doesn’t in the tree, then delete from the hashmap. If an spatial is to be found in the rendering tree that is not in the hashmap, then look for an entity that contains that spatial, if one exists, then add it to the hashmap, it it doesnt, then leave the hashmap as it is.



The first method doesnt’ need an argument and we could possibly chop this process over a few frames (configurable perhaps?) to lessen its impact and to let the GC to kick in slowly and over a period of time rather than just one big CLONG. That is ofcourse that we can guarantee that the GC will kick in tho. And if the frame number is kept low (say a 5th to a 10th of the FPS is given to update the hashmaps) then not a lot of time will be given for something to happen that the next update can’t solve quickly without an impact.



Besides, I dont see a scenario (expect level changes) where the hashmaps will vary alot.



As you can see, this needs very careful thinking…



DP

well, thank you very very very very very much mojo, you solved it for me!



hehe, the collision system that he has in place will be great for doing entity - entity collisions without the need for the relations. Basically, when mojo is done, im adding a little method in AISystem that will handle collisions for ya:



public Entity[] checkCollisions(Entity t) {
 /* loop through all the entities and check if their spatials collide with the
spatial of the parameter. If so, add that entity to the array
 */
}



So its gonna be relatively simple to do, and that also means we dont have to have a HashMap in each entity anymore.

Again, thanks mojo

DP

Check Against entire AISystem:


   /**
    * Checks to see whether a collision has occured or not with the parameter's
    * entity. If a collision has occured, it will store the Entity that it has
    * collided with in an array. When its done it will return that array. A
    * boolean parameter states whether to go down to the triangle level or not.
    *
    * @param checkEntity,
    *            the Entity to check against all other entities
    * @param triangleAccuracy,
    *            to check with triangle accuracy or not
    * @return Entity[]
    */
   public Entity[] hasCollision(Entity checkEntity, boolean triangleAccuracy) {
      // create a new arraylist to hold all the
      // collided entities
      ArrayList entitiesWithCollision = new ArrayList();
      // obtain an iterator from the hashmap
      Iterator it = entityList.values().iterator();
      // if the iterator still has more items, then loop
      while (it.hasNext()) {
         // obtain the next entity, check to see
         // if that entity is not the entity that the
         // user has supplied.
         Entity pEntity = (Entity) it.next();
         if (pEntity != checkEntity) {
            // obtain the spatial of that entity
            // and check for a collision
            Spatial spat = pEntity.getSpatial();
            boolean collision = checkEntity.getSpatial().hasCollision(spat,
                  triangleAccuracy);
            // if the collision is true, then
            // add that entity to the array list
            if (collision == true) {
               entitiesWithCollision.add(pEntity);
            }
         }
      }

      // when the loop finishes looping, return an array
      // of entities that has collided
      return (Entity[]) entitiesWithCollision.toArray();
   }



And below is the code that checks against an array of entities and returns an array of successfully collided entities:

Check against specified array:


   /**
    * Checks a collision with an entity versus an array of entities. This
    * checks against a specified array of entities and returns an array of
    * entities that the collision has occured with. A boolean parameter
    * specifies whether the collision is triangle accurate or not
    *
    * @param checkEntity,
    *            the entity to check
    * @param arrayOfEntities,
    *            a array of entities to check against
    * @param triangleAccuracy,
    *            whether the collision detection is triangle accurate
    * @return Entity[]
    */
   public Entity[] hasCollision(Entity checkEntity, Entity[] arrayOfEntities,
         boolean triangleAccuracy) {
      // an arraylist which holds the successful collision entities
      ArrayList entitiesWithCollision = new ArrayList();
      // loop through the array
      for (int i = 0; i < arrayOfEntities.length; i++) {
         // obtain the entity and check that it
         // isn't the same as the entity that we are
         // checking for
         Entity pEntity = arrayOfEntities[i];
         if (pEntity != checkEntity) {
            // obtain the spatial and see if a
            // collision has occured
            Spatial spat = pEntity.getSpatial();
            boolean collision = checkEntity.getSpatial().hasCollision(spat,
                  triangleAccuracy);
            // if so, then add to the list
            if (collision == true) {
               entitiesWithCollision.add(pEntity);
            }
         }
      }
      // when the loop finishes looping, return an array
      // of entities that has collided
      return (Entity[]) entitiesWithCollision.toArray();
   }



I think that that has solved this issue. And ive added that to the zip

Thx, DP