or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

accessibility.mdanimation.mdannotation.mdar.mdcontrols.mdenvironment.mdindex.mdloading.mdscene-graph.md
tile.json

scene-graph.mddocs/

Scene Graph

Access and manipulate the loaded 3D model's scene graph, including materials, variants, transformations, and export capabilities.

Capabilities

Material Variants

Switch between different material configurations defined in the glTF model.

/**
 * Name of the current material variant to display
 * Must be one of the available variants from the model
 */
variantName: string;

/**
 * List of all material variants available in the loaded model
 * Empty array if model has no variants defined
 */
readonly availableVariants: Array<string>;

/**
 * The loaded model object (readonly)
 * Provides access to the scene graph structure
 */
readonly model?: Model;

/**
 * Original glTF JSON data (readonly) 
 * Access to the raw glTF specification data
 */
readonly originalGltfJson: GLTF | null;

Usage:

<!-- Model with multiple material variants -->
<model-viewer 
  src="models/car.glb"
  variant-name="red_paint">
</model-viewer>
// Check available variants after model loads
modelViewer.addEventListener('load', () => {
  console.log('Available variants:', modelViewer.availableVariants);
  // ['red_paint', 'blue_paint', 'silver_metallic', 'carbon_fiber']
});

// Switch variants dynamically
function switchToBlue() {
  if (modelViewer.availableVariants.includes('blue_paint')) {
    modelViewer.variantName = 'blue_paint';
  }
}

// Create variant selector UI
function createVariantSelector() {
  const select = document.createElement('select');
  modelViewer.availableVariants.forEach(variant => {
    const option = document.createElement('option');
    option.value = variant;
    option.textContent = variant.replace(/_/g, ' ').toUpperCase();
    select.appendChild(option);
  });
  
  select.addEventListener('change', (e) => {
    modelViewer.variantName = e.target.value;
  });
  
  return select;
}

Model Transformation

Control the overall position, rotation, and scale of the model.

/**
 * Model orientation in 3D space
 * Format: "x y z" where each component is rotation in degrees
 */
orientation: string;

/**
 * Model scaling factor
 * Format: "x y z" for non-uniform scaling, or single value for uniform
 * Can use units like "2m 2m 2m" or percentage "200% 200% 200%"
 */
scale: string;

Usage:

<!-- Rotate model 45 degrees around Y axis -->
<model-viewer 
  src="models/building.glb"
  orientation="0deg 45deg 0deg">
</model-viewer>

<!-- Scale model to 2x size -->
<model-viewer 
  src="models/miniature.glb"
  scale="2 2 2">
</model-viewer>

<!-- Non-uniform scaling with units -->
<model-viewer 
  src="models/logo.glb"
  scale="3m 1m 1m">
</model-viewer>
// Animate rotation
let rotation = 0;
function rotateModel() {
  rotation += 1;
  modelViewer.orientation = `0deg ${rotation}deg 0deg`;
  requestAnimationFrame(rotateModel);
}

// Interactive scaling
const scaleSlider = document.getElementById('scale');
scaleSlider.addEventListener('input', (e) => {
  const scale = e.target.value;
  modelViewer.scale = `${scale} ${scale} ${scale}`;
});

Scene Export

Export the current model state as glTF data for saving or processing.

/**
 * Export the current scene as glTF/GLB data
 * @param options - Export configuration options
 * @returns Promise resolving to Blob containing glTF data
 */
exportScene(options?: ExportOptions): Promise<Blob>;

interface ExportOptions {
  /** Export as binary GLB format (true) or text glTF (false) */
  binary?: boolean;
  /** Include transformation matrices (translation, rotation, scale) */
  trs?: boolean;
  /** Export only visible objects */
  onlyVisible?: boolean;
  /** Truncate draw ranges to visible geometry */
  truncateDrawRange?: boolean;
  /** Embed images in glTF instead of external references */
  embedImages?: boolean;
  /** Maximum texture resolution in pixels */
  maxTextureSize?: number;
}

Usage:

// Basic export as GLB
async function exportModel() {
  try {
    const blob = await modelViewer.exportScene({ binary: true });
    
    // Create download link
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'exported-model.glb';
    a.click();
    URL.revokeObjectURL(url);
  } catch (error) {
    console.error('Export failed:', error);
  }
}

// Export with optimization
async function exportOptimized() {
  const blob = await modelViewer.exportScene({
    binary: true,
    onlyVisible: true,
    embedImages: true,
    maxTextureSize: 1024,
    truncateDrawRange: true
  });
  
  return blob;
}

// Export current variant as separate file
async function exportCurrentVariant() {
  const variant = modelViewer.variantName || 'default';
  const blob = await modelViewer.exportScene({
    binary: true,
    trs: true
  });
  
  const fileName = `model-${variant}.glb`;
  downloadBlob(blob, fileName);
}

Texture Management

Create and manage textures dynamically.

/**
 * Create a texture from image URI for use in materials
 * @param uri - Image URL or data URI
 * @param type - Optional texture type specification
 * @returns Promise resolving to ModelViewer texture or null if failed
 */
createTexture(uri: string, type?: string): Promise<ModelViewerTexture | null>;

