WebGPU Scene Objects System

This document describes the simplified scene objects system that provides a clean, unified approach for creating and managing scene objects with automatic model registration and seamless UI integration.

Overview

The scene objects system provides:

Architecture

Core Components

Key Benefits

  1. Simplicity: One class handles all object types
  2. Automation: Models automatically become scene objects
  3. Rich UI: Better names and organization in menus
  4. Performance: Automatic culling optimization
  5. Extensibility: Easy to add new object types
  6. Material Management: Centralized material system with registry

Creating Scene Objects

Basic Usage

import { createSceneObjectDefault } from "../sceneObjects/index.ts";

// Create any registered object type
createSceneObjectDefault(
  "cube",           // Object type
  sceneState,       // Scene state
  parentId,         // Parent node ID
  "My Cube",        // Display name
  resourceManager   // Resource manager
);

Using Presets

import { createSceneObject, SCENE_OBJECT_PRESETS } from "../sceneObjects/index.ts";

// Create with preset configuration
createSceneObject(
  "teapot",
  sceneState,
  parentId,
  SCENE_OBJECT_PRESETS.TEAPOT_MODEL,
  resourceManager
);

Using Materials

import { withMaterial, SCENE_OBJECT_PRESETS } from "../sceneObjects/index.ts";
import { getMaterial } from "../materials/index.ts";

// Apply built-in material to preset
const goldTeapot = withMaterial(
  SCENE_OBJECT_PRESETS.TEAPOT_MODEL,
  "gold",  // Material name from registry
  "Golden Teapot"
);

createSceneObject("teapot", sceneState, parentId, goldTeapot, resourceManager);

Custom Configuration

const customConfig: SceneObjectConfig = {
  name: "Floating Teapot",
  transform: {
    position: vec3.fromValues(0, 5, 0),
    rotation: vec3.fromValues(0, Math.PI / 4, 0),
    scale: vec3.fromValues(1.5, 1.5, 1.5)
  },
  renderable: { type: "teapot" },
  behavior: {
    type: "spinning",
    rotationSpeed: vec3.fromValues(0.1, 0.3, 0.05),
    rotationEnabled: [true, true, false]
  }
};

createSceneObject("teapot", sceneState, parentId, customConfig, resourceManager);

Built-in Scene Objects

Primitive Objects

Container Objects

Model Objects (Auto-registered)

Automatic Model Registration

When you load 3D models, they automatically become available as scene objects:

// 1. Load models (done automatically in WebGPU sketch)
await loadModelAndCreateMeshes("assets/models/teapot.obj", config);

// 2. Auto-register loaded models as scene objects
registerLoadedModelsAsSceneObjects();

// 3. Models now appear in UI automatically with proper names:
//    "teapot" becomes "Utah Teapot" in the "3D Models" category

Display Name Formatting

The system automatically formats model names for better UI:

// Automatic formatting rules:
"teapot""Utah Teapot"
"suzanne""Suzanne (Blender Monkey)"
"medieval_castle""Medieval Castle"

UI Integration

Add Menu Organization

Objects are automatically organized by category in the "Add +" menu:

Smart Defaults

Implementation Details

BasicMeshObject Class

export class BasicMeshObject implements SceneObjectInterface {
  readonly objectType: string;
  readonly description: string;
  readonly metadata?: SceneObjectMetadata;

  constructor(
    meshType: string,
    description?: string,
    metadata?: SceneObjectMetadata
  ) {
    // Simple constructor with optional metadata
  }

  getDisplayName(): string {
    return this.metadata?.displayName || 
           this.objectType.charAt(0).toUpperCase() + this.objectType.slice(1);
  }

  getCategory(): string {
    return this.metadata?.category || "primitive";
  }
}

Factory Creation

// Built-in objects created with rich metadata
export function createBasicMeshObjects(): Record<string, SceneObjectInterface> {
  const objects: Record<string, SceneObjectInterface> = {};
  
  for (const metadata of BUILTIN_OBJECT_METADATA) {
    objects[metadata.type] = new BasicMeshObject(
      metadata.type,
      metadata.description,
      metadata
    );
  }
  
  return objects;
}

Automatic Registration

// Called after models are loaded
export function registerLoadedModelsAsSceneObjects(): void {
  // Discovers loaded models from mesh registry
  // Creates BasicMeshObject instances with generated metadata
  // Registers them with scene object registry
  // Makes them available in UI automatically
}

Culling Integration

Scene objects automatically benefit from the culling system:

// Teapot gets no culling (defined in culling.ts)
const teapotObject = new BasicMeshObject("teapot", description, metadata);
// Culling system sees "teapot" name and applies no culling
// Perfect for complex models with interior details

Migration from Old System

Before (Verbose)

// Old: Separate classes for each type
import { CubeObject, TriangleObject, TeapotObject } from "./sceneObjects/index.ts";

const registry = getSceneObjectRegistry();
registry.register(new CubeObject());
registry.register(new TriangleObject());
registry.register(new TeapotObject());

After (Simple)

// New: Automatic registration
import { registerBuiltinRenderables } from "../renderable/index.ts";

registerBuiltinRenderables(); // Registers everything automatically

Migration Steps

  1. Remove manual registrations: No need to register individual object types
  2. Use automatic system: Call registerBuiltinRenderables() once
  3. Update object creation: Use createSceneObjectDefault() or createSceneObject()
  4. Remove object imports: No need to import individual object classes
  5. Use presets: Leverage predefined configurations instead of manual setup

Best Practices

1. Use Descriptive Model Names

