Convert a Rigidebody script into a Physics Body? - Buoyancy system

Hi,

For a DOTS seafaring game we want to use an existing very performant buoyancy system (DWP2) and make it compatible with DOTS. The buoyancy needs a Rigidebody on object so we want it functional with a Physics Body instead.
After contacting the dev, he said the only Rigidebody function he is using in the script is: AddForceAtPosition.

I’m wondering what is the best solutions to make it compatible. Is there a chance Unity Physics come up with support to make existing Rigidebody script work with Physics Body? Or it’s not likely and it is better to just rework the script with equivalent functions (PhysicsWorld.AddImpulseAtPoint) at risk the calculations for buoyancy could give a different result on object.

I really appreciate your help as I’m not very comfortable with DOTS Physics.

This isn’t complete code but you could be able to rewrote it for your own project:

This is part of a wrapper I am working on to expose the legacy Rigidbody interface.
Get/SetComponent are just wrappers for World.EntityManager.Get/SetComponentData(Entity). World and Entity are store locally, hence not exposed in this function. The physics TimeStep is also held by the wrapper.

        public void AddForceAtPosition(float3 force, float3 position, ForceMode mode = ForceMode.Force)
        {
            if (!isDynamic) return;

            var mass = GetComponent<PhysicsMass>();
            mass.GetImpulseFromForce(force, mode, TimeStep, out float3 impulse, out PhysicsMass impulseMass);

            var translation = GetComponent<Translation>();
            var rotation = GetComponent<Rotation>();
            var velocity = GetComponent<PhysicsVelocity>();

            velocity.ApplyImpulse(impulseMass, translation, rotation, impulse, position);

            AddOrSetComponent(velocity);
        }

Next this extension function implements the conversation of forces into velocity changes (i.e. impulses)

        public static void GetImpulseFromForce(this PhysicsMass mass, float3 force, UnityEngine.ForceMode mode, float timeStep, out float3 impulse, out PhysicsMass impulseMass)
        {
            var unitMass = new PhysicsMass { InverseInertia = new float3(1.0f), InverseMass = 1.0f, Transform = mass.Transform };

            switch (mode)
            {
                case UnityEngine.ForceMode.Force:
                    // Add a continuous force to the rigidbody, using its mass.
                    impulseMass = mass;
                    impulse = force * timeStep;
                    break;
                case UnityEngine.ForceMode.Acceleration:
                    // Add a continuous acceleration to the rigidbody, ignoring its mass.
                    impulseMass = unitMass;
                    impulse = force * timeStep;
                    break;
                case UnityEngine.ForceMode.Impulse:
                    // Add an instant force impulse to the rigidbody, using its mass.
                    impulseMass = mass;
                    impulse = force;
                    break;
                case UnityEngine.ForceMode.VelocityChange:
                    // Add an instant velocity change to the rigidbody, ignoring its mass.
                    impulseMass = unitMass;
                    impulse = force;
                    break;
                default:
                    impulseMass = mass;
                    impulse = float3.zero;
                    break;
            }
        }

velocity.ApplyImpulse should already exist in the Unity Physics package.

this looks really great, I really appreciate you share your hard work.
We’ll try it this week, I let you know how it goes

How would I add Torque?

So here is what I got. Its like almost there but has some issues. Here is video to see what is happening.

  • First I used ForceMode.Impulse instead of ForceMode.Acceleration because the object was getting crazy results flying out of water and stuff

  • Physics.gravity.normalized is probably not getting DOTS Gravity so it might be messing things up.

  • The objects act crazy at the beginning but stabilize. Not sure why.

  • Sometimes the objects kind of go crazy for a few moments randomly. Not sure why.

Any feedback and help would be appreciated.

using System.Collections.Generic;
using Unity.Entities;
using Unity.Physics;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
using Unity.Physics.Extensions;
using Unity.Entities.UniversalDelegates;

namespace DOTSActorWater
{
    public class ActorWaterBuoyancySystem : ComponentSystem
    {

        private float deltaTime;

