Skip to main content

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:

  1. Computing a depth map for the current view
  2. Intersecting the ray with depth data to find hit points
  3. Determining surface position and normal vectors
  4. 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:

  1. Project configured for Spectacles with Interactive Preview enabled
  2. Spectacles Interaction Kit imported and initialized
  3. 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

  1. Save the script and add it to your scene
  2. Create a target object to place on surfaces
  3. Set the Target Object input in the script component
  4. 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

Was this page helpful?
Yes
No