Appearance
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 1a.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 frameVector3.lerpfor 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 orientationUse 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'sworldPosition, 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:
entity.transform– relative to its parent (its local transform)entity.worldTransform– its final place in the scene, with all parents applied
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 → worldtransformPointmoves a position (translation included).transformVectormoves a direction (translation ignored, so it stays a direction) – this is why the dot-product example built the camera's forward withtransformVector.multiplychains two transforms;inversereverses 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.