/**
 * Create a texture from Lottie animation file
 * @param uri - Lottie JSON file URL
 * @param quality - Rendering quality (default: 1)
 * @returns Promise resolving to animated texture
 */
createLottieTexture(uri: string, quality?: number): Promise<ModelViewerTexture | null>;

/**
 * Create a texture from video element for dynamic content
 * @param uri - Video file URL
 * @returns Video texture object
 */
createVideoTexture(uri: string): ModelViewerTexture;

/**
 * Create a texture from HTML5 canvas for procedural content
 * @returns Canvas texture object for custom drawing
 */
createCanvasTexture(): ModelViewerTexture;

/**
 * Find material at specific screen coordinates via raycasting
 * @param pixelX - X coordinate on screen
 * @param pixelY - Y coordinate on screen  
 * @returns Material at that point or null if no intersection
 */
materialFromPoint(pixelX: number, pixelY: number): Material | null;

Usage:

// Create texture from uploaded image
async function applyCustomTexture(imageFile) {
  const dataURL = await fileToDataURL(imageFile);
  const texture = await modelViewer.createTexture(dataURL);
  
  if (texture) {
    console.log('Texture created successfully');
    // Apply to material (requires accessing scene graph)
    applyTextureToMaterial(texture);
  }
}

// Create texture from URL
async function loadEnvironmentTexture() {
  const texture = await modelViewer.createTexture('textures/environment.jpg');
  return texture;
}

function fileToDataURL(file) {
  return new Promise((resolve) => {
    const reader = new FileReader();
    reader.onload = e => resolve(e.target.result);
    reader.readAsDataURL(file);  
  });
}

Advanced Scene Manipulation

Accessing Three.js Objects

For advanced users who need direct access to the Three.js scene graph:

// Access the Three.js scene (requires knowledge of internals)
function getThreeScene() {
  // Note: This accesses private symbols and may break in updates
  return modelViewer[$scene];
}

// Find specific objects in the scene
function findObjectByName(name) {
  const scene = getThreeScene();
  return scene.getObjectByName(name);
}

// Modify materials directly
function modifyMaterial(materialName, properties) {
  const scene = getThreeScene();
  scene.traverse((child) => {
    if (child.material && child.material.name === materialName) {
      Object.assign(child.material, properties);
      child.material.needsUpdate = true;
    }
  });
}

Dynamic Variant Creation

Create custom material variants programmatically:

class VariantCreator {
  constructor(modelViewer) {
    this.modelViewer = modelViewer;
    this.customVariants = new Map();
  }
  
  async createVariant(name, materialConfig) {
    // Store variant configuration
    this.customVariants.set(name, materialConfig);
    
    // Apply variant
    await this.applyVariant(name);
  }
  
  async applyVariant(name) {
    if (this.customVariants.has(name)) {
      const config = this.customVariants.get(name);
      await this.applyMaterialConfig(config);
    } else if (this.modelViewer.availableVariants.includes(name)) {
      this.modelViewer.variantName = name;
    }
  }
  
  async applyMaterialConfig(config) {
    const scene = this.modelViewer[$scene];
    
    for (const [materialName, properties] of Object.entries(config)) {
      scene.traverse(async (child) => {
        if (child.material && child.material.name === materialName) {
          // Apply properties
          Object.assign(child.material, properties);
          
          // Handle texture properties
          if (properties.map && typeof properties.map === 'string') {
            const texture = await this.modelViewer.createTexture(properties.map);
            child.material.map = texture;
          }
          
          child.material.needsUpdate = true;
        }
      });
    }
  }
}

// Usage
const variantCreator = new VariantCreator(modelViewer);

// Create custom gold variant
variantCreator.createVariant('custom_gold', {
  'body_material': {
    color: 0xffd700,
    metalness: 1.0,
    roughness: 0.1
  },
  'trim_material': {
    color: 0xffffff,
    metalness: 0.0,
    roughness: 0.8
  }
});

Batch Operations

Perform operations on multiple scene objects efficiently:

class SceneBatch {
  constructor(modelViewer) {
    this.modelViewer = modelViewer;
    this.operations = [];
  }
  
  // Queue operations
  setMaterialProperty(materialName, property, value) {
    this.operations.push({
      type: 'material',
      target: materialName,
      property,
      value
    });
    return this;
  }
  
  setObjectVisibility(objectName, visible) {
    this.operations.push({
      type: 'visibility',
      target: objectName,
      visible
    });
    return this;
  }
  
  // Execute all queued operations
  execute() {
    const scene = this.modelViewer[$scene];
    
    scene.traverse((child) => {
      this.operations.forEach(op => {
        switch (op.type) {
          case 'material':
            if (child.material && child.material.name === op.target) {
              child.material[op.property] = op.value;
              child.material.needsUpdate = true;
            }
            break;
          case 'visibility':
            if (child.name === op.target) {
              child.visible = op.visible;
            }
            break;
        }
      });
    });
    
    // Clear operations
    this.operations = [];
    return this;
  }
}

// Usage - batch multiple changes for performance
const batch = new SceneBatch(modelViewer);
batch
  .setMaterialProperty('wheel_material', 'color', 0xff0000)
  .setMaterialProperty('body_material', 'roughness', 0.2)
  .setObjectVisibility('interior', false)
  .execute();