        protected override void OnUpdate()
        {
            var forces = new List<float3>();
            var physicsWorld = World.GetExistingSystem<Unity.Physics.Systems.BuildPhysicsWorld>().PhysicsWorld;
            deltaTime = Time.DeltaTime;

            Entities.ForEach((Entity entity, ref ActorActionWaterBuoyancyComponent actorActionWaterBuoyancyComponent, ref Translation translation,ref Rotation rotation, ref LocalToWorld localToWorld, ref PhysicsVelocity physicsVelocity, ref PhysicsMass physicsMass) =>
            {
                if (actorActionWaterBuoyancyComponent.bottomDepth > 0 && !actorActionWaterBuoyancyComponent.disabled)
                {
                    var velocityRelativeToWater = physicsVelocity.Linear - actorActionWaterBuoyancyComponent.waterSurfaceVelocity;
                    var bottomDepth = actorActionWaterBuoyancyComponent.bottomDepth;

                    //Buoyancy
                    var buoyancy = -Physics.gravity.normalized * actorActionWaterBuoyancyComponent.buoyancyCoeff * bottomDepth * bottomDepth * bottomDepth;
                    physicsVelocity.Linear += (float3)buoyancy * deltaTime;

                    //Get Forces
                    var forcePosition = translation.Value + actorActionWaterBuoyancyComponent.forceHeightOffset * (float3)Vector3.up;
                    forces.Add(Vector3.up * Vector3.Dot(Vector3.up, -velocityRelativeToWater) * actorActionWaterBuoyancyComponent.dragInWaterUp);
                    forces.Add(localToWorld.Right * Vector3.Dot(localToWorld.Right, -velocityRelativeToWater) * actorActionWaterBuoyancyComponent.dragInWaterRight);
                    forces.Add(localToWorld.Forward * Vector3.Dot(localToWorld.Forward, -velocityRelativeToWater) * actorActionWaterBuoyancyComponent.dragInWaterForward);

                    //Apply Forces NEW
                    for (int i = 0; i < forces.Count; i++)
                        AddForceAtPosition(ref physicsMass, ref translation, ref rotation, ref physicsVelocity, forces[i], forcePosition, ForceMode.Impulse);



                    //Torque
                    {
                        var torqueWidth = (float3)Vector3.Cross(localToWorld.Up, actorActionWaterBuoyancyComponent.waterSurfaceNormal);
                        var torqueLength = (float3)Vector3.Cross(localToWorld.Up, actorActionWaterBuoyancyComponent.normalLongitudinal);

                        physicsVelocity.Angular += torqueWidth * actorActionWaterBuoyancyComponent.boyancyTorque * deltaTime;
                        physicsVelocity.Angular += torqueLength * actorActionWaterBuoyancyComponent.boyancyTorque * deltaTime;

                    }
                }
            });

        }

        private void AddForceAtPosition(ref PhysicsMass physicsMass, ref Translation translation, ref Rotation rotation, ref PhysicsVelocity velocity, float3 force, float3 position, ForceMode mode = ForceMode.Force)
        {

            GetImpulseFromForce(ref physicsMass, force, mode, deltaTime, out float3 impulse, out PhysicsMass impulseMass);
            velocity.ApplyImpulse(impulseMass, translation, rotation, impulse, position);
        }

        private void AddAngularImpulseAtPosition(ref PhysicsMass physicsMass, ref PhysicsVelocity velocity, float3 force, float3 position, ForceMode mode = ForceMode.Force)
        {

            GetImpulseFromForce(ref physicsMass, force, mode, deltaTime, out float3 impulse, out PhysicsMass impulseMass);
            velocity.ApplyAngularImpulse(physicsMass, impulse);
        }


        private void GetImpulseFromForce(ref PhysicsMass mass, float3 force, UnityEngine.ForceMode mode, float timeStep, out float3 impulse, out PhysicsMass impulseMass)
        {
            var unitMass = new PhysicsMass { InverseInertia = new float3(1.0f), InverseMass = 1.0f, Transform = mass.Transform };


            switch (mode)
            {
                case UnityEngine.ForceMode.Force:
                    // Add a continuous force to the rigidbody, using its mass.
                    impulseMass = mass;
                    impulse = force * timeStep;
                    break;
                case UnityEngine.ForceMode.Acceleration:
                    // Add a continuous acceleration to the rigidbody, ignoring its mass.
                    impulseMass = unitMass;
                    impulse = force * timeStep;
                    break;
                case UnityEngine.ForceMode.Impulse:
                    // Add an instant force impulse to the rigidbody, using its mass.
                    impulseMass = mass;
                    impulse = force;
                    break;
                case UnityEngine.ForceMode.VelocityChange:
                    // Add an instant velocity change to the rigidbody, ignoring its mass.
                    impulseMass = unitMass;
                    impulse = force;
                    break;
                default:
                    impulseMass = mass;
                    impulse = float3.zero;
                    break;
            }
        }

    }
}

Here is updated and better code. I like the results.
The Physics Body has linear and angular damping set to 1 for objects. Mass is pretty high such as 1240000 for boat. Gravity is 1.

using System.Collections.Generic;
using Unity.Entities;
using Unity.Physics;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
using Unity.Physics.Extensions;
using Unity.Entities.UniversalDelegates;
using Crest;
using UnityEditor.Build.Pipeline;

