// Scene Graph Generator // Converts parsed scene data into a structured scene graph JSON export class SceneGraphGenerator { constructor() { this.nodeIdCounter = 0; } generate(sceneData) { const sceneGraph = { version: "2.0", metadata: { generator: "GLB Generation Engine", sceneName: sceneData.sceneName || "generated_scene", timestamp: new Date().toISOString() }, scene: { nodes: [], extensions: {}, extras: {} }, nodes: [], meshes: [], materials: [], textures: [], images: [], lights: [], cameras: [], animations: [], audio: { ambient: [], spatial: [] }, physics: { enabled: false, gravity: [0, 0, -9.8] }, extras: {} // Initialize extras at root level }; const rootNode = this.createNode("Scene", [0, 0, 0], [0, 0, 0], [1, 1, 1]); sceneGraph.scene.nodes = [rootNode.id]; sceneGraph.nodes.push(rootNode); this.generateEnvironment(sceneGraph, sceneData); this.generateGround(sceneGraph, sceneData); this.generateObjects(sceneGraph, sceneData); if (sceneData.avatar && sceneData.avatar.present) { this.generateAvatar(sceneGraph, sceneData); } this.generateLighting(sceneGraph, sceneData); this.generateCamera(sceneGraph, sceneData); this.generateAudio(sceneGraph, sceneData); this.generateEffects(sceneGraph, sceneData); this.generateAnimations(sceneGraph, sceneData); return sceneGraph; } createNode(name, translation = [0, 0, 0], rotation = [0, 0, 0], scale = [1, 1, 1], extras = {}) { const node = { id: this.nodeIdCounter++, name: name, translation: translation, rotation: rotation, scale: scale, children: [], mesh: null, camera: null, light: null, extensions: {}, extras: extras }; return node; } generateEnvironment(sceneGraph, sceneData) { const envNode = this.createNode( `Environment_${sceneData.environment}`, [0, 0, 0], [0, 0, 0], [1, 1, 1], { type: "environment", environment: sceneData.environment, hdri: sceneData.lighting?.hdri || null, skybox: sceneData.environment } ); sceneGraph.nodes[0].children.push(envNode.id); sceneGraph.nodes.push(envNode); } generateGround(sceneGraph, sceneData) { if (!sceneData.ground) return; const groundNode = this.createNode( "Ground", [0, 0, 0], [0, 0, 0], [sceneData.ground.size || 20, sceneData.ground.size || 20, 1], { type: "ground", texture: sceneData.ground.texture, material: "ground_material" } ); const meshId = sceneGraph.meshes.length; const materialId = this.createMaterial( sceneGraph, "ground_material", sceneData.ground.color || [0.5, 0.5, 0.5, 1], sceneData.ground.roughness || 0.8, sceneData.ground.metallic || 0.0 ); groundNode.mesh = meshId; sceneGraph.meshes.push({ id: meshId, name: "GroundMesh", primitives: [{ attributes: { POSITION: "ground_positions", NORMAL: "ground_normals", TEXCOORD_0: "ground_texcoords" }, indices: "ground_indices", material: materialId, mode: 4 }] }); sceneGraph.nodes[0].children.push(groundNode.id); sceneGraph.nodes.push(groundNode); } generateObjects(sceneGraph, sceneData) { if (!sceneData.objects || sceneData.objects.length === 0) return; const objectsNode = this.createNode("Objects", [0, 0, 0], [0, 0, 0], [1, 1, 1]); sceneGraph.nodes[0].children.push(objectsNode.id); sceneGraph.nodes.push(objectsNode); sceneData.objects.forEach((obj, index) => { const objNode = this.createNode( obj.name || `Object_${index}`, obj.location || [0, 0, 0], obj.rotation || [0, 0, 0], obj.scale || [1, 1, 1], { type: "object", primitive: obj.type, size: obj.size, radius: obj.radius } ); const materialId = this.createMaterial( sceneGraph, `material_${obj.name || index}`, obj.color || [0.8, 0.8, 0.8, 1], obj.roughness || 0.5, obj.metallic || 0.0, obj.emission, obj.emissionStrength ); const meshId = sceneGraph.meshes.length; objNode.mesh = meshId; sceneGraph.meshes.push({ id: meshId, name: `${obj.name || `Object_${index}`}_Mesh`, primitives: [{ attributes: { POSITION: `${obj.name}_positions`, NORMAL: `${obj.name}_normals` }, material: materialId, mode: 4 }], extras: { primitiveType: obj.type, size: obj.size, radius: obj.radius } }); if (obj.animation) { objNode.extras.animation = obj.animation; } objectsNode.children.push(objNode.id); sceneGraph.nodes.push(objNode); }); } generateAvatar(sceneGraph, sceneData) { const avatar = sceneData.avatar; const avatarNode = this.createNode( avatar.name || "Avatar", avatar.position || [0, 0, 0], [0, 0, 0], [avatar.scale || 1.0, avatar.scale || 1.0, avatar.scale || 1.0], { type: "avatar", action: avatar.action || "stand", rigged: true, bones: ["root", "spine", "head", "left_arm", "right_arm", "left_leg", "right_leg"] } ); const meshId = sceneGraph.meshes.length; const materialId = this.createMaterial( sceneGraph, "avatar_material", [1, 0.8, 0.6, 1], 0.5, 0.0 ); avatarNode.mesh = meshId; sceneGraph.meshes.push({ id: meshId, name: "AvatarMesh", primitives: [{ attributes: { POSITION: "avatar_positions", NORMAL: "avatar_normals", JOINTS_0: "avatar_joints", WEIGHTS_0: "avatar_weights" }, material: materialId, mode: 4 }], extras: { skinned: true, skeleton: "avatar_skeleton" } }); if (avatar.action && avatar.action !== "stand") { avatarNode.extras.animation = { type: avatar.action, speed: avatar.animation?.speed || 1.0 }; } sceneGraph.nodes[0].children.push(avatarNode.id); sceneGraph.nodes.push(avatarNode); } generateLighting(sceneGraph, sceneData) { if (!sceneData.lighting) return; const lighting = sceneData.lighting; const lightsNode = this.createNode("Lights", [0, 0, 0], [0, 0, 0], [1, 1, 1]); sceneGraph.nodes[0].children.push(lightsNode.id); sceneGraph.nodes.push(lightsNode); if (lighting.lights && lighting.lights.length > 0) { lighting.lights.forEach((light, index) => { const lightNode = this.createNode( `Light_${light.type}_${index}`, light.location || [0, 0, 0], [0, 0, 0], [1, 1, 1], { type: "light", lightType: light.type, energy: light.energy || 1.0, color: light.color || lighting.color || [1, 1, 1], angle: light.angle } ); const lightId = sceneGraph.lights.length; lightNode.light = lightId; sceneGraph.lights.push({ id: lightId, name: `Light_${light.type}_${index}`, type: light.type, color: light.color || lighting.color || [1, 1, 1], intensity: light.energy || 1.0, range: light.range || 100, innerConeAngle: light.innerConeAngle, outerConeAngle: light.outerConeAngle }); lightsNode.children.push(lightNode.id); sceneGraph.nodes.push(lightNode); }); } // Ensure extras exists before setting ambientLight if (!sceneGraph.extras) { sceneGraph.extras = {}; } // Ensure extras exists if (!sceneGraph.extras) { sceneGraph.extras = {}; } sceneGraph.extras.ambientLight = { color: lighting.color || [1, 1, 1], strength: lighting.ambient_strength || 0.3 }; } generateCamera(sceneGraph, sceneData) { if (!sceneData.camera) return; const camera = sceneData.camera; const cameraNode = this.createNode( "Camera", camera.position || [4, -4, 2.2], camera.rotation || [1.05, 0, 0.78], [1, 1, 1] ); const cameraId = sceneGraph.cameras.length; cameraNode.camera = cameraId; sceneGraph.cameras.push({ id: cameraId, name: "MainCamera", type: "perspective", perspective: { aspectRatio: 16 / 9, yfov: (camera.fov || 50) * Math.PI / 180, znear: 0.1, zfar: 1000 } }); sceneGraph.nodes[0].children.push(cameraNode.id); sceneGraph.nodes.push(cameraNode); } generateAudio(sceneGraph, sceneData) { if (!sceneData.audio) return; sceneGraph.audio.ambient = sceneData.audio.ambient || []; sceneGraph.audio.spatial = sceneData.audio.spatial || []; } generateEffects(sceneGraph, sceneData) { if (!sceneData.effects) return; const effectsNode = this.createNode("Effects", [0, 0, 0], [0, 0, 0], [1, 1, 1], { type: "effects", effects: sceneData.effects }); sceneGraph.nodes[0].children.push(effectsNode.id); sceneGraph.nodes.push(effectsNode); if (sceneData.effects.fog) { sceneGraph.extras.fog = { type: "fog", color: [0.8, 0.8, 0.9, 1], density: 0.05, near: 1, far: 50 }; } } generateAnimations(sceneGraph, sceneData) { const animations = []; if (sceneData.avatar && sceneData.avatar.present && sceneData.avatar.action) { animations.push({ name: "avatar_animation", type: sceneData.avatar.action, target: "avatar", duration: 2.0, channels: [] }); } sceneData.objects?.forEach((obj, index) => { if (obj.animation) { animations.push({ name: `${obj.name}_animation`, type: obj.animation.type, target: obj.name, duration: 3.0, channels: [] }); } }); sceneGraph.animations = animations; } createMaterial(sceneGraph, name, baseColor, roughness, metallic, emission = null, emissionStrength = null) { const materialId = sceneGraph.materials.length; const material = { id: materialId, name: name, pbrMetallicRoughness: { baseColorFactor: baseColor, metallicFactor: metallic, roughnessFactor: roughness } }; if (emission) { material.emissiveFactor = emission.slice(0, 3); if (emissionStrength) { material.extras = { emissionStrength: emissionStrength }; } } sceneGraph.materials.push(material); return materialId; } }