JUPITER / INTERLACE SDK 1.0.0

Jupiter Interlace SDK Developer Guide

Version 1.0.0 · English edition · 2026-09-11

Render GLB assets, existing Three.js scenes, or multi-view texture atlases as subpixel-interlaced output for an autostereoscopic display. Your application retains control of scene content and business logic. Calibration names remain Pitch / Tan / Offset, with API and JSON keys pitch / tan / offset.

1. Choose an integration path

Your application Entry point Your responsibility
A new GLB viewer JupiterViewer Container, model URL, device calibration
An existing Three.js application InterlaceRenderer.render(scene, camera) Scene updates, input, render loop
A custom rendering pipeline InterlaceRenderer.renderViews(callback) Synchronous rendering of each linear-color view
An existing multi-view atlas InterlaceRenderer.compositeAtlas(input) Atlas layout, view count, color encoding
Calibration and reference calculations jupiter-interlace-sdk/core Rendering and platform integration

The rendering backend uses WebGL2 and Three.js 0.180.0. The core module has no Three.js dependency. This release supports perspective cameras and requires EXT_color_buffer_float. It does not include Android AAR, Unity, or Unreal adapters.

2. Run the included examples

Install Node.js 20.19 or later. From the package directory, run:

node scripts/serve.mjs

On Windows, you can double-click Start-Demo.cmd. Open:

http://127.0.0.1:4173/examples/glb-viewer/

The prebuilt example needs no npm installation or CDN. It starts in 2D preview and loads a sample with an embedded texture, transparency, skeletal animation, and object animation. Use Open GLB for a local file, or enter a URL. The panel controls calibration and rendering.

Other examples are available at /examples/existing-scene/ and /examples/atlas/. The bundled server listens on localhost. To run on another device, deploy the example and dist folders to your own static server.

Use HTTP/HTTPS for the examples. Opening them with file:// prevents reliable module imports, model fetching, workers, and WASM loading. The HTML guide itself can be opened directly.

3. Install in an npm project

Copy the supplied tarball into your project and install it locally:

npm install ./jupiter-interlace-sdk-1.0.0.tgz three@0.180.0
# For TypeScript projects:
npm install -D @types/three@0.180.0

Use the pinned Three.js version. Do not combine objects from multiple engine versions or import an additional CDN copy of Three.js.

3.1 Create the container

<div id="viewer" style="width:100%;height:600px"></div>
<input id="model-file" type="file" accept=".glb">
<div id="calibration"></div>
<p id="load-status" role="status"></p>

The container must have a nonzero height. For optical calibration, use the final fullscreen layout on the target display.

3.2 Initialize the viewer

import { JupiterViewer, createCalibrationPanel } from 'jupiter-interlace-sdk';

const status = document.getElementById('load-status')!;
const viewer = new JupiterViewer({
  container: document.getElementById('viewer')!,
  calibration: {
    pitch: 0.27777, tan: 10, offset: 2, // Sample values: replace with device calibration.
    order: 'forward', subpixelOrder: 'RGB', rotation: 0,
  },
  render: {
    mode: '2d', views: 9, viewWidth: 640,
    viewSpacing: 0.006, focusDistance: 3,
    toneMapping: 'aces', exposure: 1,
  },
  decoders: {
    dracoDecoderPath: '/jupiter-decoders/draco/',
    ktx2TranscoderPath: '/jupiter-decoders/basis/',
  },
  onError(error) { status.textContent = error.message; },
});
const panel = createCalibrationPanel(
  viewer.interlacer, document.getElementById('calibration')!,
);
try {
  await viewer.loadGLB('/models/product.glb', {
    onProgress(p) {
      status.textContent = p.ratio === null
        ? `${p.loaded} bytes downloaded`
        : p.ratio === 1 ? 'Parsing model...' : `Download ${Math.round(p.ratio * 100)}%`;
    },
    onLoad(asset) { status.textContent = `Loaded: ${asset.animations.length} animations`; },
  });
} catch (error) { console.error(error); }

document.getElementById('model-file')!.addEventListener('change', async event => {
  const input = event.target as HTMLInputElement;
  if (!input.files?.[0]) return;
  try { await viewer.loadGLB(input.files[0]); }
  catch (error) { console.error(error); }
  input.value = '';
});

// Enable after device calibration:
// viewer.setOptions({ mode: 'interlaced' });
// On teardown, remove your event listeners, then:
// panel.dispose();
// viewer.dispose();

