Omni SpatialQR

Smart
Initializing...

QR Test & Generator

Scannable assets. Grab another device to scan and experience the magic. Long press (or right click) any QR code to download.

Tap any QR code to isolate it for scanning.

Showcase

The core capabilities.

Rich Cards

JSON-driven layout mapping.

Custom HTML Elements

Mapping hidden DOM nodes in 3D.

Smart Text & Data

Regex parsed native integrations.

3D Models

WebGL .glb loaders.

Video Players

Liquid morphing media.

Image Frames

Static spatial assets.

Audio Players

Interactive widgets.


Custom Generator

Instantly create and download your own custom styled QR anchors. Right-click/long-press to download. Hint: Try appending ?scale=1.5 to your payload to test dynamic AR overrides!

Live Configuration


Tracking & Performance


Engine & Advanced Setup

Changing these requires a full hardware/DOM reload.

Omni SpatialQR

Zero-Friction WebAR Framework. A blazing-fast Vanilla JS library for mapping rich HTML interfaces and 3D models perfectly onto physical QR codes.

The physical world is static. The digital world is limitless. Omni SpatialQR is the bridge between them.

Until now, Augmented Reality required massive friction: forcing users to download heavy apps, scanning weird proprietary markers, or waiting for bloated 3D engines to load. Omni SpatialQR changes the paradigm. It turns standard, everyday QR codes—on business cards, restaurant menus, retail packaging, and real estate signs—into instant, zero-friction spatial interfaces directly in the mobile browser.

Make your print media play video. Turn a coaster into a 3D showroom. Turn a poster into a glassmorphic checkout interface. It’s lightweight, instantly familiar to users, and feels like native magic.

README.md

A vanilla JavaScript library that calculates camera pose homography from the native BarcodeDetector API to project HTML DOM elements and Three.js WebGL models onto physical QR codes via mathematically precise CSS matrix3d transforms.

Installation

<script src="omni-spatial-qr.js"></script>

Minimal Quick Start

const visionAR = new OmniSpatialQR({
  container: '#ar-wrapper',
  type: 'dynamic'
});

visionAR.start();

// Essential for SPA (React/Vue) routing to prevent memory leaks
visionAR.stop();

Use Cases & Data Routing

Physical QR codes should be as simple as possible (low data density) so they scan instantly from far away. Omni SpatialQR provides two powerful architectures to keep your codes microscopic while rendering massive data payloads.

Use Case A: The Universal Billboard

