lens-studio-world-query
Lens Studio World Query — Reference Guide
"World query" covers two related capabilities: detecting real-world surfaces (using the device's depth and mesh data) and raycasting against scene geometry (using physics). Most Spectacles lenses need at least one of these.
World Query Module (Real Surfaces)
WorldQueryModule lets you cast a ray and find where it hits real-world surfaces recognised by the Spectacles depth sensor.
const WorldQueryModule = require('LensStudio:WorldQueryModule')
Creating a hit test session
onAwake(): void {
const options = HitTestSessionOptions.create()
options.filter = true // smooth / filter jitter (recommended)
this.hitTestSession = WorldQueryModule.createHitTestSessionWithOptions(options)
}
Performing a hit test
// rayStart and rayEnd are world-space vec3 positions
this.hitTestSession.hitTest(rayStart, rayEnd, (result) => {
if (result === null) {
// No surface found along the ray
indicator.enabled = false
return
}
// result.position — world position of the hit point
// result.normal — surface normal at the hit point
const hitPos = result.position
const hitNormal = result.normal
indicator.getTransform().setWorldPosition(hitPos)
indicator.getTransform().setWorldRotation(
quat.lookAt(hitNormal.cross(vec3.up()), hitNormal)
)
indicator.enabled = true
})
Typical hit test using SIK targeting interactor (Spectacles)
import { InteractorInputType } from 'SpectaclesInteractionKit.lspkg/Core/Interactor/Interactor'
import { SIK } from 'SpectaclesInteractionKit.lspkg/SIK'
this.createEvent('UpdateEvent').bind(() => {
// Get the currently active targeting interactor (the hand pointing at something)
const primaryInteractor = SIK.InteractionManager
.getTargetingInteractors()
.shift()
if (primaryInteractor && primaryInteractor.isActive() && primaryInteractor.isTargeting()) {
const rayStart = primaryInteractor.startPoint
const rayEnd = primaryInteractor.endPoint
this.hitTestSession.hitTest(rayStart, rayEnd, (result) => {
if (result) this.placeObject(result.position, result.normal)
})
}
})
Typical hit test in UpdateEvent (camera gaze, phone or Spectacles)
this.createEvent('UpdateEvent').bind(() => {
const origin = cameraTransform.getWorldPosition()
const forward = cameraTransform.forward
const rayEnd = origin.add(forward.uniformScale(5)) // 5-metre ray
this.hitTestSession.hitTest(origin, rayEnd, (result) => {
if (result) this.placeObject(result.position, result.normal)
})
})
Semantic Hit Testing (Spectacles only)
Semantic hit testing classifies the surface type at the hit point — useful for only placing content on floors, tables, or walls.
onAwake(): void {
const options = HitTestSessionOptions.create()
options.filter = true
options.surfaceClassification = true // enable surface type detection
this.hitTestSession = WorldQueryModule.createHitTestSessionWithOptions(options)
}
// In the hit callback:
this.hitTestSession.hitTest(rayStart, rayEnd, (result) => {
if (!result) return
// result.classification — the detected surface type
switch (result.classification) {
case HitTestSessionOptions.SurfaceClassification.Floor:
print('Hit floor — safe to place furniture')
break
case HitTestSessionOptions.SurfaceClassification.Wall:
print('Hit wall — mount picture here')
break
case HitTestSessionOptions.SurfaceClassification.Ceiling:
print('Hit ceiling')
break
case HitTestSessionOptions.SurfaceClassification.Table:
print('Hit table — place small object')
break
default:
print('Hit unclassified surface')
}
})
Note: Semantic hit testing is only available on Spectacles. Phone lenses should use basic hit test (no
surfaceClassification). The classification is unavailable in the Lens Studio desktop simulator — test on-device.
Aligning Objects to Hit Surfaces
After a hit test, orient an object so it sits flat on the detected surface:
function alignToSurface(obj: SceneObject, position: vec3, normal: vec3): void {
obj.getTransform().setWorldPosition(position)
// If the surface is nearly horizontal (floor/ceiling), use a stable up vector
const EPSILON = 0.01
const isHorizontal = 1 - Math.abs(normal.normalize().dot(vec3.up())) < EPSILON
const lookDir = isHorizontal ? vec3.forward() : normal.cross(vec3.up())
obj.getTransform().setWorldRotation(quat.lookAt(lookDir, normal))
}
Physics Raycasting (Scene Geometry)
Use Physics.createGlobalProbe() to cast rays against scene colliders (not real-world surfaces). This is the tool for hover detection, interaction, and projectile collision:
const probe = Physics.createGlobalProbe()
probe.rayCast(
rayStart.getTransform().getWorldPosition(),
rayEnd.getTransform().getWorldPosition(),
(hit) => {
if (hit) {
print('Hit object: ' + hit.collider.getSceneObject().name)
print('Hit position: ' + JSON.stringify(hit.position))
print('Hit normal: ' + JSON.stringify(hit.normal))
if (markerObject) {
markerObject.getTransform().setWorldPosition(hit.position)
}
}
}
)
Gaze ray from camera
const cam = scene.findByName('Camera').getComponent('Camera') as Camera
const camT = cam.getSceneObject().getTransform()
const origin = camT.getWorldPosition()
const direction = camT.forward
const rayLength = 50
probe.rayCast(origin, origin.add(direction.uniformScale(rayLength)), (hit) => {
if (hit) handleGazeHit(hit)
})
Layers and filtering
const probe = Physics.createGlobalProbe()
probe.collisionMask = CollisionLayer.getMask(['Default']) // only hit Default layer
WorldQueryModule vs Physics Raycasting
WorldQueryModule.hitTest |
Physics.createGlobalProbe().rayCast |
|
|---|---|---|
| Hits | Real-world surfaces (depth mesh) | Scene colliders only |
| Use for | Placing content in the room | Interaction, collision detection |
| Async? | Yes (callback) | Yes (callback) |
| Available in simulator? | Limited / No | Yes |
| Semantic labels? | Yes (Spectacles only) | No |
Leaderboard Module
Lens Studio's Leaderboard module stores globally visible scores via Snap's Lens Cloud.
Setup
const leaderboardModule = require('LensStudio:LeaderboardModule')
Or inject via @input:
@input leaderboardModule: LeaderboardModule
Create or retrieve a leaderboard
const options = Leaderboard.CreateOptions.create()
options.name = 'MY_LEADERBOARD'
options.ttlSeconds = 86400 // 24 hours; 0 = permanent
options.orderingType = Leaderboard.OrderingType.Descending // higher = better
leaderboardModule.getLeaderboard(
options,
(leaderboard) => {
// leaderboard is ready
submitScore(leaderboard, 42)
},
(status) => print('Failed to get leaderboard: ' + status)
)
Submit a score
function submitScore(leaderboard: any, score: number): void {
leaderboard.submitScore(
score,
(userInfo) => {
print('Score submitted! User: ' + userInfo.snapchatUser.displayName)
},
(status) => print('Submit failed: ' + status)
)
}
Read the leaderboard
const retrieval = Leaderboard.RetrievalOptions.create()
retrieval.usersLimit = 10
retrieval.usersType = Leaderboard.UsersType.Global // or Friends, User
leaderboard.getLeaderboardInfo(
retrieval,
(otherRecords, currentUserRecord) => {
if (currentUserRecord) {
print('My score: ' + currentUserRecord.score)
}
otherRecords.forEach((record, i) => {
if (record?.snapchatUser) {
print(`#${i + 1}: ${record.snapchatUser.displayName} — ${record.score}`)
}
})
},
(status) => print('Fetch failed: ' + status)
)
Common Gotchas
- Hit test results are async — never read
resultsynchronously before the callback fires. filter: trueon hit test sessions smooths jittery hit positions. Disable it only if you need raw sensor accuracy.- Ray length matters — a ray that misses all surfaces returns
null. If you expect a hit, try multiple lengths (0.5×, 1×, 2× of your target) before giving up. - World Query is unavailable in the Lens Studio simulator on desktop — test surface placement on-device.
- Semantic classification requires
surfaceClassification = truein options and is Spectacles-only. - Leaderboard names are global within a lens. Different lenses cannot share a leaderboard by name.
- Leaderboard
ttlSeconds = 0means the record never expires, which could fill up quota. Use a TTL appropriate for your game's session length.
More from rolandsmeenk/lensstudioagents
lens-studio-scripting
Reference guide for the Lens Studio TypeScript component system — covering the @component, @input, @hint, @allowUndefined, and @label decorators, the BaseScriptComponent lifecycle (onAwake vs OnStartEvent, UpdateEvent, DelayedCallbackEvent one-shot and repeating timers, TurnOnEvent/TurnOffEvent, onDestroy), accessing components with getComponent (plus null-check patterns to fix 'cannot read property of null' errors), cross-TypeScript imports with getTypeName(), NativeLogger vs print, prefab instantiation (sync and async), SceneObject hierarchy queries, and enabling/disabling objects. Use this skill whenever writing or debugging any Lens Studio TypeScript script, wiring up scene objects, or fixing 'this is undefined' or null-reference errors — platform-agnostic (works for Spectacles and phone lenses).
12spectacles-lens-essentials
Reference guide for foundational Lens Studio patterns on Spectacles — covering the GestureModule (pinch down/up/strength, targeting, grab, phone-in-hand with correct TypeScript API), SIK components (PinchButton, DragInteractable, GrabInteractable, ScrollView), hand-tracking gestures, physics bodies/colliders/callbacks (including audio-on-collision), LSTween animation (position/scale/rotation/color tweens), prefab instantiation at runtime, materials (clone-before-modify), spatial anchors, on-device persistent storage (putString/getFloat), spatial images, and the Path Pioneer raycasting pattern. Use this skill for any Spectacles lens that needs interaction, motion, animation, physics, audio, or persistent local storage — including Essentials, Throw Lab, Spatial Persistence, Spatial Image Gallery, Path Pioneer, Public Speaker, Voice Playback, Material Library, and DJ Specs samples.
9spectacles-cloud
Reference guide for Snap Cloud (Supabase-powered backend) in Spectacles lenses — covering Fetch API setup (requires Internet Access capability in Project Settings), Postgres REST queries with the anon key, Row Level Security policies, Realtime WebSocket subscriptions with correct postgres_changes event format and reconnect-on-sleep patterns, cloud storage uploads of base64 images captured by Spectacles, serverless Edge Functions, and companion web dashboard architecture. Use this skill whenever a lens needs persistent cloud data, needs to share data with a web app in real time, uploads captured images to a bucket, or calls a cloud function — covering Snap Cloud and World Kindness Day samples. Use spectacles-networking for plain REST calls to non-Snap backends, and spectacles-connected-lenses for in-session multiplayer state.
7lens-studio-materials-shaders
Reference guide for materials and shaders in Lens Studio — covering runtime material property changes (clone-before-modify, mainPass.baseColor, mainPass.opacity, mainPass.baseTex), blend modes (Normal/Alpha/Add/Screen/Multiply), depth and cull settings (depthTest, depthWrite, twoSided, cullMode), render order, material variants, assigning textures and render targets, reading and writing RenderTarget textures for post-processing, the graph-based Material Editor node system, custom shader graph nodes, and common shader pitfalls. Use this skill for any lens that needs to change material colors or textures at runtime, implement custom visual effects with shaders, set up post-processing render pipelines, chain render targets, or debug material/blend-mode issues — covering MaterialEditor, Drawing, and HairSimulation examples.
6spectacles-networking
Reference guide for the Lens Studio Fetch API and WebView component in Spectacles lenses — covering InternetModule (Lens Studio 5.9+), Fetch API via internetModule.fetch(Request) with bytes/text/json response handling, performHttpRequest, Internet Access capability, GET/POST requests, custom headers, Bearer auth, polling, timeouts, CORS/HTTPS, WebSocket and RemoteMediaModule for media from URLs, and bidirectional WebView messaging. Use this skill for any lens that calls a REST API, polls a JSON endpoint, loads remote images, embeds a webpage, or talks to a custom backend — including the Fetch sample. Use spectacles-ai for LLM/RSG calls, or spectacles-cloud for Supabase/Snap Cloud integration.
6spectacles-connected-lenses
Reference guide for real-time multiplayer AR on Spectacles using Connected Lenses and Spectacles Sync Kit — covering session creation/joining with joinOrCreateSession (including 'already-in-session' error handling), TransformSyncComponent for position/rotation replication, RealtimeStore for shared key-value state (max 512 bytes per key), NetworkEventSystem for one-shot broadcast events, EntityOwnership for physics authority, Lens Cloud for persistent cross-session data, and patterns for turn-based (Tic Tac Toe) and real-time physics (Air Hockey). Also covers late-joiner state sync, transform drift mitigation, and store size limits. Use this skill whenever multiple Spectacles users need to share AR objects or state — covering Tic Tac Toe, Air Hockey, Laser Pointer, High Five, Shared Sync Controls, Spectacles Sync Kit, and Think Out Loud samples.
5