venkat_glb_creation / scripts /glb_generator_threejs.js
Antigravity AI
Initial HF commit
37ed7e4
Raw
History Blame Contribute Delete
12 kB
// Three.js GLB Generator
// Generates GLB files using Three.js and GLTFExporter
// Note: This requires three.js to be installed
// If three.js is not available, the server will fallback to Blender
export class ThreeJSGLBGenerator {
constructor() {
this.three = null;
this.GLTFExporter = null;
this.scene = null;
this.objects = [];
this.lights = [];
}
async loadThreeJS() {
if (this.three && this.GLTFExporter) {
return true;
}
try {
const threeModule = await import('three');
const exporterModule = await import('three/examples/jsm/exporters/GLTFExporter.js');
this.three = threeModule;
this.GLTFExporter = exporterModule.GLTFExporter;
return true;
} catch (error) {
return false;
}
}
async generate(sceneData, outputPath) {
const fs = (await import('fs')).default;
const loaded = await this.loadThreeJS();
if (!loaded) {
throw new Error('Three.js is not available. Install with: npm install three');
}
const THREE = this.three;
const GLTFExporter = this.GLTFExporter;
try {
this.scene = new THREE.Scene();
this.objects = [];
this.lights = [];
this.setupEnvironment(sceneData, THREE);
if (sceneData.ground) {
this.createGround(sceneData.ground, THREE);
}
if (sceneData.objects) {
sceneData.objects.forEach(obj => this.createObject(obj, THREE));
}
if (sceneData.avatar && sceneData.avatar.present) {
this.createAvatar(sceneData.avatar, THREE);
}
if (sceneData.lighting) {
this.createLights(sceneData.lighting, THREE);
}
if (sceneData.camera) {
this.createCamera(sceneData.camera, THREE);
}
await this.exportGLB(outputPath, THREE, GLTFExporter, fs);
return { success: true, path: outputPath };
} catch (error) {
return { success: false, error: error.message };
}
}
setupEnvironment(sceneData, THREE) {
if (sceneData.lighting?.color) {
this.scene.background = new THREE.Color(
sceneData.lighting.color[0],
sceneData.lighting.color[1],
sceneData.lighting.color[2]
);
}
if (sceneData.effects?.fog) {
this.scene.fog = new THREE.FogExp2(
new THREE.Color(0.8, 0.8, 0.9),
0.05
);
}
}
createGround(groundData, THREE) {
const size = groundData.size || 20;
const geometry = new THREE.PlaneGeometry(size, size);
const color = groundData.color || [0.5, 0.5, 0.5, 1];
const material = new THREE.MeshStandardMaterial({
color: new THREE.Color(color[0], color[1], color[2]),
roughness: groundData.roughness || 0.8,
metalness: groundData.metallic || 0.0
});
const mesh = new THREE.Mesh(geometry, material);
mesh.rotation.x = -Math.PI / 2;
mesh.name = "Ground";
this.scene.add(mesh);
this.objects.push(mesh);
}
createObject(objData, THREE) {
let geometry;
switch (objData.type) {
case 'cube':
const size = objData.size || 1;
geometry = new THREE.BoxGeometry(size, size, size);
break;
case 'sphere':
geometry = new THREE.SphereGeometry(objData.radius || 0.5, 32, 32);
break;
case 'cylinder':
geometry = new THREE.CylinderGeometry(
objData.radius || 0.5,
objData.radius || 0.5,
objData.size || 1,
32
);
break;
case 'cone':
geometry = new THREE.ConeGeometry(
objData.radius || 0.5,
objData.size || 1,
32
);
break;
case 'plane':
geometry = new THREE.PlaneGeometry(
objData.size || 10,
objData.size || 10
);
break;
default:
geometry = new THREE.BoxGeometry(1, 1, 1);
}
const color = objData.color || [0.8, 0.8, 0.8, 1];
const material = new THREE.MeshStandardMaterial({
color: new THREE.Color(color[0], color[1], color[2]),
roughness: objData.roughness || 0.5,
metalness: objData.metallic || 0.0,
transparent: color[3] < 1,
opacity: color[3] || 1
});
if (objData.emission) {
material.emissive = new THREE.Color(
objData.emission[0],
objData.emission[1],
objData.emission[2]
);
material.emissiveIntensity = objData.emissionStrength || 1.0;
}
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(
objData.location[0] || 0,
objData.location[1] || 0,
objData.location[2] || 0
);
mesh.rotation.set(
objData.rotation[0] || 0,
objData.rotation[1] || 0,
objData.rotation[2] || 0
);
mesh.scale.set(
objData.scale[0] || 1,
objData.scale[1] || 1,
objData.scale[2] || 1
);
mesh.name = objData.name || 'Object';
this.scene.add(mesh);
this.objects.push(mesh);
}
createAvatar(avatarData, THREE) {
const group = new THREE.Group();
group.name = avatarData.name || 'Avatar';
const torso = new THREE.Mesh(
new THREE.CylinderGeometry(0.25, 0.25, 0.8, 32),
new THREE.MeshStandardMaterial({ color: 0xffccaa })
);
torso.position.y = 1.0;
group.add(torso);
const head = new THREE.Mesh(
new THREE.SphereGeometry(0.18, 32, 32),
new THREE.MeshStandardMaterial({ color: 0xffccaa })
);
head.position.y = 1.9;
group.add(head);
const leftArm = new THREE.Mesh(
new THREE.BoxGeometry(0.2, 0.4, 0.2),
new THREE.MeshStandardMaterial({ color: 0xffccaa })
);
leftArm.position.set(-0.45, 1.3, 0);
group.add(leftArm);
const rightArm = new THREE.Mesh(
new THREE.BoxGeometry(0.2, 0.4, 0.2),
new THREE.MeshStandardMaterial({ color: 0xffccaa })
);
rightArm.position.set(0.45, 1.3, 0);
group.add(rightArm);
const leftLeg = new THREE.Mesh(
new THREE.BoxGeometry(0.25, 0.75, 0.25),
new THREE.MeshStandardMaterial({ color: 0x4444ff })
);
leftLeg.position.set(-0.18, 0.5, 0);
group.add(leftLeg);
const rightLeg = new THREE.Mesh(
new THREE.BoxGeometry(0.25, 0.75, 0.25),
new THREE.MeshStandardMaterial({ color: 0x4444ff })
);
rightLeg.position.set(0.18, 0.5, 0);
group.add(rightLeg);
group.position.set(
avatarData.position[0] || 0,
avatarData.position[1] || 0,
avatarData.position[2] || 0
);
group.scale.set(
avatarData.scale || 1,
avatarData.scale || 1,
avatarData.scale || 1
);
this.scene.add(group);
this.objects.push(group);
}
createLights(lightingData, THREE) {
const ambientLight = new THREE.AmbientLight(
new THREE.Color(lightingData.color || [1, 1, 1]),
lightingData.ambient_strength || 0.3
);
this.scene.add(ambientLight);
this.lights.push(ambientLight);
if (lightingData.lights && lightingData.lights.length > 0) {
lightingData.lights.forEach(light => {
let lightObj;
switch (light.type) {
case 'SUN':
case 'DIRECTIONAL':
lightObj = new THREE.DirectionalLight(
new THREE.Color(light.color || lightingData.color || [1, 1, 1]),
light.energy || 1
);
lightObj.position.set(
light.location[0] || 10,
light.location[1] || -10,
light.location[2] || 10
);
break;
case 'POINT':
lightObj = new THREE.PointLight(
new THREE.Color(light.color || lightingData.color || [1, 1, 1]),
light.energy || 50,
100
);
lightObj.position.set(
light.location[0] || 0,
light.location[1] || 0,
light.location[2] || 2
);
break;
case 'SPOT':
lightObj = new THREE.SpotLight(
new THREE.Color(light.color || lightingData.color || [1, 1, 1]),
light.energy || 50
);
lightObj.position.set(
light.location[0] || 0,
light.location[1] || 0,
light.location[2] || 2
);
break;
default:
return;
}
if (lightObj) {
this.scene.add(lightObj);
this.lights.push(lightObj);
}
});
}
}
createCamera(cameraData, THREE) {
const camera = new THREE.PerspectiveCamera(
cameraData.fov || 50,
16 / 9,
0.1,
1000
);
camera.position.set(
cameraData.position[0] || 4,
cameraData.position[1] || -4,
cameraData.position[2] || 2.2
);
camera.rotation.set(
cameraData.rotation[0] || 1.05,
cameraData.rotation[1] || 0,
cameraData.rotation[2] || 0.78
);
this.scene.add(camera);
}
async exportGLB(outputPath, THREE, GLTFExporter, fs) {
return new Promise((resolve, reject) => {
// Polyfill FileReader for Node.js environment
if (typeof global !== 'undefined' && !global.FileReader) {
global.FileReader = class FileReader {
constructor() {
this.result = null;
this.onload = null;
this.onerror = null;
}
readAsArrayBuffer(blob) {
// In Node.js, we'll handle this differently
if (blob && blob.arrayBuffer) {
blob.arrayBuffer().then(buffer => {
this.result = buffer;
if (this.onload) this.onload({ target: { result: buffer } });
}).catch(err => {
if (this.onerror) this.onerror(err);
});
}
}
};
}
const exporter = new GLTFExporter();
const options = {
binary: true,
includeCustomExtensions: false, // Disable to avoid FileReader issues
onlyVisible: false,
truncateDrawRange: true,
trs: false,
animations: []
};
try {
exporter.parse(
this.scene,
(result) => {
try {
// Handle both ArrayBuffer and Uint8Array
let buffer;
if (result instanceof ArrayBuffer) {
buffer = Buffer.from(result);
} else if (result instanceof Uint8Array) {
buffer = Buffer.from(result);
} else if (typeof result === 'string') {
// If it's a string (JSON), convert to buffer
buffer = Buffer.from(result, 'utf8');
} else if (result && result.buffer) {
// Handle TypedArray
buffer = Buffer.from(result.buffer);
} else {
// Try to convert directly
buffer = Buffer.from(result);
}
if (!buffer || buffer.length === 0) {
reject(new Error('GLB export resulted in empty buffer'));
return;
}
fs.writeFileSync(outputPath, buffer);
resolve();
} catch (error) {
reject(new Error(`Failed to write GLB file: ${error.message}`));
}
},
(error) => {
// Check if error is about FileReader
const errorMsg = error?.message || String(error);
if (errorMsg.includes('FileReader')) {
reject(new Error('GLTFExporter requires browser environment. Please install three.js properly or use Blender fallback.'));
} else {
reject(new Error(`GLTFExporter error: ${errorMsg}`));
}
},
options
);
} catch (error) {
const errorMsg = error?.message || String(error);
if (errorMsg.includes('FileReader')) {
reject(new Error('GLTFExporter requires browser environment. Please install three.js properly.'));
} else {
reject(error);
}
}
});
}
}