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.
The scene objects system provides:
BasicMeshObject class handles all mesh typesimport { 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
);
import { createSceneObject, SCENE_OBJECT_PRESETS } from "../sceneObjects/index.ts";
// Create with preset configuration
createSceneObject(
"teapot",
sceneState,
parentId,
SCENE_OBJECT_PRESETS.TEAPOT_MODEL,
resourceManager
);
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);
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);
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
The system automatically formats model names for better UI:
// Automatic formatting rules:
"teapot" → "Utah Teapot"
"suzanne" → "Suzanne (Blender Monkey)"
"medieval_castle" → "Medieval Castle"
Objects are automatically organized by category in the "Add +" menu:
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";
}
}
// 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;
}
// 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
}
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
// 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());
// New: Automatic registration
import { registerBuiltinRenderables } from "../renderable/index.ts";
registerBuiltinRenderables(); // Registers everything automatically
registerBuiltinRenderables() oncecreateSceneObjectDefault() or createSceneObject()// 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"
// 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
// Don't manually register models:
// ❌ registry.register(new TeapotObject());
// Load and let auto-registration handle it:
// ✅ await loadModelAndCreateMeshes("assets/models/teapot.obj", config);
// registerLoadedModelsAsSceneObjects();
The system automatically categorizes objects:
// 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"
}
);
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);
// 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);
// 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());
}
The Material Registry provides centralized management of materials with the following features:
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
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");
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]
});
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();
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
createSceneObject(type, sceneState, parentId, config, resourceManager) - Create with full configcreateSceneObjectDefault(type, sceneState, parentId, name, resourceManager) - Create with defaultsgetSceneObjectRegistry() - Get the global registryregisterBuiltinRenderables() - Register all built-in types (call once)createBasicMeshObjects() - Create all built-in objectsregisterLoadedModelsAsSceneObjects() - Auto-register loaded modelsBasicMeshObject - Universal scene object classEmptyObject - Special container object (extends BasicMeshObject)SceneObjectConfig - Complete object configurationSceneObjectMetadata - Rich object informationSceneObjectResult - Creation result with node IDsSCENE_OBJECT_PRESETS - All available presetsgetPreset(name) - Get specific presetcustomizePreset(preset, overrides) - Customize existing presetThe 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.