// Good model filenames:
"medieval_castle.obj""Medieval Castle"
"glass_window.obj""Glass Window" 
"utah_teapot.obj""Utah Teapot"

// Less optimal:
"model01.obj""Model01"
"mesh.obj""Mesh"

2. Leverage Presets

// Instead of manual configuration:
const config = {
  name: "Spinning Teapot",
  transform: { position: vec3.fromValues(0, -3, 0) },
  renderable: { type: "teapot" },
  behavior: { type: "spinning", rotationSpeed: vec3.fromValues(0.06, 0.14, 0.09) }
};

// Use presets:
SCENE_OBJECT_PRESETS.TEAPOT_MODEL; // Already configured

3. Let Automation Work

// Don't manually register models:
// ❌ registry.register(new TeapotObject());

// Load and let auto-registration handle it:
// ✅ await loadModelAndCreateMeshes("assets/models/teapot.obj", config);
//    registerLoadedModelsAsSceneObjects();

4. Use Categories for Organization

The system automatically categorizes objects:

5. Customize Only When Needed

// For special cases, create custom objects:
const specialObject = new BasicMeshObject(
  "special_type",
  "Custom description",
  {
    type: "special_type",
    displayName: "Special Object",
    category: "model",
    description: "Custom object with special properties"
  }
);

Hierarchical Objects

Scene objects fully support parent-child relationships:

const parentConfig: SceneObjectConfig = {
  name: "Solar System",
  renderable: { type: "sphere" }, // Sun
  children: [
    {
      name: "Planet",
      transform: { position: vec3.fromValues(5, 0, 0) },
      renderable: { type: "sphere" },
      children: [
        {
          name: "Moon",
          transform: { position: vec3.fromValues(2, 0, 0) },
          renderable: { type: "sphere" }
        }
      ]
    }
  ]
};

createSceneObject("sphere", sceneState, parentId, parentConfig, resourceManager);

Advanced Usage

Custom Object Types

// Create custom object with metadata
const customMetadata: SceneObjectMetadata = {
  type: "custom_mesh",
  displayName: "Custom Mesh",
  category: "model",
  description: "My custom mesh type"
};

const customObject = new BasicMeshObject(
  "custom_mesh",
  "Custom mesh description",
  customMetadata
);

// Register it
getSceneObjectRegistry().register(customObject);

Runtime Object Discovery

// Get all available object types
const registry = getSceneObjectRegistry();
const availableTypes = registry.getRegisteredTypes();

// Check if specific type exists
if (registry.has("teapot")) {
  // Teapot is available
}

// Get object metadata
const teapotObject = registry.get("teapot");
if (teapotObject instanceof BasicMeshObject) {
  console.log("Display name:", teapotObject.getDisplayName());
  console.log("Category:", teapotObject.getCategory());
}

Material Registry System

Overview

The Material Registry provides centralized management of materials with the following features:

Built-in Materials

The system includes these built-in materials:

Flat Colors: white, black, red, green, blue, yellow, cyan, magenta

Phong Materials: gold, silver, copper, plastic_red, plastic_blue, matte_white

Material Registry Usage

import { getMaterialRegistry, registerBuiltinMaterials } from "../materials/index.ts";

// Initialize the system (call once at startup)
registerBuiltinMaterials();

const registry = getMaterialRegistry();

// Check available materials
console.log("Available materials:", registry.getRegisteredNames());

// Get materials by category
const phongMaterials = registry.getByCategory("phong");
const flatMaterials = registry.getByCategory("flat");

// Use material with scene objects
const goldCube = withMaterial(SCENE_OBJECT_PRESETS.SPINNING_CUBE, "gold");

Creating Custom Materials

import { getMaterialRegistry, phongMaterial } from "../materials/index.ts";

const registry = getMaterialRegistry();

// Register a new custom material
registry.register(
  "neon_pink",
  phongMaterial([1.0, 0.2, 0.6, 1.0], [0.9, 0.9, 0.9], 64),
  false, // not readonly
  "custom",
  "Bright neon pink material"
);

// Clone and modify existing materials
registry.clone("gold", "bright_gold");
registry.update("bright_gold", {
  shininess: 128,
  specular: [1.0, 1.0, 0.8]
});

Enhanced Preset Customization

import { PresetBuilder, withMaterial, withTransform } from "../sceneObjects/index.ts";

// Method 1: Individual functions
const customTeapot = withMaterial(
  withTransform(SCENE_OBJECT_PRESETS.TEAPOT_MODEL, {
    position: vec3.fromValues(0, 2, 0),
    scale: vec3.fromValues(1.5, 1.5, 1.5)
  }),
  "silver",
  "Large Silver Teapot"
);

// Method 2: Fluent API
const fluentTeapot = PresetBuilder
  .from(SCENE_OBJECT_PRESETS.TEAPOT_MODEL)
  .withName("Custom Teapot")
  .withMaterial("gold")
  .withTransform({
    position: vec3.fromValues(0, 2, 0),
    scale: vec3.fromValues(1.5, 1.5, 1.5)
  })
  .withSpinning(vec3.fromValues(0.1, 0.2, 0.05))
  .build();

Material Protection

Built-in materials are protected from modification:

// This will fail - built-in materials are readonly
registry.update("gold", { shininess: 999 }); // Returns false

// This works - clone first, then modify
registry.clone("gold", "my_gold");
registry.update("my_gold", { shininess: 999 }); // Returns true

API Reference

Core Functions

Factory Functions

Object Classes

Configuration Types

Presets

The system is now much simpler while being more powerful - objects are created automatically, appear in the UI with good names, and work seamlessly with the rendering and culling systems.