The viewer creates its own render loop, OrbitControls, hemisphere light, and directional light. Use defaultLights: false if the application supplies lighting. Loading a model automatically centers it, fits the camera, and plays its first animation unless disabled.

frameModel() preserves model scale and scene units. It changes the camera position, clipping planes, and focusDistance, but preserves viewSpacing. Pass autoFrame: false to preserve your camera, or apply your own settings after loading. Use autoPlay: false to disable automatic animation playback.

3.3 Deploy the decoders

Bundlers do not automatically publish decoder files. Copy:

node_modules/jupiter-interlace-sdk/dist/decoders/
  -> public/jupiter-decoders/

Cross-platform copy command:

node -e "require('node:fs').cpSync('node_modules/jupiter-interlace-sdk/dist/decoders','public/jupiter-decoders',{recursive:true})"

Configure the paths as in the example. Paths must end with /; include the deployment base path when hosting below a subdirectory. Ordinary GLB assets do not request Draco or Basis files. Meshopt is integrated into the module; Draco and KTX2/Basis resources load on demand.

The server must serve JS/WASM resources; application/wasm is the recommended WASM MIME type. If using CSP, allow the required local scripts, WASM compilation, blob: workers, and embedded image blobs according to your site's policy.

4. Use the standalone browser build

Copy the complete dist directory to your static site:

<!doctype html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>html,body,#viewer{margin:0;width:100%;height:100%;overflow:hidden}</style>
<div id="viewer"></div>
<script type="module">
  import { JupiterViewer } from './dist/jupiter-interlace.standalone.js';
  const viewer = new JupiterViewer({
    container: document.querySelector('#viewer'),
    render: { mode: '2d' },
    onError: error => console.error(error),
  });
  try { await viewer.loadGLB('./models/product.glb'); }
  catch (error) { document.body.append(String(error)); }
  window.addEventListener('pagehide', () => viewer.dispose(), { once: true });
</script>

The standalone build includes Three.js and the loaders. Keep dist/decoders/ next to it for compressed assets. It is an ES module, not a window.Jupiter script. To create additional scene objects, import THREE from the same standalone module:

import { THREE, InterlaceRenderer } from './dist/jupiter-interlace.standalone.js';

5. Integrate an existing Three.js scene

import * as THREE from 'three';
import { InterlaceRenderer } from 'jupiter-interlace-sdk';

// renderer, scene, camera, and mixer belong to your application.
renderer.outputColorSpace = THREE.SRGBColorSpace;
const interlacer = new InterlaceRenderer(renderer, {
  calibration: { pitch: 0.27777, tan: 10, offset: 2 },
  render: { views: 9, viewWidth: 640, viewSpacing: 0.006, focusDistance: 3 },
});
let previous = performance.now();
renderer.setAnimationLoop(now => {
  const dt = Math.min((now - previous) / 1000, 0.1);
  previous = now;
  mixer.update(dt); // Advance animation and physics once per displayed frame.
  interlacer.render(scene, camera);
});
// Teardown: renderer.setAnimationLoop(null); interlacer.dispose();

Use PerspectiveCamera. The SDK copies the camera's world transform, generates parallel offset cameras with off-axis projections, and leaves the original camera unchanged. Avoid nonuniform scaling on camera parents.

The output fills the drawing buffer. Match that buffer to physical pixels, for example by setting the renderer pixel ratio to devicePixelRatio and using the actual CSS container size. Do not scale, blur, apply FXAA, or run neighborhood filters after interlacing. Apply effects separately to each view before interlacing.

Every view triggers a scene render. Do not advance animation or physics in onBeforeRender, because the views would capture different simulation times. The SDK does not dispose your external scene, camera, renderer, or assets.

5.1 Reduce quality during interaction

let settle;
function onStart() {
  clearTimeout(settle);
  interlacer.setOptions({ views: 3, viewWidth: 320 });
}
function onEnd() {
  settle = setTimeout(() => interlacer.setOptions({ views: 30, viewWidth: 720 }), 180);
}
controls.addEventListener('start', onStart);
controls.addEventListener('end', onEnd);
// Clear the timeout and remove both listeners on teardown.

This is an application-controlled policy. Keep optical calibration unchanged when switching quality levels.

5.2 Supply custom view rendering

interlacer.renderViews((target, view) => {
  // view: { index, logicalIndex, offset, count, width, height }
  // target is already bound and cleared: linear HalfFloat color with depth.
  // Construct the offset camera and off-axis projection using view.offset.
  renderMyViewToTarget(target, view); // Implemented by your application.
});

