World Query Module
Overview
The World Query Module enables you to place objects on real-world surfaces instantly and accurately. This lightweight solution is specifically designed for Spectacles' wearable platform.
Why Use World Query?
Traditional surface detection APIs like hitTestWorldMesh or DepthTexture.sampleDepthAtPoint can be computationally expensive for wearable devices. The World Query Module provides:
- Instant placement: Fast hit testing for real surfaces
- Lightweight performance: Optimized for wearable devices
- Surface analysis: Depth and normal sampling at specific locations
- Smoothing options: Results can be smoothed across multiple hits
How It Works
The World Query Hit Test performs ray casting against real surfaces by:
- Computing a depth map for the current view
- Intersecting the ray with depth data to find hit points
- Determining surface position and normal vectors
- Returning null if the ray falls outside the field of view
Performance Limitation
Due to the low, 5Hz update rate of the underlying depth data, the hit test works only for static or slowly moving objects. Consider use cases accordingly.
Basic Surface Placement
Setup Requirements
Before implementing World Query, ensure you have:
- Project configured for Spectacles with Interactive Preview enabled
- Spectacles Interaction Kit imported and initialized
- A target object to place on surfaces
Implementation Steps
Follow these steps to implement basic surface placement using the WorldQueryHit API:
Step 1: Create a new TypeScript file with the required imports
Step 2: Set up the hit test session with filtering options
Step 3: Handle hit test results and object placement
Step 4: Configure user interaction for spawning objects
Complete Example
// import required modules
const WorldQueryModule = require('LensStudio:WorldQueryModule');
const SIK = require('SpectaclesInteractionKit/SIK').SIK;
const InteractorTriggerType =
require('SpectaclesInteractionKit/Core/Interactor/Interactor').InteractorTriggerType;
const InteractorInputType =
require('SpectaclesInteractionKit/Core/Interactor/Interactor').InteractorInputType;
const EPSILON = 0.01;
@component
export class WorldQueryHit extends BaseScriptComponent {
private primaryInteractor;
private hitTestSession;
private transform: Transform;
@input
targetObject: SceneObject;
@input
filterEnabled: boolean;
onAwake() {
// create new hit session
this.hitTestSession = this.createHitTestSession(this.filterEnabled);
if (!this.sceneObject) {
print('Please set Target Object input');
return;
}
this.transform = this.targetObject.getTransform();
// disable target object when surface is not detected
this.targetObject.enabled = false;
// create update event
this.createEvent('UpdateEvent').bind(this.onUpdate.bind(this));
}
createHitTestSession(filterEnabled) {
// create hit test session with options
var options = HitTestSessionOptions.create();
options.filter = filterEnabled;
var session = WorldQueryModule.createHitTestSessionWithOptions(options);
return session;
}
onHitTestResult(results) {
if (results === null) {
this.targetObject.enabled = false;
} else {
this.targetObject.enabled = true;
// get hit information
const hitPosition = results.position;
const hitNormal = results.normal;
//identifying the direction the object should look at based on the normal of the hit location.
var lookDirection;
if (1 - Math.abs(hitNormal.normalize().dot(vec3.up())) < EPSILON) {
lookDirection = vec3.forward();
} else {
lookDirection = hitNormal.cross(vec3.up());
}
const toRotation = quat.lookAt(lookDirection, hitNormal);
//set position and rotation
this.targetObject.getTransform().setWorldPosition(hitPosition);
this.targetObject.getTransform().setWorldRotation(toRotation);
if (
this.primaryInteractor.previousTrigger !== InteractorTriggerType.None &&
this.primaryInteractor.currentTrigger === InteractorTriggerType.None
) {
// Called when a trigger ends
// Copy the plane/axis object
this.sceneObject.copyWholeHierarchy(this.targetObject);
}
}
}
onUpdate() {
this.primaryInteractor =
SIK.InteractionManager.getTargetingInteractors().shift();
if (
this.primaryInteractor &&
this.primaryInteractor.isActive() &&
this.primaryInteractor.isTargeting()
) {
const rayStartOffset = new vec3(
this.primaryInteractor.startPoint.x,
this.primaryInteractor.startPoint.y,
this.primaryInteractor.startPoint.z
);
const rayStart = rayStartOffset;
const rayEnd = this.primaryInteractor.endPoint;
this.hitTestSession.hitTest(
rayStart,
rayEnd,
this.onHitTestResult.bind(this)
);
} else {
this.targetObject.enabled = false;
}
}
}
Integration Steps
- Save the script and add it to your scene
- Create a target object to place on surfaces
- Set the Target Object input in the script component
- Test in preview by moving the mouse and tapping, or send to Connected Spectacles
This example demonstrates the simplest method for spawning objects on surfaces using hand gestures (point to move, pinch to spawn).
Check out the World Query Hit - Spawn On Surface and Surface Detection assets on Asset Library for ready-to-use implementations!
Semantic Hit Testing
Ground Detection
World Query can identify specific surface types, such as ground surfaces. This enables automatic object placement based on surface classification.
Experimental Feature
You need to enable Experimental APIs in your project settings to use semantic hit testing functionality.
Classification Implementation
Enable semantic classification to detect ground surfaces:
const WorldQueryModule =
require('LensStudio:WorldQueryModule') as WorldQueryModule;
@component
export class HitTestClassification extends BaseScriptComponent {
private hitTestSession: HitTestSession;
onAwake() {
this.hitTestSession = this.createHitTestSession();
this.createEvent('UpdateEvent').bind(this.onUpdate);
}
createHitTestSession() {
const options = HitTestSessionOptions.create();
options.classification = true;
const session = WorldQueryModule.createHitTestSessionWithOptions(options);
return session;
}
onHitTestResult = (result: WorldQueryHitTestResult) => {
if (result === null) {
// Hit test failed
return;
}
const hitPosition = result.position;
const hitNormal = result.normal;
const hitClassification = result.classification;
switch (hitClassification) {
case SurfaceClassification.Ground:
print('Hit ground!');
break;
case SurfaceClassification.None:
print('Hit unknown surface!');
break;
}
};
onUpdate = () => {
// Ray start and end depend on the specific application
const rayStart = new vec3(0, 0, 0);
const rayEnd = new vec3(0, 0, 1);
this.hitTestSession.hitTest(rayStart, rayEnd, this.onHitTestResult);
};
}
Additional Resources
API Reference
- WorldQueryModule - Main module for world querying functionality
- HitTestSession - Session management for hit testing operations
- HitTestSessionOptions - Configuration options for hit test sessions
- WorldQueryHitTestResult - Results returned from hit test operations
Related APIs
- hitTestWorldMesh - Alternative world mesh hit testing
- Physics.Probe.raycast - Physics-based ray casting
- DepthTexture.sampleDepthAtPoint - Direct depth sampling