namespace DOTSActorWater
{
    [UpdateAfter(typeof(Unity.Physics.Systems.StepPhysicsWorld))]
    public class ActorWaterBuoyancySimpleSystem : ComponentSystem
    {
        private SampleHeightHelper sampleHeightHelper;
        private SampleFlowHelper sampleFlowHelper;
        private Vector3 _displacementToObject;

        private float3 position;
        private Vector3 normal;
        private Vector3 waterSurfaceVel;

        private Vector3 undispPos;
        private Vector3 dispPos;
        private float bottomDepth;

        private Dictionary<int, SampleHeightHelper> sampleHeightHelpers = new Dictionary<int, SampleHeightHelper>();
        private Dictionary<int, SampleHeightHelper> sampleHeightLengthHelpers = new Dictionary<int, SampleHeightHelper>();
        private Dictionary<int, SampleFlowHelper> sampleFlowHelpers = new Dictionary<int, SampleFlowHelper>();

        private float deltaTime;

        protected override void OnUpdate()
        {
            deltaTime = Time.DeltaTime;

            CrestScanner();
            DoBuoyancy();
        }

        private void CrestScanner()
        {
            //Check For Ocean Rendrer
            if (OceanRenderer.Instance == null)
                return;

            var collProvider = OceanRenderer.Instance.CollisionProvider;
            var deltaTime = Time.DeltaTime;

            Entities.ForEach((Entity entity, ref ActorActionWaterBuoyancySimpleComponent actorActionWaterBuoyancyComponent, ref Translation translation, ref LocalToWorld localToWorld) =>
            {
                position = translation.Value;
                normal = Vector3.up;
                waterSurfaceVel = Vector3.zero;
                _displacementToObject = Vector3.zero;

                if (!sampleHeightHelpers.ContainsKey(entity.Index))
                {
                    sampleHeightHelpers.Add(entity.Index, new SampleHeightHelper());
                    sampleHeightLengthHelpers.Add(entity.Index, new SampleHeightHelper());
                    sampleFlowHelpers.Add(entity.Index, new SampleFlowHelper());
                }
                sampleFlowHelper = sampleFlowHelpers[entity.Index];
                sampleHeightHelper = sampleHeightHelpers[entity.Index];

                sampleHeightHelper.Init(position, actorActionWaterBuoyancyComponent.objectWidth);
                sampleHeightHelper.Sample(ref _displacementToObject, ref normal, ref waterSurfaceVel);

                if (QueryFlow.Instance)
                {
                    sampleFlowHelper.Init(position, actorActionWaterBuoyancyComponent.objectWidth);

                    Vector2 surfaceFlow = Vector2.zero;
                    sampleFlowHelper.Sample(ref surfaceFlow);
                    waterSurfaceVel += new Vector3(surfaceFlow.x, 0, surfaceFlow.y);
                }

                actorActionWaterBuoyancyComponent.waterSurfaceVelocity = waterSurfaceVel;
                actorActionWaterBuoyancyComponent.waterSurfaceNormal = normal;


                undispPos = (Vector3)position - _displacementToObject;
                undispPos.y = OceanRenderer.Instance.SeaLevel;
                dispPos = undispPos + _displacementToObject;

                //Get Bottom Of Object
                bottomDepth = dispPos.y - position.y + actorActionWaterBuoyancyComponent.buoyancyBottomHeight;
                actorActionWaterBuoyancyComponent.bottomDepth = bottomDepth;

                actorActionWaterBuoyancyComponent.disabled = bottomDepth < 0;

                sampleHeightLengthHelpers[entity.Index].Init(position, actorActionWaterBuoyancyComponent.objectLength);
                var dummy = 0f;
                var normalLongitudinal = Vector3.up;

                if (sampleHeightLengthHelpers[entity.Index].Sample(ref dummy, ref normalLongitudinal))
                {
                    var F = (Vector3)localToWorld.Forward;
                    F.y = 0f;
                    F.Normalize();
                    normal -= Vector3.Dot(F, normal) * F;

                    var R = (Vector3)localToWorld.Right;
                    R.y = 0f;
                    R.Normalize();
                    normalLongitudinal -= Vector3.Dot(R, normalLongitudinal) * R;

                    actorActionWaterBuoyancyComponent.normalLongitudinal = normalLongitudinal;
                }
            });
        }