The callback must be synchronous; returning a Promise is unsupported. All views are generated before the completed frame is published. The SDK restores the target, viewport, scissor, clear color/alpha, autoClear, tone mapping/exposure, and XR enabled state it changes. Restore any other renderer state modified by your callback.

6. Supply an existing atlas

Each cell contains a complete view. Cells have equal dimensions and no padding, ordered left-to-right and bottom-to-top:

interlacer.setOptions({ mode: 'interlaced' });
interlacer.compositeAtlas({
  texture: atlasTexture,
  width: 1920, height: 1080,
  columns: 3, rows: 3, views: 9,
  encoding: 'srgb',
});

Dimensions must match the texture and divide evenly by the grid. views must be 1–30 and cannot exceed the cell count. srgb means display-encoded input that needs no further exposure or tone mapping; linear means scene-linear input to be processed with SDK settings.

An sRGB texture tagged with THREE.SRGBColorSpace is decoded by hardware and re-encoded without additional tone mapping. Raw display bytes in a DataTexture can use THREE.NoColorSpace. Keep flipY, row ordering, and encoding consistent. Textures must belong to the same WebGL context. The SDK does not dispose external atlas textures or synthesize missing viewpoints.

7. Parameter reference

7.1 Optical calibration: setCalibration()

Key Default / values Meaning
pitch 0.27777, finite number Original v0.17.1 Pitch: row phase coefficient, not lens pitch in millimeters
tan 10, finite number Original Tan: cycle length in subpixel-coordinate units; negative values are supported; zero selects logical view 14
offset 2, finite number Phase offset in the same units as Tan
order forward, reverse, pingpong Forward, reversed, or original 59-phase reflected mapping
subpixelOrder RGB, BGR Physical color-channel arrangement
rotation 0, 90, 180, 270 Output coordinate rotation for calibration; does not rotate the model

In calibrated coordinates, the origin is at the top left:

base = offset + y * 3 * pitch + (width - 1 - x) * 3
RGB phases = base + 2, base + 1, base

There are always 30 logical views, and the legacy center remains 14, not 14.5. Legacy settings are interpreted using v0.17.1 application names. CPU calculations use JavaScript double precision; GPU calculations use float precision. Extreme values or exact quantization boundaries may differ between CPU and GPU.

7.2 Rendering: setOptions()

Key Default Constraint / purpose
views 9 Integer 1–30, actual rendered view count
viewWidth 640 Positive integer, maximum per-view width; aspect ratio and GPU limits determine the actual size
viewSpacing 0.006 Nonnegative camera spacing between logical positions, in scene units
focusDistance 3 Positive forward distance to the zero-parallax plane, in scene units
mode interlaced interlaced, 2d, or view
previewView 14 Integer 0–29, used in view mode
toneMapping aces none, reinhard, or aces, applied per view before interlacing
exposure 1 Positive exposure multiplier for linear input

Logical views define optical mapping; actual rendered views define workload. Nine rendered views approximate 30 logical positions and are not equivalent to 30 independently rendered views. Invalid values and unknown parameter names throw errors instead of silently clamping.

viewer.setCalibration({ offset: 2.5, tan: 10 });
viewer.setOptions({ viewSpacing: 0.012, focusDistance: 4, views: 30 });

8. Profiles, loading, animation, and ownership

localStorage.setItem('device-A', viewer.exportProfile());
const saved = localStorage.getItem('device-A');
if (saved) viewer.importProfile(saved);
viewer.importProfile({ pitch: 0.27777, tan: 10, offset: 2, spacing: 0.006, focus: 3, order: 0 });
if (viewer.model?.animations.length) viewer.model.play(0); // Index or clip name.
viewer.model?.pause();
viewer.model?.resume();

Profiles use schemaVersion: 1, algorithm: 'jupiter-30-v1', and separate calibration and render objects. Unknown versions are rejected. Store device identity and display resolution separately in your application. Legacy tan keeps its name, while spacing and focus migrate to viewSpacing and focusDistance.

loadGLB accepts a URL/string, File/Blob, or ArrayBuffer. A new viewer load cancels the preceding pending load. A successfully parsed model replaces the current one; load failures preserve the previous model. Download progress reaching 100% does not mean texture decoding or GPU upload is finished. Use Promise resolution or onLoad for completion. Without Content-Length, ratio is null.

Supply AbortSignal for cancellation. Parsing or decoder work already started may continue, but cancelled assets will not attach and are disposed when completed. credentials applies to the main GLB fetch; child-resource authentication/CORS requires separate server or LoadingManager configuration. Local GLB files should embed their buffers and images; resourcePath can provide an external-resource base URL.

