DiscoverLearnDocumentationGet OpenPLXSearch Contact

Physics3D

The Physics3D bundle is a collection of template models that should be considered a common ground for all modelling of domain specific engineering physics. Types like Body, RigidBody are intended to be extended to links and joints for robotics, booms and hydraulic cylinders or tires and suspensions for vehicle modelling. Mate interactions like the Hinge and the Prismatic enable modeling of mechanical systems. A physical System may be automatically assembled with only bodies and mate definitions using the built in SNAP algorithm.

Mates

A Mate is an interaction that connects two rigid bodies. Physics3D include an automatic assembly feature called SNAP, which is operating on Mates, doing what is possible to assemble all implicitly defined relative positions for the bodies of a system given the Mates. A mate require two MateConnector’s, one for each body, which are body relative coordinate systems. A Mate should be considered at rest when the two coordinate systems overlap, which is what the purpose of SNAP is to fulfil.

Component based modeling

With OpenPLX Physics3D we address the problem of building advanced mechanical systems with as few as possible “hardcoded” internal positions and rotations of the bodies. Instead of defining them manually we let SNAP calculate the undefined positions and rotations. However, it is yet a challenge to define the building blocks of a mechanical system so that they resemble their real world counterpart. To define the body relative MateConnector position and rotation we let the user define the position as a Vec3, and the direction as two Vec3s, main_axis and normal. The main_axis is required and should be used as the hinge axis for a hinge or the normal axis of a planar joint. The normal is intended to be orthogonal to the main_axis, and if it is not, only the orthogonal part will be considered. By default OpenPLX will compute a normal orthogonal to the main_axis, and if the user specifies a normal vector parallel to the main axis, an error is reported.

Clock Example

Lets look at an example of modelling a clock, with just the clock-face and one hand. We like to think about the two object as products of two completely different suppliers, but yet we like them to have a common ground to assemble their two digital twins of their respective product. In the real world we would see that we for example can mount the hand on a small pin or screw.

Clock modelling example

The OpenPLX.Physics3D both models have one MateConnector each, which are the keys for defining the assembled state. On the clock the position of the MateConnector is in the center, but probably a distance above the face. On the hand it needs to be defined on the point where it is supposed to rotate. To define a positive angle as a rotation of the hand as forward in time, according to the right hand rule, we let the main_axis point into the clock. The hand also have the main_axis defined as a normal to its back side so that the front side faces the person or camera looking at the watch. Now the hand will rotate as we like in the clock-face plane.

Clock modelling example with Mate Connectors

But we also like to have control of where the hand is pointing, and therefore we also define the direction toward twelve a clock, as well as the local direction along the hand. Given these two local directions for the two objects, we have defined what is required to model the clock. This differ a lot from modeling with explicit positions of the objects, where the user has to hard code a relative position and rotation, and thereby define one initial condition which is quite intricate to change. Using the two local vectors is more obvious and straight forward. It also opens up for initial conditions of the rotation of the hand around the clock-face normal axis, enabling scalar angle values to put the hand in desired conditions.

SNAP

SNAP will compute local transforms for Body’s and System’s given Mate’s connecting bodies together, or to the world. SNAP will NOT solve ambiguities. And if there are redundant variables SNAP will force them to be the identity when necessary.

Three level SNAP example

Consider the snap_parent.openplx file below

snap_parent.openplx ```js Rod is Physics3D.Bodies.RigidBody: inertia.mass: 10 geometry is Physics3D.Charges.Box: size: Math.Vec3.from_xyz(0.1, 0.1, 1) arrow is Physics3D.Charges.Box: local_transform: position.z: 0.5 rotation: Math.Quat.angleAxis(Math.PI / 4, Math.Vec3.Y_AXIS()) size: Math.Vec3.from_xyz(0.071, 0.1, 0.071) connector is Physics3D.Charges.MateConnector: position.z: -geometry.size.z * 0.6 main_axis: Math.Vec3.Y_AXIS() normal: Math.Vec3.Z_AXIS() RodParent is Physics3D.System: rod is Rod RodParentParent is Physics3D.System: rod_system is RodParent World is Physics3D.System: world_connector is Physics3D.Charges.MateConnector: position.x: 0.5 main_axis: Math.Vec3.Y_AXIS() normal: Math.Vec3.X_AXIS() rod_system is RodParentParent world_lock is Physics3D.Interactions.Lock: charges: [rod_system.rod_system.rod.connector, world_connector] SnapRod is World: rod_system.local_transform.rotation: Math.Quat.fromTo(Math.Vec3.from_xyz(1,0,0), Math.Vec3.from_xyz(1,1,1).normal()) rod_system.rod_system.local_transform.rotation: Math.Quat.fromTo(Math.Vec3.from_xyz(0,1,0), Math.Vec3.from_xyz(1,1,1).normal()) SnapRodParent is World: rod_system.local_transform.rotation: Math.Quat.fromTo(Math.Vec3.from_xyz(1,0,0), Math.Vec3.from_xyz(1,1,1).normal()) rod_system.rod_system.rod.kinematics.local_transform.rotation: Math.Quat.fromTo(Math.Vec3.from_xyz(0,1,0), Math.Vec3.from_xyz(1,1,1).normal()) SnapRodParentParent is World: rod_system.rod_system.local_transform.rotation: Math.Quat.fromTo(Math.Vec3.from_xyz(1,0,0), Math.Vec3.from_xyz(1,1,1).normal()) rod_system.rod_system.rod.kinematics.local_transform.rotation: Math.Quat.fromTo(Math.Vec3.from_xyz(0,1,0), Math.Vec3.from_xyz(1,1,1).normal()) ```

