So you want to move stuff. Lets define what stuff can be moved, there are two types of objects in Sansar that can be scripted to move, physical objects and non physical objects.

Physical objects

Physical objects are are objects that have collision, you will know if they have collision by looking at the object Properties, you will see a section for Volume as well as setting for Motion Type, Friction and Bounce

Object structure

Motion Type Property

The motion type property is extremely important when it comes to physics objects and it greatly affects how the object behaves and what can be done to the object from the script API.

The Static motion type indicates that the object will never be moving. This is considered the most restrictive motion type. In fact static objects built into the scene are assumed never to move and are potentially optimized by the build process, so script manipulation of static objects is not possible.

The Keyframed motion type indicates that the object will only move when explicitly moved from script. This is the next most restrictive motion type. When keyframed objects are moved, they will not stop or otherwise be affected by any other collisions. They will simply move through everything and push avatars and dynamic objects out of the way.

The Dynamic motion type indicates that the object will be subject to gravity and other phsyical interactions. This is the least restrictive motion type. Dynamic objects will fall, roll and slide depending on what forces get applied to them in the scene. They will collide with static and keyframed objects and avatars.

Motion types can be changed from script but only to a more restrictive motion type than the initial import or scene settings allow. So for example, an object imported as Dynamic can be set to Keyframed from script but an object imported as Static cannot be set to Keyframed or Dynamic.

Non physical objects

Objects that have no collision are considered non-physical objects in Sansar. These can be moved with the Mover API if they are configured to be allowed to move. Much like Static objects above, objects that are not configured for movement are assumed never to move and are potentially optimized by the build process.

To configure an object for movement, set the Movable From Script and the IsScriptable attributes set to On.

Object structure

Mover API

The Mover API can drive non-physical objects as well as physical objects that have their Motion Type set to Keyframed as well as Movable From Script toggle On.

Properly configured objects can then be immediately moved using any of these functions, where position is a Sansar.Vector and rotation is a Sansar.Quaternion.

ObjectPrivate.Mover.AddMove(position, rotation);
ObjectPrivate.Mover.AddTranslate(position);
ObjectPrivate.Mover.AddRotate(rotation);

You may also use these interfaces to control the movement over time using additional parameters specifying length of time in seconds and MoveMode, EaseIn, EaseOut, Linear and SmoothStep.

ObjectPrivate.Mover.AddMove(position, rotation, seconds, moveMode);
ObjectPrivate.Mover.AddTranslate(position, seconds, moveMode);
ObjectPrivate.Mover.AddRotate(rotation, seconds, moveMode);

Mover functions act like a queue and execute commands sequentially. In this way it is possible to make a simple behavior to move an object through a set of points as shown in the Patrol Mover sample script snippet below.

ObjectPrivate.Mover.AddTranslate(point1, 5.0, MoveMode.Linear);
ObjectPrivate.Mover.AddTranslate(point2, 5.0, MoveMode.Linear);
ObjectPrivate.Mover.AddTranslate(point3, 5.0, MoveMode.Linear);
ObjectPrivate.Mover.AddTranslate(point4, 5.0, MoveMode.Linear);

Utility functions like WaitFor, AddPause and StopAndClear can be used to control movement commands executions for finer control over movement.

Vectors

Sansar.Vector struct represents a three dimensional vector using the standard Cartesian coordinate system of X,Y,Z. The Vector class provides three interfaces for supplying coordinate values but the most common is in the signature of Vector (float X, float Y, float Z, float W). The W coordinate should not be needed in most scripting use cases, it’s used to compute matrix mathematics and indicates a position or transform, it defaults to 0 and is assumed to be 0 for most operations if it is not specified, you can omit it entirely.

3D Vector

Lets look at the Patrol Mover sample script to see an example of how we can use vectors in our scripts to move an object along the vector axis’.

Start by creating a new scene and choosing the Base Template Scene.

Base Template Scene

Notice the scene is blank with nothing but a spawn point. In the Settings for the spawn point set the position X = 0, Y = -0.5, Z = 0 this will place the tip of the spawn point, making it look like an arrow pointing, at the coordinates 0,0,0. From above it should look like a piece of graph paper where the grid lines indicate each square area of the X and Y axis’.

Base Template Spawn

Next drag an object to animate into the scene, I am using a free Wrasse Fish non physical object in this example as it has a distinct head and tail which will be important in the next section when we discuss rotation.

For now drag your object in front of the spawn point and set it coordinates at Position X = 1, Y = 1, Z = 0 so that you are to the front right of the scene center. You’ll also want to turn on the Movable from Script and IsScriptable toggles.

Base Template Spawn

Import the Patrol Mover sample script and attach it to the fish. It will provide four inputs to create an area to patrol. Lets keep it simple and input the coordinates for four points of a single cell square.

Patrol Mover Settings

Build and visit the scene to see the fish is indeed moving around the square, but fish don’t move like that, so we’re going to have add some rotations so that the fish swims in the direction its head is facing, like irl.

Click to see video on YouTube : Fish video

Go back to your scene and remove the Patrol Mover script from the fish. Import the Patrol Turn Mover sample script and add to the fish.

Lets look at what makes this script different. For starters the public fields are expanded to allow more than a four point patrol if you desire. There is also a field called WorldObjectForward which is defining the vector to which the object faces forward, in our case our fish is facing forward to the left, which if you were to translate the fish from vector 0,0,0 forward by one unit it would be moving to vector -1,0,0.

    public List<Vector> PatrolPoints;

    [DefaultValue(1.0f)]
    public float MoveSpeed;

    [DefaultValue("<0,1,0>")]
    public Vector WorldObjectForward;