A standalone GLBLoader returns a caller-owned GLBAsset. A viewer-loaded asset belongs to the viewer and is disposed when replaced, unloaded, or destroyed. Do not retain shared asset textures elsewhere after disposing their owner. viewer.dispose() clears its model, controls, observer, render loop, decoders, and canvas. Dispose externally added scene objects and environment maps yourself.

9. Display calibration and performance

  1. Verify the model in 2D first.
  2. Use the target display in fullscreen with the correct physical size, orientation, and subpixel layout.
  3. Enter device calibration, adjust Offset, and use test patterns to refine Pitch and Tan.
  4. Adjust view spacing and focus distance after the optical mapping is stable.
  5. Save the preset and check crosstalk, depth, and view transitions at real viewing positions.

The calibration origin is the drawing buffer, not the operating system desktop. Window movement, zoom, orientation, CSS transforms, or compositor scaling can change the physical arrangement. A preset for one window layout is not automatically valid for another.

Keep the final interlaced framebuffer at physical resolution. Reduce per-view resolution or view count to save work. Output is opaque sRGB, not a freely resizable UI image.

The renderer reuses one HalfFloat view target with depth and two RGBA8 output buffers. getStats().estimatedBufferBytes estimates those buffers, excluding model, texture, shadow, post-processing, and driver allocations. cpuMs is CPU submission time, not GPU time or FPS. This release generates all views synchronously; it has no automatic GPU budget, multi-frame progressive scheduler, or fixed frame-rate guarantee.

On WebGL context loss, the high-level viewer stops and reports an error. Call start() after restoration, or recreate the viewer if application resources fail to recover.

10. Troubleshooting

Symptom Check
Blank display or initialization failure Container height, WebGL2, float-buffer extension, hardware acceleration, console
Download completes but model is absent Parsing errors, embedded textures, extensions, decoder URLs, CSP
Remote URL fails HTTP status, CORS, HTTPS/HTTP mismatch, authentication
Missing local textures Export a GLB with embedded textures and buffers
Dark model Lighting, environment maps, metalness, exposure; optical settings do not fix lighting
Color stripes on an ordinary monitor Use 2D or single-view preview; interlacing expects matching optical hardware
Reversed depth Check order and the intended view arrangement
Fullscreen changes the effect Check physical size and origin, then calibrate the final layout
Multiple canvases after route changes Create the viewer once per mount and dispose it on teardown
SSR document/window error Instantiate only after client mounting
Poor performance at 30 views Reduce actual views or per-view width, not final output resolution

Appearance depends on materials, lighting, and environment and may differ from authoring software. The tested extension combinations are not a claim of support for every glTF extension, orthographic camera, WebXR pipeline, or GPU. Refer to the bundled validation record.

11. AI development prompts

Provide the AI with the tarball, this guide, dist/types, your package.json, and relevant scene/render-loop files. Replace example paths, framework names, and calibration values with your own.

Prompt A: Build a GLB viewer

Build a GLB viewer using the supplied Jupiter Interlace SDK 1.0.0.
Read docs/developer-guide.md and dist/types/index.d.ts before coding.
Install ./jupiter-interlace-sdk-1.0.0.tgz and pin Three.js to 0.180.0.
Use JupiterViewer for URL and local GLB loading, progress, visible errors,
orbit/pan/zoom, camera fitting, animation play/pause, 2D/interlaced switching, and fullscreen.
Use createCalibrationPanel with pitch, tan, offset, order, subpixelOrder,
rotation, views, viewWidth, viewSpacing, and focusDistance.
Copy decoders to public/jupiter-decoders and configure both decoder paths.
Start in 2D. Treat unknown device calibration as pending, not verified.
Handle failed and superseded loads and component cleanup.
Do not add PLY/Spark or rewrite the interlacing equation.
Build the application and verify GLB loading, live settings, animation, and disposal in a browser.
Report changed files, run commands, test results, and remaining physical-display checks.

Prompt B: Integrate an existing scene

Integrate Jupiter Interlace SDK 1.0.0 into my existing Three.js scene.
Read the guide and types, then identify renderer, scene, PerspectiveCamera, and render loop.
Reuse these objects with InterlaceRenderer.render(scene, camera).
Update animation and physics once per displayed frame, never once per view.
Use sRGB output and a drawing buffer matching physical pixels.
Do not resize, blur, or apply FXAA after interlacing.
Keep the existing model loader. Add JSON calibration, 2D/interlaced switching,
view spacing, focus distance, and rendered-view controls.
If the project does not use Three.js 0.180.0, explain the compatibility work before changing it.
Do not mix two engine copies. Verify occlusion, camera preservation, temporal consistency,
saved settings, and resource ownership.

