In imperative/object oriented programming with mutable state, it would be very common and useful to declare a structure such as the following:
struct RigidBody {
float m_mass;
float m_inverseMass;
Mat3 m_localInverseInertiaTensor;
Mat3 m_globalInverseInertiaTensor;
Vec3 m_globalCentroid;
Vec3 m_localCentroid;
Vec3 m_position;
Mat3 m_orientation;
Vec3 m_linearVelocity;
Vec3 m_angularVelocity;
};
Source: http://allenchou.net/2013/12/game-physics-motion-dynamics-implementations/
There are many properties here that are able to be computed directly from others, such as m_inverseMass
from m_mass
. In a stateless programming language like Haskell, getting derived values is easy enough:
data RigidBody = RigidBody {mass :: Float}
inverseMass :: RigidBody -> Float
inverseMass body = 1 / mass body
But this computes the inverseMass
every time we need it, which can get expensive especially in domains where performance is critical, like physics simulation. I've considered memoization, but I wasn't sure if this is a good way of expressing this lazy evaluation of dependent properties, as it seemed to be a complicated solution. How would I store derivative values without having to recompute them?
As @4castle and @Shersh note, a simple approach would be to include the derived value in the data type:
data RigidBody = RigidBody
{ m_mass :: Float
, m_inverseMass :: Float }
and then use a smart constructor to make new RigidBody
s:
rigidBody mass = RigidBody mass (1/mass)
The expression 1/mass
will create a thunk for m_inverseMass
which, after it is first evaluated, will be available without recalculation, so it provides a sort of auto memoization.
More general transformations, like changing the position and properly updating all the global*
fields based on the local*
values would be handled in a similar manner. As a simplified example:
module Rigid where
type Vec3 = Double -- just to type check
data RigidBody = RigidBody
{ m_mass :: Float
, m_inverseMass :: Float
, m_pos :: Vec3
, m_localCentroid :: Vec3
, m_globalCentroid :: Vec3
}
rigidBody mass pos centroid =
RigidBody mass (1/mass) pos centroid (centroid + pos)
move body delta =
rigidBody (m_mass body)
(m_pos body + delta)
(m_localCentroid body)
In an application that's performance critical, you would want to take steps to introduce strictness in appropriate places so you don't build up huge piles of unevaluated thunks.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With