Spaces:
Runtime error
Runtime error
| // 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; | |
| } | |
| } | |