Skip to content

Vectors & Transforms

Positions, directions, and rotations are the raw material of anything spatial – aiming an object at the viewer, checking how far away something is, moving smoothly, turning a hand position into an object's own space. This page is a tour of the Vector3, Rotation, and Transform helpers, focused less on the maths and more on what each one is for.

Points and directions

A Vector3 is just three numbers, but it plays two roles:

  • a point – a position in space (an object's position, a hand joint, a raycast hit)
  • a direction – which way something points, usually with a length of 1

The direction from one point to another is their difference:

javascript
var toTarget = target.subtract(myPosition);   // points from me toward the target
var distance = toTarget.length();              // how far away the target is
var heading  = toTarget.normalize();           // same direction, length 1

a.distanceTo(b) is a shortcut for b.subtract(a).length() when you only need the distance.

What normalize() is for: it scales a vector to length 1 while keeping its direction, so you're left with pure "which way" and no "how far". You want it whenever direction is what matters but magnitude shouldn't leak in – moving at a constant speed, aiming, or feeding a direction into a dot product (below), which only reads as an angle when its inputs are unit length.

TIP

Normalize a direction before you use it to move or aim. A raw direction whose length happens to be, say, 3 would move an object 3× too fast – speed would depend on how far the target is, which is almost never what you want.

Move an object a fixed speed toward a target each frame:

javascript
// Attach to: On Render
var dt   = scriptContext.sourceEvent.deltaTime;             // seconds since last frame
var step = target.subtract(obj.position).normalize().multiply(2.0 * dt);   // 2 m/s
obj.position = obj.position.add(step);

Dot product – how aligned are two directions?

a.dot(b) measures how much two directions point the same way. For unit vectors it is the cosine of the angle between them: 1 means the same direction, 0 perpendicular, -1 opposite.

That makes it the go-to test for "is this in front of me?" and "is the viewer looking at it?":

javascript
// Attach to: On Render
var cam     = scene.cameraTransform;
var forward = cam.transformVector(Vector3(0, 0, -1));               // where the camera looks
var toObj   = obj.representation.worldPosition.subtract(cam.position).normalize();

if (forward.dot(toObj) > 0.97) {   // 0.97 ≈ within ~14° of dead-on; a smaller number = wider cone
    // the viewer is looking almost straight at the object
}

Need the actual angle instead of a threshold? a.angleTo(b) returns it in radians.

Cross product – give me a perpendicular

a.cross(b) returns a vector at right angles to both a and b. You reach for it when you need an axis you don't already have:

  • a surface normal – the direction a surface faces – from two of its edges: edge1.cross(edge2). Normals are what lighting uses to shade a surface, so this comes up when you build or deform a mesh.
  • a "right" direction from a forward and an up vector: forward.cross(up) – useful for placing something beside the viewer or moving sideways.

This one is more advanced – if you're mostly moving and aiming objects, you may not need it yet.

Smooth motion – lerp and slerp

Snapping straight to a target reads as robotic; easing toward it feels alive. lerp blends between two values by a fraction t (0 = start, 1 = end):

javascript
// Attach to: On Render — smooth follow
obj.position = obj.position.lerp(target, 0.1);   // moves 10% of the remaining distance each frame
  • Vector3.lerp for positions, Math.lerp(a, b, t) for plain numbers.
  • Rotations use rotation.slerp(other, t) – the shortest smooth turn between two orientations.

Rotations

A Rotation orients something. Build one from angles (in radians), then combine, undo, or blend:

javascript
var quarterTurn = Rotation(0, Math.toRadians(90), 0);   // 90° around the up (Y) axis
var combined    = rotA.multiply(rotB);                  // apply B, then A
var undo        = rot.inverse();                        // the opposite rotation
var eased       = current.slerp(target, 0.2);           // smooth turn toward another orientation

Use Math.toRadians() / Math.toDegrees() to convert. To turn an object to face a point (a "look-at"), see the billboard example in Examples.

Coordinate spaces

The same point can be described from different frames of reference, and mixing them up is a common source of "why is it in the wrong place" bugs:

  • World space – the one frame shared by everything, so any two things can be compared in it. The values you read are almost always world-space: hand joints (scene.tracking.hands), raycast hits, an object's worldPosition, the camera's position.
  • Local space – each object has its own, relative to its transform. A mesh's vertices, and an object's position, live in that object's local frame. (The camera has its own local frame too – "camera space".)

So "local" isn't one place – it's per-object. Moving a point from one frame into another is exactly what a Transform does.

Transforms

A Transform bundles position, rotation, and scale – it is how one object sits in space, i.e. where its local frame lands in the world. Every entity has one:

Use it to move points and directions between the world and an object's local space:

javascript
var inv   = Transform.inverse(entity.worldTransform);
var local = Transform.transformPoint(inv, worldPoint);          // world → this entity's local frame
var world = entity.worldTransform.transformPoint(localPoint);   // local → world
  • transformPoint moves a position (translation included).
  • transformVector moves a direction (translation ignored, so it stays a direction) – this is why the dot-product example built the camera's forward with transformVector.
  • multiply chains two transforms; inverse reverses one.

A hand joint feeding a mesh you built is the classic case: the joint is world-space, the mesh's vertices are the entity's local space – convert the joint into the entity's local frame first.

In practice you rarely convert by hand, because entities hand you both frames directly: a representation's position is its local position, its worldPosition is the world one, and you can read or set either. Manual transformPoint conversion is for a loose point that isn't already an entity property – a hand joint, a raycast hit, a mesh vertex.

TIP

An object at the scene's origin has local space equal to world space, so no conversion is needed. It starts to matter once the object – or the experience origin it sits under – is moved or rotated.