Prompt C: Create a React or Vue component

Wrap JupiterViewer in a reusable component matching my project's framework.
Read the real API first; do not invent loadModel or setPitch methods.
Accept a GLB URL, calibration, and render options as props; expose progress, load, and error events.
Create one viewer after mounting. Use loadGLB for URL changes and setters for parameter changes.
For React, use refs/effects and handle StrictMode; for Vue, use mounted/unmount lifecycle hooks.
Instantiate only on the client. Cancel pending work and dispose the viewer and panel on teardown.
Configure public decoder URLs. Verify one canvas per mount, rapid URL switching without stale models,
and no active render loop after unmounting.

Prompt D: Integrate an atlas

Use compositeAtlas to display my existing multi-view atlas.
Confirm texture dimensions, columns, rows, view count, Y direction, and color encoding from my code.
Cells are equal-sized, ordered left-to-right and bottom-to-top; views must be 1–30.
Set mode to interlaced and choose srgb or linear from the actual input.
Check texture.colorSpace to avoid duplicate exposure, tone mapping, or encoding.
Do not treat one ordinary image as 30 independently captured views.
Do not share texture objects across WebGL contexts.
Verify mapping with fixed-color views before connecting application textures.

Prompt E: Build a calibration page

Create a device-calibration page for Jupiter Interlace SDK.
Keep the v0.17.1 names and meaning: pitch, tan, offset.
Separate optical calibration from scene depth controls. Pitch is not lens spacing in millimeters.
Validate finite numbers, allow negative offsets, and support JSON import/export and named presets.
Explain that tan=0 selects the legacy center-view fallback, not a valid multi-view calibration.
Include 2D, single-view, and interlaced test patterns and show drawing-buffer dimensions and DPR.
Recheck the origin in fullscreen. Do not claim optical validation from an ordinary monitor screenshot.
Use supplied device values; leave unknown calibration pending.

Prompt F: Diagnose integration or performance

Diagnose my Jupiter Interlace SDK integration using the guide, console, and network evidence.
Identify whether the failure is downloading, parsing/decoding, single-view rendering, or interlacing.
Collect SDK/Three.js versions, WebGL extensions, model size, views, viewWidth,
physical output dimensions, getStats(), and the exact error. cpuMs is not GPU time or FPS.
If 2D works, check calibration, origin, DPR, color encoding, and post-processing.
If 2D fails, check model data, materials, lighting, camera, CORS, and decoder paths.
Reduce actual view count or per-view resolution for performance, but preserve final physical resolution.
Make the smallest justified fix, verify the affected flow, and identify remaining hardware checks.

Before accepting AI-generated integration, verify the real SDK API, decoder paths, local/remote GLB loading, error handling, live parameters, profile round trips, synchronized animation, and cleanup. Record browser tests separately from optical-display calibration.

12. API index

Object Main members
JupiterViewer scene, camera, renderer, controls, interlacer, loader, model
Viewer methods loadGLB, frameModel, setCalibration, setOptions, exportProfile, importProfile, onFrame, resize, render, start, stop, unload, dispose
InterlaceRenderer render, renderViews, compositeAtlas, setters, profile methods, getStats, outputTexture, subscribe, dispose
GLBLoader load(source, options), dispose()
GLBAsset scene, gltf, animations, mixer, play, pause, resume, update, isPaused, dispose
createCalibrationPanel Returns { element, dispose }
core Profile helpers, validation, logicalView, subpixelViews, renderedView, viewLogicalIndex, defaults

See dist/types/ for complete signatures. outputTexture is a read-only completed display-encoded frame; double buffering swaps its identity on subsequent frames. Do not dispose it or treat a cached reference as a permanent image.

13. Provenance and verification

The phase equation and camera convention originate from the supplied Jupiter World Viewer v0.17.1 project. This SDK preserves that mapping and adds independent GLB loading, configuration validation, color processing, and lifecycle management.

The runtime is pinned to Three.js r180. Loader configuration follows the official GLTFLoader documentation, while actual support is determined by the pinned implementation and tests. Features added to newer Three.js versions are not automatically available here.

See docs/validation.md for software verification. Optical quality, crosstalk, device performance, and viewing range still require calibration on the target display. Third-party license texts are preserved with the package.

This edition uses English documentation, example interfaces, and SDK messages. API identifiers, resource paths, and third-party license originals are preserved.