In the coroutine we see that a variable is set for the Next patrol vector called toNext and a rotation is calculated using the WorldObjectForward and the toNext vectors via a Quaternion method called ShortestRotation which as it’s name states will generates the shortest rotation quaternion to rotate from one vector to another.

Input the following settings, build and visit.

Patrol Turn Mover Settings

Did you see what I saw? The fish patrols the vectors but seems to disappear out of view for a section of the patrol and then reappear to continue its way.

Click to see video on YouTube : Fish Turning video

Before we answer what happened to the fish I think we need to take a moment to talk about quaternions and then we can solve the mystery of the disappearing fish.

Quaternions

Sansar.Quaternion struct represents a quaternion orientation. Quaternions are not a light subject this tutorial cannot provide a lesson on quaternion math but instead make you familiar with the concepts and the usage of the Sansar.Quaternion class and it’s methods in Sansar scripting.

The most common uses for your scripts will be to rotate an object from one point to another based on an angle. You might think to yourself, isn’t rotation by angle enough, why complicate it with Quaternions? The answer has to do with optimizations and to avoid unexpected behaviors such as “Gimble Lock”.

Let’s watch a quick video describing at a high level why Quaternions are used.

And one more video to visualize rotation with Euler Angles and the problem of Gimble Lock.

If you are looking for a lesson on quaternion math this video does a great job explaining the math starting from how 2D complex numbers can be used to represent 2D rotation (and scale) in real space and why multiplying two 2D complex numbers together results in a 2D rotational transform, and then extending that logic to show how 4D numbers result in calculation of rotation in 3D space.

Don’t feel you have to understand the math to understand how to use the Quaternion class, its perfectly fine if you do not. The scripting API provides methods to convert angles to quaternions and use them for rotations using the Mover API. There are more resources for the curious below in resources section but for now we’re going to move back into rotation of objects in Sansar Namespace and the mystery of the disappearing fish.

In my humble opinion the best way to gain insight to what is happening with your script is to add debug logs at each critical computation to inspect exactly what values are being used. This is what that looks like for the Patrol Turn Mover script.

 while (true)
    {
        // Calculate direction to next patrol point
        Vector toNext = PatrolPoints[next] - PatrolPoints[current];

        Log.Write("Calculate direction to next patrol point " + toNext);

        // Compute a world space rotation for this object to point at the next patrol point
        Quaternion rotation = Quaternion.ShortestRotation(WorldObjectForward, toNext.Normalized());

        Log.Write("Compute a world space rotation for this object to point at the next patrol point " + rotation);
        
        ObjectPrivate.Mover.AddRotate(rotation);  // Immediately turn to face

        // Compute the time based on the distance and move speed
        double moveTime = toNext.Length() / MoveSpeed;

        Log.Write("Compute the time based on the distance and move speed " + moveTime);

        // Move the object to the next patrol point
        WaitFor(ObjectPrivate.Mover.AddTranslate, PatrolPoints[next], moveTime, MoveMode.Linear);

        Log.Write("Move the object to the next patrol point " +  PatrolPoints[next]);

        // Increment to the next patrol point
        current = next;
        next = (next + 1) % PatrolPoints.Count;

    }

Now when I visit the scene and ctrl + d to open the debug panel I can see that the rotation value at the offending turn, outlined in red, has a vast difference in its value for the rotation than the others, outlined in yellow.

Rotation Debug

I wasn’t certain why this was happening and I asked one of the script team members what was going on, and it was discovered that the axis for the fish was situated lower to the floor in the fish giving its center an offset. When using ShortestRotation method using only the two vectors can potentially give any spin, rotation around the axis in the direction it is facing, since it is only using two vectors it doesn’t know what spin to use. So what happens then it is the fish’s origin has an offset from its center which is then flipped upside down because that is computationally the shortest rotation between the two vectors, the fish continues to move underground out of view, and then flip back again to continue on its way. There might be more than one possible solution to this, fixing the center origin of the fish to remove the offset would likely be the easiest, but if that is not possible there are other ways to get the rotation value for the fish.

Update : This fish was further discussed in the August 30th event during a mob programming session where we modified the MoverExample3: aka, spinning scared robot, an interaction script that makes a robot flee away and rotate randomly when you interact with it, to make the fish turn 90 degrees to the left and swim away from you.

The first thing we did was to remove the Spin method and SpinSpeed property associated with it. Next we remove the fleeing away code at line 72:

WaitFor(ObjectPrivate.Mover.AddTranslate, ObjectPrivate.Position + fromAgentToObject * FleeDistance, FleeSeconds, MoveMode.EaseOut);

And replace it with

Quaternion rot = ObjectPrivate.Rotation;
Quaternion stepRot = Quaternion.FromEulerAngles(new Vector(0,0,(float)Math.PI/2f));
rot *= stepRot;
ObjectPrivate.Mover.AddRotate(rot);
ObjectPrivate.Mover.AddTranslate(ObjectPrivate.Position + Vector.ObjectLeft.Rotate(rot), 2.0, MoveMode.Linear);

Stepping through:

  • the first line obtains the fish’s world rotation value
  • the second line obtains the 90 degree rotation of the Z axis, the axis which we wish to rotate the fish on
  • the third line multiplies the two quaternion values to obtain the final rotation value
  • the fourth line applies the rotation using the Mover API
  • the fifth line translates the fish forward using the Object’s position plus the left rotation quaternion which we have calculated, at a time of 2 seconds with a linear move mode.

Special thanks to MadEye and EvoAvo for coming up with the solution to this exercise, final script can be found here

Resources

A handy site for visualizing/converting quaternions and euler angles quaternions.online

An in depth series of interactive video lessons called Visualizing Quaternions

OpenGL tutorial on rotations.