        private void DoBuoyancy()
        {
            var forces = new List<float3>();
            var physicsWorld = World.GetExistingSystem<Unity.Physics.Systems.BuildPhysicsWorld>().PhysicsWorld;
            Entities.ForEach((Entity entity, ref ActorActionWaterBuoyancySimpleComponent actorActionWaterBuoyancyComponent, ref Translation translation, ref Rotation rotation, ref LocalToWorld localToWorld, ref PhysicsVelocity physicsVelocity, ref PhysicsMass physicsMass) =>
            {
                if (actorActionWaterBuoyancyComponent.bottomDepth > 0 && !actorActionWaterBuoyancyComponent.disabled)
                {


                    //Buoyancy
                    var bottomDepth = actorActionWaterBuoyancyComponent.bottomDepth;
                    var buoyancy = -Physics.gravity.normalized * actorActionWaterBuoyancyComponent.buoyancyCoeff * bottomDepth * bottomDepth * bottomDepth;
                    physicsVelocity.Linear += (float3)buoyancy * deltaTime;

                    //Get Forces
                    var velocityRelativeToWater = physicsVelocity.Linear - actorActionWaterBuoyancyComponent.waterSurfaceVelocity;
                    var forcePosition = translation.Value + actorActionWaterBuoyancyComponent.forceHeightOffset * (float3)Vector3.up;
                    forces.Add(Vector3.up * Vector3.Dot(Vector3.up, -velocityRelativeToWater) * actorActionWaterBuoyancyComponent.dragInWaterUp);
                    forces.Add(localToWorld.Right * Vector3.Dot(localToWorld.Right, -velocityRelativeToWater) * actorActionWaterBuoyancyComponent.dragInWaterRight);
                    forces.Add(localToWorld.Forward * Vector3.Dot(localToWorld.Forward, -velocityRelativeToWater) * actorActionWaterBuoyancyComponent.dragInWaterForward);

                    //Apply Forces
                    for (int i = 0; i < forces.Count; i++)
                        physicsVelocity.ApplyImpulse(physicsMass, translation, rotation, forces[i], forcePosition);

                    //Torque
                    {
                        var torqueWidth = (float3)Vector3.Cross(localToWorld.Up, actorActionWaterBuoyancyComponent.waterSurfaceNormal);
                        var torqueLength = (float3)Vector3.Cross(localToWorld.Up, actorActionWaterBuoyancyComponent.normalLongitudinal);

                        physicsVelocity.Angular += torqueWidth * actorActionWaterBuoyancyComponent.boyancyTorque * deltaTime;
                        physicsVelocity.Angular += torqueLength * actorActionWaterBuoyancyComponent.boyancyTorque * deltaTime;

                    }
                }
            });
        }
    }
}
using System;
using System.ComponentModel;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;

namespace DOTSActorWater
{
    [Serializable]
    public struct ActorActionWaterBuoyancySimpleComponent : IComponentData
    {
        //Main Data
        public float buoyancyBottomHeight;
        public float buoyancyCoeff;
        public float boyancyTorque;

        public float objectWidth;
        public float objectLength;

        public float forceHeightOffset;
        public float dragInWaterUp;
        public float dragInWaterRight;
        public float dragInWaterForward;
        public float dragInWaterRotational;

        //Write Data
        public float3 inverseInertia;
        public float3 waterSurfaceVelocity;
        public float3 waterSurfaceNormal;
        public float3 normalLongitudinal;
        public float bottomDepth;
        public bool disabled;
    }
}
using Unity.Entities;
using Unity.Transforms;
using UnityEngine;

namespace DOTSActorWater
{
    public class ActorWaterBuyancySimpleAuthor : MonoBehaviour, IConvertGameObjectToEntity
    {
        [Header("Buoyancy")]
        public float buoyancyBottomHeight = -1.3f;
        public float buoyancyCoeff = 1.5f;
        public float boyancyTorque = 8f;

        [Header("Wave Response")]
        public float objectWidth = 4.5f;
        public float objectLength = 2.25f;

        [Header("Fore | Drag")]
        public float forceHeightOffset = -0.3f;
        public float dragInWaterUp = 3f;
        public float dragInWaterRight = 2f;
        public float dragInWaterForward = 1f;
        public float dragInWaterRotational = 0.2f;

        [Header("Physics Body")]
        public Vector3 inverseInertia;

        public void Convert(Entity entity, EntityManager entityManager, GameObjectConversionSystem conversionSystem)
        {
            entityManager.AddComponentData(entity, new ActorActionWaterBuoyancySimpleComponent()
            {
                buoyancyBottomHeight = buoyancyBottomHeight,
                buoyancyCoeff = buoyancyCoeff,
                boyancyTorque = boyancyTorque,
                objectWidth = objectWidth,
                objectLength = objectLength,
                forceHeightOffset = forceHeightOffset,
                dragInWaterUp = dragInWaterUp,
                dragInWaterRight = dragInWaterRight,
                dragInWaterForward = dragInWaterForward,
                dragInWaterRotational = dragInWaterRotational,
                inverseInertia = inverseInertia
            });

            entityManager.SetComponentData(entity, new Translation { Value = transform.position });
        }

        public void OnDrawGizmos()
        {
        }
    }
}