Use this if your QR codes are standard URLs designed to be scanned by native iPhone/Android cameras to open your website. The library automatically extracts your query parameters (e.g. https://mysite.com?promo=vip).

const visionAR = new OmniSpatialQR({
  type: 'dynamic',
  parseParam: ['promo'], // Safely extracts { promo: "vip" }
  assetResolver: async (parsedObj) => {
    if (parsedObj.promo === 'vip') {
      // Return JSON to instantly build a Glassmorphic Card!
      return {
        title: "VIP Status",
        description: "Welcome back.",
        buttons: [{ label: "Enter", color: "#22c55e" }]
      };
    }
  }
});

Use Case B: The Lightning In-App Scanner

If users scan codes inside your webapp, you don't need full URLs. Use custom URIs (e.g., omni:vip). This makes the physical QR code smaller, allowing the camera to track it significantly faster and from much further away.

const visionAR = new OmniSpatialQR({
  type: 'dynamic',
  uriPrefix: 'omni', // Intercepts "omni:vip"
  assetResolver: async (parsedData, rawData) => {
    // parsedData is the clean string: "vip"
    // rawData is the untouched original: "omni:vip?scale=2"
    const dbItem = await fetch(`/api/items/${parsedData}`);
    return dbItem.json();
  }
});

Use Case C: The 3D Model Viewer

Ideal for e-commerce or museums. Map physical items to their 3D digital twins automatically.

const visionAR = new OmniSpatialQR({
  type: 'dynamic',
  enable3D: true, // Triggers Three.js CDN download
  autoRotate3D: true, // Creates a turntable effect
  uriPrefix: 'model',
  assetResolver: async (payload) => {
    if (payload === 'shoe') return 'https://mysite.com/shoe.glb';
  }
});

Use Case D: True Physical AR Utility

If you need an AR element (like a 3D arrow or a machine part overlay) to point in the exact physical direction encoded by the printed QR code ink, you must force the engine to track the absolute physical orientation.

const visionAR = new OmniSpatialQR({
  type: '3d',
  enable3D: true,
  orientationMode: 'physical' // Forces Nimiq WASM ink tracking and bypasses screen-normalization
});

Type & Dynamic URL Overrides

You can temporarily override your core configuration for specific assets simply by appending parameters directly to the physical QR code URL. The library applies them instantly and safely strips them out so file inference remains untouched.

  • ?omni=type (Forces the specific UI layout, e.g. ?omni=audio)
  • ?scale=2.5 (Scales the overlay dynamically for this scan)
  • ?pos=top (Overrides the anchor layout position)
  • ?mm=50 (Triggers trueScale3D and sets the physical QR size in millimeters)
  • ?yaw=90 (Rotates a 3D model on the Y-axis to correct export orientation)

Omni URI Scheme: You can also prefix strings with type:payload. Example: video:https://mysite.com/vid.mp4 or 3d:duck.glb. Furthermore, Omni URIs fully support URL parameter overrides natively. For example, scanning 3d:duck.glb?scale=2&yaw=90 will cleanly extract the parameters, rotate and scale the model, and will load duck.glb to the type preset.

AssetResolver Overrides & Error Handling

If your backend returns a URL without an extension (e.g. https://api.com/file_77), dynamic mode will fail to infer the media type. Your resolver can return a config object to force the engine type. You can also pass AR overrides directly from your database!

Furthermore, if your database lookup fails, simply return null. The engine will gracefully abort the visual update and fire the onError hook.

assetResolver: async (id) => {
  if (!isValid(id)) return null; // Aborts render, fires onError()

  return {
    type: '3d', // Forces the 3D Engine
    data: 'https://api.com/model_77',
    scale: 1.5, // Overrides global scale
    yaw: 180 // Corrects backwards facing model
  };
}

Custom HTML vs. DOM Templates

You have two distinct ways to render custom user interfaces in 3D:

1. DOM Templates (Static): Best for React/Vue components already mounted on your page. Map an ID string to a hidden DOM node.

<!-- Your hidden custom UI -->
<div id="my-rating" style="display:none;">⭐⭐⭐⭐⭐</div>

const visionAR = new OmniSpatialQR({
  templates: {
    'stars': '#my-rating'
  }
});
// If the scanner reads the exact string "stars", it clones your HTML!

2. Raw HTML Injection (Dynamic): Best for backend-driven content. Simply return an object with an html property from your resolver.

assetResolver: async (payload) => {
  return { html: "<div class='live-price'>$99.00</div>" };
}

CSS Resolution Nuance: The library uses a strict CSS reset (all: initial) to protect your layout, but explicitly inherits your font-family. Due to the default overlayResolution: 100, 1 unit equals 1 physical pixel relative to the QR code size. Scale your CSS padding and fonts accordingly!

Advanced 3D Animation Control

The on3DLoad hook gives you total control over the Three.js scene. The library provides a constantly ticking AnimationMixer in the background, allowing you to trigger baked animations flawlessly without writing your own render loops.

let myMixer = null;
let myAnimations = null;

const visionAR = new OmniSpatialQR({
  type: '3d',
  autoPlay3DAnimations: false, // Prevents track 0 from auto-playing
  on3DLoad: (gltf, scene, mixer) => {
    // Save the library's ticking mixer globally
    myMixer = mixer;
    myAnimations = gltf.animations;

    // Or use GSAP to manually animate the raw mesh!
    gsap.to(gltf.scene.position, { y: 2, duration: 1 });
  }
});

// Trigger a baked animation later via an HTML button click
document.getElementById('play-btn').onclick = () => {
  if (myMixer && myAnimations[2]) {
    myMixer.stopAllAction();
    myMixer.clipAction(myAnimations[2]).play();
  }
};

Full Configuration API Reference

  • container (String): DOM selector for the AR viewport. (Default: '#omni-qr')
  • type (String): 'dynamic', 'square', 'text', 'card', 'image', 'video', 'audio', '3d', 'youtube', 'customHtml' (Default: 'square')
  • scale (Number): Base scale multiplier. (Default: 1.0)
  • position (String): 'center', 'top', 'bottom', 'left', 'right'. (Default: 'center')
  • gap (Number): Unit offset if position is not center, scales proportionally with the overlay. (Default: 10)
  • overlayResolution (Number): Base render matrix. Higher numbers increase clarity but can reduce performance. Use 100 for optimal performance, 200 for big scales and 400 only for computers. (Default: 100)
  • spawnAnimation (Boolean): Triggers an Apple-style pop-up animation for all DOM overlays and 3D models when they spawn. (Default: true)
  • autoPlay (Boolean): Auto-plays injected media loops. (Default: true)
  • showControls (Boolean): Toggles custom media HUD overlays. (Default: true)
  • textTruncate (Boolean): Truncates strings vs wrapping to paragraph block. (Default: true)
  • cameraFacing (String): 'environment' or 'user'. (Default: 'environment')
  • mirrorVideo (String|Bool): 'auto' (mirrors webcams/front-cameras flawlessly), true, or false. (Default: 'auto')
  • gpuTransform (Boolean): Applies will-change. Keep false to prevent text blurring on high-end devices. (Default: false)
  • cameraQuality (String): 'low', 'normal', 'high'. (Default: 'normal')
  • cameraFOV (Number): Field of View for the 3D camera. Matches standard mobile wide-angle lenses to prevent 3D stretching. (Default: 65)
  • orientationMode (String): Master tracking mode. 'auto' = Smart hybrid. 'facing' = Always screen-upright. 'upright' = Spawns screen-upright, then physically locks. 'physical' = Absolute ink tracking via WASM engine bypass. (Default: 'auto')
  • scanFPSActive / scanFPSIdle (Number): Battery-saving frame throttlers. (Default Active: 60, Default Idle: 2)
  • nimiqUrl (String): Path to the Nimiq WASM file. Change this to point to a CDN or your custom directory. (Default: './qr-scanner.umd.min.js')
  • showReticle (Boolean): UX helper. Shows pulsing box when tracking is lost. (Default: true)
  • debugHUD (Boolean): Built-in debugging HUD for performance telemetry. (Default: false)
  • enable3D (Boolean): Requires true to dynamically fetch Three.js from CDN. (Default: false)
  • trueScale3D (Boolean): Bypasses auto-fitting to render WebGL at exact native 1:1 physical scale. (Default: false)
  • qrSize (Number): Physical dimension of the QR in millimeters. Required if trueScale3D is true. (Default: null)
  • autoRotate3D (Boolean): Constantly rotates models like a turntable. (Default: true)
  • rotateSpeed (Number): Turntable speed multiplier. (Default: 1.0)
  • autoPlay3DAnimations (Boolean): Automatically detects and loops embedded .glb animations. (Default: true)
  • yawOffset (Number): Adds rotational offset (in degrees) to correct 3D models exported with the wrong forward axis. (Default: 0)
  • show3DSpinner (Boolean): Shows a frosted silicone loading sphere while heavy 3D models download. (Default: true)
  • dracoDecoderPath (String): URL path to Google's Draco WASM decoders. Required to load highly compressed 3D models. (Default: 'https://www.gstatic.com/draco/v1/decoders/')
  • parseParam (Array/String): e.g. ['id', 'user']. Extracts parameters natively to use in assetResolver. (Default: null)
  • uriPrefix (Array/String): e.g. 'brand'. Enables custom brand:payload extraction. (Default: null)
  • assetResolver (Async Function): (parsedData, rawData) => {}. Receives the cleanly parsed payload and the untouched raw string. Returns mapped URLs, JSON Cards, or JSON Config Overrides. Supported override keys: type, data, html, scale, position, yaw, mm. (Default: null)
  • templates (Object): Maps IDs to hidden DOM elements for custom HTML rendering. (Default: {})

Lifecycle Events

  • onDetect(parsedData, rawData): Fires the instant a payload is decoded and extracted, before the UI builds. Perfect for firing a background analytics pixel or triggering an auto-download.
  • onTrackingRestored(): Fires when AR physically locks onto the square.
  • onTrackingLost(): Fires when AR loses physical tracking.
  • onClick(resolvedData, rawData): Fires when the user taps the AR overlay. Passes the final resolved data (the output of your assetResolver) and the original raw string. Great for navigating to external product pages.
  • on3DLoad(gltf, scene, mixer): Fires instantly when a 3D model finishes downloading and parsing, but before it appears on screen. Provides raw access to the Three.js objects and the animation mixer for custom modifications.
  • onError(err): Captures Camera, WebWorker, and Resolver failures gracefully.

Pro-Tips & Gotchas

  • HTTPS Requirement: Modern browsers strictly require https:// (or localhost) to access the getUserMedia camera API.
  • CORS for 3D: If you are loading .glb files from an external server (like AWS S3 or a custom CDN), ensure your server is configured with Access-Control-Allow-Origin: *, otherwise the WebGL canvas will block the download for security reasons.

Live Engine Methods

  • .setScale(number): Smoothly animates the scale dynamically. Snaps instantly if called while tracking is lost.
  • .set3DRotation(bool): Instantly starts or stops the 3D turntable animation.
  • .freezeTracking(bool): Freezes the AR overlay in place on screen while the camera keeps playing, drastically improving UX for viewing media.
  • .pause(): Synchronously stops the camera hardware to save battery for modal/background views.
  • await .resume(): Asynchronously requests camera hardware access to spin the AR engine back up.

Physical QR Code Generator API

Don't use a third-party generator. Our static helper creates beautiful, branded physical anchors instantly. With maximum error correction (Level H) when adding an embedded logos.

const qr = await OmniSpatialQR.generate({
  data: 'omni:promo1',
  type: 'soft', // standard, soft, dots
  size: 300,
  color: '#18181b', // Dot color
  bgColor: '#ffffff',// Background color
  logo: '/logo.png' // Drops your brand into the center
});
document.body.appendChild(qr);