venkat112/venkat_glb_creation
0
1// Three.js GLB Generator2// Generates GLB files using Three.js and GLTFExporter3// Note: This requires three.js to be installed4// If three.js is not available, the server will fallback to Blender5 6export class ThreeJSGLBGenerator {7 constructor() {8 this.three = null;9 this.GLTFExporter = null;10 this.scene = null;11 this.objects = [];12 this.lights = [];13 }14 15 async loadThreeJS() {16 if (this.three && this.GLTFExporter) {17 return true;18 }19 20 try {21 const threeModule = await import('three');22 const exporterModule = await import('three/examples/jsm/exporters/GLTFExporter.js');23 24 this.three = threeModule;25 this.GLTFExporter = exporterModule.GLTFExporter;26 return true;27 } catch (error) {28 return false;29 }30 }31 32 async generate(sceneData, outputPath) {33 const fs = (await import('fs')).default;34 35 const loaded = await this.loadThreeJS();36 if (!loaded) {37 throw new Error('Three.js is not available. Install with: npm install three');38 }39 40 const THREE = this.three;41 const GLTFExporter = this.GLTFExporter;42 43 try {44 this.scene = new THREE.Scene();45 this.objects = [];46 this.lights = [];47 48 this.setupEnvironment(sceneData, THREE);49 50 if (sceneData.ground) {51 this.createGround(sceneData.ground, THREE);52 }53 54 if (sceneData.objects) {55 sceneData.objects.forEach(obj => this.createObject(obj, THREE));56 }57 58 if (sceneData.avatar && sceneData.avatar.present) {59 this.createAvatar(sceneData.avatar, THREE);60 }61 62 if (sceneData.lighting) {63 this.createLights(sceneData.lighting, THREE);64 }65 66 if (sceneData.camera) {67 this.createCamera(sceneData.camera, THREE);68 }69 70 await this.exportGLB(outputPath, THREE, GLTFExporter, fs);71 72 return { success: true, path: outputPath };73 } catch (error) {74 return { success: false, error: error.message };75 }76 }77 78 setupEnvironment(sceneData, THREE) {79 if (sceneData.lighting?.color) {80 this.scene.background = new THREE.Color(81 sceneData.lighting.color[0],82 sceneData.lighting.color[1],83 sceneData.lighting.color[2]84 );85 }86 87 if (sceneData.effects?.fog) {88 this.scene.fog = new THREE.FogExp2(89 new THREE.Color(0.8, 0.8, 0.9),90 0.0591 );92 }93 }94 95 createGround(groundData, THREE) {96 const size = groundData.size || 20;97 const geometry = new THREE.PlaneGeometry(size, size);98 99 const color = groundData.color || [0.5, 0.5, 0.5, 1];100 const material = new THREE.MeshStandardMaterial({101 color: new THREE.Color(color[0], color[1], color[2]),102 roughness: groundData.roughness || 0.8,103 metalness: groundData.metallic || 0.0104 });105 106 const mesh = new THREE.Mesh(geometry, material);107 mesh.rotation.x = -Math.PI / 2;108 mesh.name = "Ground";109 this.scene.add(mesh);110 this.objects.push(mesh);111 }112 113 createObject(objData, THREE) {114 let geometry;115 116 switch (objData.type) {117 case 'cube':118 const size = objData.size || 1;119 geometry = new THREE.BoxGeometry(size, size, size);120 break;121 case 'sphere':122 geometry = new THREE.SphereGeometry(objData.radius || 0.5, 32, 32);123 break;124 case 'cylinder':125 geometry = new THREE.CylinderGeometry(126 objData.radius || 0.5,127 objData.radius || 0.5,128 objData.size || 1,129 32130 );131 break;132 case 'cone':133 geometry = new THREE.ConeGeometry(134 objData.radius || 0.5,135 objData.size || 1,136 32137 );138 break;139 case 'plane':140 geometry = new THREE.PlaneGeometry(141 objData.size || 10,142 objData.size || 10143 );144 break;145 default:146 geometry = new THREE.BoxGeometry(1, 1, 1);147 }148 149 const color = objData.color || [0.8, 0.8, 0.8, 1];150 const material = new THREE.MeshStandardMaterial({151 color: new THREE.Color(color[0], color[1], color[2]),152 roughness: objData.roughness || 0.5,153 metalness: objData.metallic || 0.0,154 transparent: color[3] < 1,155 opacity: color[3] || 1156 });157 158 if (objData.emission) {159 material.emissive = new THREE.Color(160 objData.emission[0],161 objData.emission[1],162 objData.emission[2]163 );164 material.emissiveIntensity = objData.emissionStrength || 1.0;165 }166 167 const mesh = new THREE.Mesh(geometry, material);168 mesh.position.set(169 objData.location[0] || 0,170 objData.location[1] || 0,171 objData.location[2] || 0172 );173 mesh.rotation.set(174 objData.rotation[0] || 0,175 objData.rotation[1] || 0,176 objData.rotation[2] || 0177 );178 mesh.scale.set(179 objData.scale[0] || 1,180 objData.scale[1] || 1,181 objData.scale[2] || 1182 );183 mesh.name = objData.name || 'Object';184 185 this.scene.add(mesh);186 this.objects.push(mesh);187 }188 189 createAvatar(avatarData, THREE) {190 const group = new THREE.Group();191 group.name = avatarData.name || 'Avatar';192 193 const torso = new THREE.Mesh(194 new THREE.CylinderGeometry(0.25, 0.25, 0.8, 32),195 new THREE.MeshStandardMaterial({ color: 0xffccaa })196 );197 torso.position.y = 1.0;198 group.add(torso);199 200 const head = new THREE.Mesh(201 new THREE.SphereGeometry(0.18, 32, 32),202 new THREE.MeshStandardMaterial({ color: 0xffccaa })203 );204 head.position.y = 1.9;205 group.add(head);206 207 const leftArm = new THREE.Mesh(208 new THREE.BoxGeometry(0.2, 0.4, 0.2),209 new THREE.MeshStandardMaterial({ color: 0xffccaa })210 );211 leftArm.position.set(-0.45, 1.3, 0);212 group.add(leftArm);213 214 const rightArm = new THREE.Mesh(215 new THREE.BoxGeometry(0.2, 0.4, 0.2),216 new THREE.MeshStandardMaterial({ color: 0xffccaa })217 );218 rightArm.position.set(0.45, 1.3, 0);219 group.add(rightArm);220 221 const leftLeg = new THREE.Mesh(222 new THREE.BoxGeometry(0.25, 0.75, 0.25),223 new THREE.MeshStandardMaterial({ color: 0x4444ff })224 );225 leftLeg.position.set(-0.18, 0.5, 0);226 group.add(leftLeg);227 228 const rightLeg = new THREE.Mesh(229 new THREE.BoxGeometry(0.25, 0.75, 0.25),230 new THREE.MeshStandardMaterial({ color: 0x4444ff })231 );232 rightLeg.position.set(0.18, 0.5, 0);233 group.add(rightLeg);234 235 group.position.set(236 avatarData.position[0] || 0,237 avatarData.position[1] || 0,238 avatarData.position[2] || 0239 );240 group.scale.set(241 avatarData.scale || 1,242 avatarData.scale || 1,243 avatarData.scale || 1244 );245 246 this.scene.add(group);247 this.objects.push(group);248 }249 250 createLights(lightingData, THREE) {251 const ambientLight = new THREE.AmbientLight(252 new THREE.Color(lightingData.color || [1, 1, 1]),253 lightingData.ambient_strength || 0.3254 );255 this.scene.add(ambientLight);256 this.lights.push(ambientLight);257 258 if (lightingData.lights && lightingData.lights.length > 0) {259 lightingData.lights.forEach(light => {260 let lightObj;261 262 switch (light.type) {263 case 'SUN':264 case 'DIRECTIONAL':265 lightObj = new THREE.DirectionalLight(266 new THREE.Color(light.color || lightingData.color || [1, 1, 1]),267 light.energy || 1268 );269 lightObj.position.set(270 light.location[0] || 10,271 light.location[1] || -10,272 light.location[2] || 10273 );274 break;275 case 'POINT':276 lightObj = new THREE.PointLight(277 new THREE.Color(light.color || lightingData.color || [1, 1, 1]),278 light.energy || 50,279 100280 );281 lightObj.position.set(282 light.location[0] || 0,283 light.location[1] || 0,284 light.location[2] || 2285 );286 break;287 case 'SPOT':288 lightObj = new THREE.SpotLight(289 new THREE.Color(light.color || lightingData.color || [1, 1, 1]),290 light.energy || 50291 );292 lightObj.position.set(293 light.location[0] || 0,294 light.location[1] || 0,295 light.location[2] || 2296 );297 break;298 default:299 return;300 }301 302 if (lightObj) {303 this.scene.add(lightObj);304 this.lights.push(lightObj);305 }306 });307 }308 }309 310 createCamera(cameraData, THREE) {311 const camera = new THREE.PerspectiveCamera(312 cameraData.fov || 50,313 16 / 9,314 0.1,315 1000316 );317 camera.position.set(318 cameraData.position[0] || 4,319 cameraData.position[1] || -4,320 cameraData.position[2] || 2.2321 );322 camera.rotation.set(323 cameraData.rotation[0] || 1.05,324 cameraData.rotation[1] || 0,325 cameraData.rotation[2] || 0.78326 );327 this.scene.add(camera);328 }329 330 async exportGLB(outputPath, THREE, GLTFExporter, fs) {331 return new Promise((resolve, reject) => {332 // Polyfill FileReader for Node.js environment333 if (typeof global !== 'undefined' && !global.FileReader) {334 global.FileReader = class FileReader {335 constructor() {336 this.result = null;337 this.onload = null;338 this.onerror = null;339 }340 readAsArrayBuffer(blob) {341 // In Node.js, we'll handle this differently342 if (blob && blob.arrayBuffer) {343 blob.arrayBuffer().then(buffer => {344 this.result = buffer;345 if (this.onload) this.onload({ target: { result: buffer } });346 }).catch(err => {347 if (this.onerror) this.onerror(err);348 });349 }350 }351 };352 }353 354 const exporter = new GLTFExporter();355 const options = {356 binary: true,357 includeCustomExtensions: false, // Disable to avoid FileReader issues358 onlyVisible: false,359 truncateDrawRange: true,360 trs: false,361 animations: []362 };363 364 try {365 exporter.parse(366 this.scene,367 (result) => {368 try {369 // Handle both ArrayBuffer and Uint8Array370 let buffer;371 if (result instanceof ArrayBuffer) {372 buffer = Buffer.from(result);373 } else if (result instanceof Uint8Array) {374 buffer = Buffer.from(result);375 } else if (typeof result === 'string') {376 // If it's a string (JSON), convert to buffer377 buffer = Buffer.from(result, 'utf8');378 } else if (result && result.buffer) {379 // Handle TypedArray380 buffer = Buffer.from(result.buffer);381 } else {382 // Try to convert directly383 buffer = Buffer.from(result);384 }385 386 if (!buffer || buffer.length === 0) {387 reject(new Error('GLB export resulted in empty buffer'));388 return;389 }390 391 fs.writeFileSync(outputPath, buffer);392 resolve();393 } catch (error) {394 reject(new Error(`Failed to write GLB file: ${error.message}`));395 }396 },397 (error) => {398 // Check if error is about FileReader399 const errorMsg = error?.message || String(error);400 if (errorMsg.includes('FileReader')) {401 reject(new Error('GLTFExporter requires browser environment. Please install three.js properly or use Blender fallback.'));402 } else {403 reject(new Error(`GLTFExporter error: ${errorMsg}`));404 }405 },406 options407 );408 } catch (error) {409 const errorMsg = error?.message || String(error);410 if (errorMsg.includes('FileReader')) {411 reject(new Error('GLTFExporter requires browser environment. Please install three.js properly.'));412 } else {413 reject(error);414 }415 }416 });417 }418}419 