World Query Module
Overview
When developing lenses for Spectacles, you may need to place an object on a surface instantaneously. This method aims to be accurate and lightweight.
Existing APIs such as hitTestWorldMesh
, Physics.Probe.raycast
, or DepthTexture.getDepth
provide accurate results but are often computationally heavy or not designed for wearable devices. The World Query Hit Test from the World Query Module performs a hit test for real surfaces, sampling the depth and normal at a specific location. If the hit test is executed, the internal algorithm computes a depth map for the current view and tries to intersect the ray with the depth to determine the point and surface normal. If the ray is outside the field of view of the depth map, the hit test fails and returns null. The API provides additional options to smooth out results across multiple hits.
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.
Example
Follow these steps to sample depth and normal of a ray to place an object on real-world geometry using the WorldQueryHit API.
- Configure your project for Spectacles and enable Interactive Preview.
- Import and initialize Spectacles Interaction Kit.
- Create a new TypeScript file and include the following code:
// 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 NewScript 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 + 30
);
const rayStart = rayStartOffset;
const rayEnd = this.primaryInteractor.endPoint;
this.hitTestSession.hitTest(
rayStart,
rayEnd,
this.onHitTestResult.bind(this)
);
} else {
this.targetObject.enabled = false;
}
}
}
-
Save the script and add it to the scene.
-
Create a scene object to place on the surface and set it as a Target Object input of this script.
Test this lens in Lens Studio preview (by moving the mouse and tapping) or send it to Connected Spectacles (point your hand to move and pinch to spawn an object).
This example demonstrates the simplest method for spawning objects on a surface.
Check out the World Query Hit - Spawn On Surface
and Surface Detection
assets on Asset Library!