There is one Rod model, a RigidBody defined in RodParent a System. To make things a little more complicated, there is a parent system to the RodParent, named RodParentParent. The file contains three different scenarios for SNAP to handle. There is one body with one MateConnector to position at the world_connector, but three transforms to specify. The user either need to specify the redundant ones, or SNAP will force two of them to be identity transforms. In this case, we like to point out the difference in computations for the three variants.

  • SNAP computes the RodParentParent local_transform given that RodParent and Rod local_transforms are specified (not default).
  • SNAP computes the RodParent local_transform given that RodParentParent and Rod local_transforms are specified (not default).
  • SNAP computes the Rod local_transform given that RodParentParent and RodParent local_transforms are specified (not default).

To position the Rod body at the world_connector the following affine matrix equation must be satisfied.

$A = S_1 S_2 R M$

Where A is the world_connector transform. $S_1$ is the local transform of RodParentParent relative to it´s parent (world frame). $S_2$ is the local transform of RodParent relative to $S_1$, R is the local transform of Rod relative $S_2$. $M$ is the local transform of the connector of the Rod relative the $Rod$.

When leaving the computation to Snap, it is undefined which of the three ($S_1$, $S_2$ and $R$) that will be computed and which will become the identity.

Here follows the computation for $S_1$, $S_2$ and $R$.

$S_1 = A M^{-1} R^{-1} S_2^{-1} $

$S_2 = S_1^{-1}AM^{-1} R^{-1}$

$R = S_2^{-1}S_1^{-1}AM^{-1}$

Generic transform computation

Consider the example above for computing $S_1$, SnapRodParent example. It illustrates well how the generic snap computation works. The tree structure of transforms are visualized in the transform graph below, where each node is a SnapFrame containing a local transform. Ancestor is the SnapFrame corresponding to the closest common ancestor (a System) to $M_f$ and $M_t$.

M stand for MateConnector R stand for RigidBody S stand for System

Snap hierarchial transforms

The world_lock is a Mate and points to two mate connectors rod.connector and world_connector. $M_t$ (world_connector) is where we want to snap $M_f$, ie we are “snapping from $M_f$ to $M_t$”. The algorithm has identified $S_2$ as the target SnapFrame world_lock Mate.

<ancestor>_<child>_reduced means that we have a combined transform from an upper coordinate system to a child coordinate system.

The generic formula for computing the target local transform $S$ is derived from

$NSP = A$

meaning the transforms $NSP$ must be equivalent to $A$ in order for the SnapFrames to align. We need to break out $S$ which is the only unknown:

$S = N^{-1}AP^{-1}$

Hereafter referred to as the SNAP transform.

Algorithm

Intuitively SNAP is easy to understand. It assembles the mechanical system as a human would assemble a set of lego pieces. Starting from one piece and from there add more and more pieces until the model is complete.

For a mechanical system declared with mates, bodies and systems in the OpenPLX Physics3D bundle, it might be hard to visualize weather the model is possible to assemble at all. There might be pieces that

  1. two different mates are trying to SNAP in ambiguous ways.
  2. are part of systems that have redundant transforms.
  3. are not declared to any mate.

SNAP will try to do the best it can in those scenarios. The purpose of SNAP is to find a local transform for all systems and bodies. SNAP will only place pieces using mates when the result is not redundant. SNAP will find redundant transforms, and force them to be the identity transform.

The aim of anyone relying on SNAP should be that if the model would be a real world model, it would be possible to assemble, just like lego.

To follow the steps done by SNAP you may add --loglevel trace to your commandline.

Below follows pseudo-code for SNAP:

S ← Set of all mates
D ← Degrees of freedom left from mates being snapped
A, B ← mate connectors
C ← A coordinate system that has an undefined local transform relative its parent
Snap(A, B, C) ← Function that computes a local transform for C which moves A to B, using the SNAP transform.


while there are unused mate's in S:

    N = current_num_used_mates(S)

    for each mate M with mate connectors A and B:
        G ← closest common ancestor of A and B
        [AG[ ← sequence of local transforms from A to G
        [BG[ ← sequence of local transforms from B to G

        if num_undefined_transforms([AG[) == 1 && num_undefined_transforms([BG[) == 0:
            C ← get_first_undefined_transform([AG[)
            T ← Snap(A, B, C)
            C.set_local_transform(T)
            D.insert(generate_degrees_of_freedom(M, C))
        else if num_undefined_transforms([AG[) == 0 && num_undefined_transforms([BG[) == 1:
            C ← get_first_undefined_transform([BG[)
            T ← Snap(A, B, C)
            C.set_local_transform(T)
            D.insert(generate_degrees_of_freedom(M, C))
        else if num_undefined_transforms([AG[) == 0 && num_undefined_transforms([BG[) == 0:
            T ← find_solution_using_degrees_of_freedom(M, D)


    # No frame was positioned in the previous loop
    # SNAP will force a transform to be the identity

    num_dof ← current_num_unused_degrees_of_freedom(D)

    if N == current_num_used_mates(S):
        if num_dof > 0:
            kill_first_degree_of_freedom(D)
            continue    # Now try all mates again, might have got rid of a ambiguity

        M ← first_unused_mate_that_with_undefined_transform_chain(S)
        A, B ← get_mate_connectors(M)
        G ← closest common ancestor of A and B
        [AG[ ← sequence of local transforms from A to G
        [BG[ ← sequence of local transforms from B to G
        if num_undefined_transforms([AG[) > 0:
            C ← get_first_undefined_transform([AG[)
            C.set_local_transform(I)
            continue   # Now try all mates again, might have got rid of a ambiguity
        else if num_undefined_transforms([BG[) > 0:
            C ← get_first_undefined_transform([BG[)
            C.set_local_transform(I)
            continue    # Now try all mates again, might have got rid of a ambiguity
        else:
            # ERROR
            This mate M did not have an undefined transform chain

    break the while loop