Search Unity

Question Modifying collision results to keep partial momentum on collision with destructile object

Discussion in 'Physics for ECS' started by davidus2010, Mar 7, 2021.

  1. davidus2010

    davidus2010

    Joined:
    Dec 4, 2017
    Posts:
    72
    Hello, i am working on a ship building game, where each ship is made out of many smaller blocks. I am using the blocks to create a compound collider which then represents the ship. The issue i'm currently facing is that when the ship collides with something, i want some of the blocks which were part of the collision to be destroyed. To destroy blocks on a ship, i remove the child entities and rebuild the compound collider of the ship (here is a video of something similar to what i'm trying to achieve in space engineers: youtu.be/J3LnHsJHLdM?t=159.
    What i have tried so far was to edit the compound colliders sub-colliders to raise collision events, and then either destroy or damage the blocks that were part of the collision, depending in the impact force. However, this still results in the ship loosing all it's momentum when colliding. I also tried to get the velocity of the ship and then apply a fraction of the ship's velocity again after the collision, but this did not work as expected.
    What would be the recommended way to achieve this? Could i use an IJacobiansJob to modify the physics simulation results to make the ship keep it's velocity?
     
  2. argibaltzi

    argibaltzi

    Joined:
    Nov 13, 2014
    Posts:
    220
    If the ship has some constant velocity applied every frame it should keep moving forward after collision?
    how about storing the previous frame velocity and apply some of that lost velocity due to collision as a one time force?
     
  3. milos85miki

    milos85miki

    Joined:
    Nov 29, 2019
    Posts:
    197
    The goal of a physics engine is to prevent penetration, so it applies impulses to both colliding bodies in order to keep them apart. If you're hitting a static wall head-on, it's expected that you'll lose the velocity normal to the wall.

    Of course you can change the velocity, just make sure to do it after ExportPhysicsWorld and before next BuildPhysicsWorld. If I had to make a guess based on the info you provided, I'd say your "thrusters" system is updating before ExportPhysicsWorld.
     
  4. milos85miki

    milos85miki

    Joined:
    Nov 29, 2019
    Posts:
    197
    Hi @davidus2010 , if you want to detect potential impact and implement destruction, here is something you could try: Create a "secondary" PhysicsWorld (ideally with important bodies only or by completely cloning the main one) and step it before building the main one to see what will actually collide (you can use StepImmediate). Then you'd have an opportunity to change entity components and make static bodies dynamic (or split them into dynamic pieces, however you'd like to do destruction). Then the main PhysicsWorld would get rebuilt in BuildPhysicsWorld and collisions would affect the bodies that were previously static.
     
  5. argibaltzi

    argibaltzi

    Joined:
    Nov 13, 2014
    Posts:
    220
    wouldn't that be expensive?
     
  6. milos85miki

    milos85miki

    Joined:
    Nov 29, 2019
    Posts:
    197
    Yes, but depends on what you want to put into the secondary world.
     
  7. davidus2010

    davidus2010

    Joined:
    Dec 4, 2017
    Posts:
    72
    Thank you all for the ideas. I decided to take the approach of storing the previous velocity and then partially applying it again. Took me a while to figure out where i need to run the system and how to set up dependencies to athe physics jobs, but i got it working okay in the end. Only thing that's still a bit weird is when gravity is enabled, the entity sometimes starts to "bounce" a bit before stopping.

    In case anyone wants to do something similar, here's my current code:
    using Unity.Burst;
    using Unity.Collections;
    using Unity.Entities;
    using Unity.Jobs;
    using Unity.Mathematics;
    using Unity.Physics;
    using Unity.Physics.GraphicsIntegration;
    using Unity.Physics.Systems;
    using Unity.Transforms;
    using UnityEngine;

    [UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
    [UpdateAfter(typeof(ExportPhysicsWorld))]
    [UpdateBefore(typeof(EndFramePhysicsSystem))]
    public class GridCollisionDetectionSystem : SystemBase {

    public static readonly float collisionDamageMultiplier = 0.2f;

    private EndFixedStepSimulationEntityCommandBufferSystem commandBufferSystem;

    protected override void OnCreate() {
    commandBufferSystem = World.GetOrCreateSystem<EndFixedStepSimulationEntityCommandBufferSystem>();
    }

    protected override void OnUpdate() {
    var commandBuffer = commandBufferSystem.CreateCommandBuffer();

    var physicsWorldSystem = World.DefaultGameObjectInjectionWorld.GetExistingSystem<BuildPhysicsWorld>();
    var stepPhysicsWorld = World.DefaultGameObjectInjectionWorld.GetExistingSystem<StepPhysicsWorld>();
    var physicsWorld = physicsWorldSystem.PhysicsWorld;

    Dependency = new gridCollisionJob() {
    gridData = GetComponentDataFromEntity<GridMainData>(true),
    blockTags = GetComponentDataFromEntity<PlacedGridBlockTag>(true),
    childBuffers = GetBufferFromEntity<Child>(true),
    blockIntegrities = GetComponentDataFromEntity<BlockIntegrity>(false),
    physicsVelocities = GetComponentDataFromEntity<PhysicsVelocity>(false),
    physicsBuffer = GetComponentDataFromEntity<PhysicsVelocityCache>(true),
    slowdownCaches = GetBufferFromEntity<CollisionSlowDownCached>(false),
    commandBuffer = commandBuffer,
    physicsWorld = physicsWorld
    }.Schedule(stepPhysicsWorld.Simulation, ref physicsWorld, JobHandle.CombineDependencies(Dependency, stepPhysicsWorld.FinalSimulationJobHandle));

    commandBufferSystem.AddJobHandleForProducer(Dependency);

    World.DefaultGameObjectInjectionWorld.GetExistingSystem<ApplyCollisionSlowdownCaches>().inputDependency = Dependency;
    }

    [BurstCompile]
    struct gridCollisionJob : ICollisionEventsJob {
    [ReadOnly] public ComponentDataFromEntity<GridMainData> gridData;
    [ReadOnly] public ComponentDataFromEntity<PlacedGridBlockTag> blockTags;
    [ReadOnly] public ComponentDataFromEntity<PhysicsVelocityCache> physicsBuffer;
    [ReadOnly] public BufferFromEntity<Child> childBuffers;
    [ReadOnly] public PhysicsWorld physicsWorld;

    public BufferFromEntity<CollisionSlowDownCached> slowdownCaches;

    public ComponentDataFromEntity<PhysicsVelocity> physicsVelocities;
    public ComponentDataFromEntity<BlockIntegrity> blockIntegrities;

    public EntityCommandBuffer commandBuffer;

    public void Execute(CollisionEvent collisionEvent) {
    var details = collisionEvent.CalculateDetails(ref physicsWorld);

    if (details.EstimatedImpulse <= 10) return;

    handleCollisionForEventPartner(collisionEvent.EntityA, collisionEvent.ColliderKeyA, collisionEvent.BodyIndexA, details.EstimatedImpulse);
    handleCollisionForEventPartner(collisionEvent.EntityB, collisionEvent.ColliderKeyB, collisionEvent.BodyIndexB, details.EstimatedImpulse);
    }

    private unsafe void handleCollisionForEventPartner(Entity gridEntity, ColliderKey colliderKey, int rbIndex, float detailsEstimatedImpulse) {
    if (!gridData.HasComponent(gridEntity) || !slowdownCaches.HasComponent(gridEntity) || !childBuffers.HasComponent(gridEntity)) return;

    var blocksOnGrid = childBuffers[gridEntity];

    var hitCollider = physicsWorld.CollisionWorld.Bodies[rbIndex].Collider;

    var isCompound = (CompoundCollider*) hitCollider.GetUnsafePtr();
    colliderKey.PopSubKey(isCompound->NumColliderKeyBits, out var indexKey);

    for (var i = 0; i < blocksOnGrid.Length; i++) {
    var child = blocksOnGrid.Value;

    if (!blockTags.HasComponent(child)) continue;

    var blockTag = blockTags[child];

    if (blockTag.colliderIndexKey == indexKey) {
    var blockHP = blockIntegrities[child];
    blockHP.Value -= detailsEstimatedImpulse * collisionDamageMultiplier;

    if (blockHP.Value < 0) {
    commandBuffer.DestroyEntity(child);
    commandBuffer.AddComponent<GridNeedsColliderRebuildTag>(gridEntity);

    //restore partial momentum of grid
    var lastSpeed = physicsVelocities[gridEntity];
    var previousFrameSpeed = physicsBuffer[gridEntity].Value;
    lastSpeed.Linear = previousFrameSpeed;

    slowdownCaches[gridEntity].Add(new CollisionSlowDownCached {MomentumRemoved = blockTag.initialIntegrity / collisionDamageMultiplier});

    physicsVelocities[gridEntity] = lastSpeed;
    }
    else {
    blockIntegrities[child] = blockHP;
    }

    break;
    }
    }
    }
    }
    }

    [InternalBufferCapacity(16)]
    struct CollisionSlowDownCached : IBufferElementData {
    public float MomentumRemoved;
    }

    [UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
    [UpdateAfter(typeof(GridCollisionDetectionSystem))]
    [UpdateBefore(typeof(EndFramePhysicsSystem))]
    public class ApplyCollisionSlowdownCaches : SystemBase {

    internal JobHandle inputDependency;

    protected override void OnUpdate() {

    var cacheBuffer = GetBufferFromEntity<CollisionSlowDownCached>();

    Dependency = Entities.WithBurst().ForEach((Entity ent, ref PhysicsVelocity velocity, in PhysicsMass mass, in GridMainData gridMainData) => {

    if (!cacheBuffer.HasComponent(ent)) return;

    var entBuffer = cacheBuffer[ent];

    if (entBuffer.Length <= 0) return;

    var momentum = math.length(velocity.Linear / mass.InverseMass);

    for (var i = 0; i < entBuffer.Length; i++) {
    var slowDownCached = entBuffer;
    momentum -= slowDownCached.MomentumRemoved;
    }

    entBuffer.Clear();

    velocity.Linear = math.normalize(velocity.Linear) * momentum * mass.InverseMass;
    }).Schedule(JobHandle.CombineDependencies(Dependency, inputDependency));


    World.DefaultGameObjectInjectionWorld.GetExistingSystem<EndFramePhysicsSystem>().AddInputDependency(Dependency);
    }
    }