I'm working on some Three.js code to import a GLTF model and display it. The model is imported successfully through a different component, but no matter how I adjust the camera or reposition the model, I still can't have the camera display the front of a centered model with both the top and the bottom of the model visible.
I've tried an approach in which the camera is positioned at half the height of the model (calculated using a bounding box), but even that fails to work and the camera exists at the base of the model zoomed into one of its feet.
How do I automatically center and display the entire front of the model from head to toe (touching the edges of the window it is in so as to ensure maximal size) when it is imported into my app? The relevant part of my code is below:
<script>
const { ipcRenderer } = window.electron;
var mixer, clock;
let currentAnimationIndex = 0;
function playNextAnimation() {
if (currentAnimationIndex >= animations.length) return;
const action = mixer.clipAction(animations[currentAnimationIndex]);
action.reset().play();
// Listen for the end of the current animation to play the next one
action.clampWhenFinished = true;
action.loop = THREE.LoopOnce;
action.onFinished = () => {
currentAnimationIndex++;
playNextAnimation();
};
}
ipcRenderer.on('load-gltf', (event, { filePath, characterName }) => {
console.log(`Loading GLTF file: ${filePath}, Character Name: ${characterName}`);
if (!filePath) {
console.error('File path is undefined or invalid');
return;
}
// Initialize Three.js scene
const scene = new THREE.Scene();
clock = new THREE.Clock();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, preserveDrawingBuffer: false });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio || 1); // Improve resolution on high-DPI screens
renderer.setClearColor(0x000000, 0); // Transparent background
// Enable Tone Mapping and adjust Exposure
renderer.toneMapping = THREE.LinearToneMapping; // Choose a tone mapping algorithm
renderer.toneMappingExposure = 2; // Increase exposure to brighten the scene
renderer.physicallyCorrectLights = true;
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.clear()
document.body.appendChild(renderer.domElement);
// Add lights
const directionalLight = new THREE.DirectionalLight(0xffffff, 3);
directionalLight.position.set(5, 10, 7.5);
scene.add(directionalLight);
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
let modelRadius, modelSize;
const loader = new THREE.GLTFLoader();
loader.load(filePath, (gltf) => {
const bbox = new THREE.Box3().setFromObject(gltf.scene);
const center = bbox.getCenter(new THREE.Vector3());
const size = bbox.getSize(new THREE.Vector3());
gltf.scene.position.sub(center);
gltf.scene.position.y = size.y / 2;
// Calculate camera position
const verticalOffset = size.y * 0.5;
const fovRad = camera.fov * Math.PI / 180;
const distance = (Math.max(size.x, size.y, size.z) / Math.sin(fovRad / 2)) * 1.5;
// Set camera position and target
camera.position.set(0, verticalOffset, distance);
camera.lookAt(0, verticalOffset, 0);
controls.target.set(0, verticalOffset, 0);
controls.update();
// gltf.scene.fustrumCulled = false;
scene.add(gltf.scene);b
// Initialize AnimationMixer
mixer = new THREE.AnimationMixer(gltf.scene);
// Store animations
if (gltf.animations && gltf.animations.length > 0) {
animations = gltf.animations;
console.log('Animations found in the model:');
animations.forEach((clip, index) => {
console.log(`${index + 1}: ${clip.name}`);
});
// Start playing animations sequentially
playNextAnimation();
} else {
console.log('No animations found in the model.');
}
animate();
});
// Add OrbitControls
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.target.set(0, 0, 0);
controls.update();
function animate() {
renderer.clear(); // Clear canvas before rendering
requestAnimationFrame(animate);
controls.update();
const delta = clock.getDelta();
if ( mixer ) mixer.update( delta );
renderer.render(scene, camera);
}
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
camera.position.set(0, verticalOffset, distance);
camera.lookAt(0, verticalOffset, 0);
controls.target.set(0, verticalOffset, 0);
controls.update();
});
});
</script>