A camera control for three.js, similar to THREE.OrbitControls yet supports smooth transitions and more features
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Automatic camera positioning to fit objects, bounding boxes, and spheres with configurable padding and framing options.
Automatically position camera to frame bounding boxes or Three.js objects with configurable padding.
/**
* Fit camera to view a bounding box or Three.js object
* @param box3OrObject - Three.js Box3 or Object3D to fit to
* @param enableTransition - Whether to animate the camera movement
* @param options - Optional padding and framing configuration
* @returns Promise array that resolves when all movements complete
*/
fitToBox(
box3OrObject: THREE.Box3 | THREE.Object3D,
enableTransition: boolean,
options?: Partial<FitToOptions>
): Promise<void[]>;
interface FitToOptions {
cover: boolean; // true = fill viewport, false = fit entirely
paddingLeft: number; // Left padding (0-1 or pixels)
paddingRight: number; // Right padding (0-1 or pixels)
paddingBottom: number; // Bottom padding (0-1 or pixels)
paddingTop: number; // Top padding (0-1 or pixels)
}Usage Examples:
import * as THREE from 'three';
// Fit to a mesh with default settings
const mesh = new THREE.Mesh(geometry, material);
await cameraControls.fitToBox(mesh, true);
// Fit with custom padding (values 0-1 for viewport percentage)
await cameraControls.fitToBox(mesh, true, {
paddingLeft: 0.1, // 10% left padding
paddingRight: 0.1, // 10% right padding
paddingTop: 0.2, // 20% top padding
paddingBottom: 0.05 // 5% bottom padding
});
// Cover mode (fills viewport, may crop object)
await cameraControls.fitToBox(mesh, true, {
cover: true,
paddingLeft: 0.05,
paddingRight: 0.05,
paddingTop: 0.05,
paddingBottom: 0.05
});
// Fit to explicit bounding box
const customBox = new THREE.Box3(
new THREE.Vector3(-5, -3, -2),
new THREE.Vector3(5, 7, 8)
);
await cameraControls.fitToBox(customBox, true);
// Multiple objects using group
const group = new THREE.Group();
group.add(mesh1, mesh2, mesh3);
await cameraControls.fitToBox(group, true);Automatically position camera to frame bounding spheres or Three.js objects.
/**
* Fit camera to view a bounding sphere or Three.js object
* @param sphereOrMesh - Three.js Sphere or Object3D to fit to
* @param enableTransition - Whether to animate the camera movement
* @returns Promise array that resolves when all movements complete
*/
fitToSphere(
sphereOrMesh: THREE.Sphere | THREE.Object3D,
enableTransition: boolean
): Promise<void[]>;Usage Examples:
// Fit to mesh using computed bounding sphere
const mesh = new THREE.Mesh(geometry, material);
await cameraControls.fitToSphere(mesh, true);
// Fit to explicit sphere
const customSphere = new THREE.Sphere(
new THREE.Vector3(0, 5, 0), // center
10 // radius
);
await cameraControls.fitToSphere(customSphere, true);
// Instant fitting without transition
await cameraControls.fitToSphere(mesh, false);
// Create bounding sphere from object using static method
const boundingSphere = CameraControls.createBoundingSphere(complexObject);
await cameraControls.fitToSphere(boundingSphere, true);Multi-Object Fitting:
// Fit to multiple objects using group
const scene = new THREE.Scene();
const group = new THREE.Group();
// Add multiple objects to group
group.add(
new THREE.Mesh(geometry1, material1),
new THREE.Mesh(geometry2, material2),
new THREE.Mesh(geometry3, material3)
);
scene.add(group);
// Fit camera to encompass all objects
await cameraControls.fitToBox(group, true, {
paddingLeft: 0.1,
paddingRight: 0.1,
paddingTop: 0.1,
paddingBottom: 0.1
});Responsive Fitting:
// Fit with different padding based on viewport size
const aspectRatio = window.innerWidth / window.innerHeight;
const padding = aspectRatio > 1.5 ? 0.05 : 0.15; // Less padding on wide screens
await cameraControls.fitToBox(mesh, true, {
paddingLeft: padding,
paddingRight: padding,
paddingTop: padding,
paddingBottom: padding
});Sequential Fitting:
// Fit to multiple objects in sequence
const objects = [mesh1, mesh2, mesh3];
for (const obj of objects) {
await cameraControls.fitToBox(obj, true, {
paddingLeft: 0.2,
paddingRight: 0.2,
paddingTop: 0.2,
paddingBottom: 0.2
});
// Wait before fitting to next object
await new Promise(resolve => setTimeout(resolve, 2000));
}Cover vs Contain:
// Contain mode (default): entire object visible with padding
await cameraControls.fitToBox(mesh, true, {
cover: false,
paddingLeft: 0.1,
paddingRight: 0.1,
paddingTop: 0.1,
paddingBottom: 0.1
});
// Cover mode: object fills viewport, may be partially cropped
await cameraControls.fitToBox(mesh, true, {
cover: true, // Fill viewport
paddingLeft: 0,
paddingRight: 0,
paddingTop: 0,
paddingBottom: 0
});Use distance calculation utilities for manual camera positioning:
// From boundaries-constraints.md - repeated here for context
getDistanceToFitBox(width: number, height: number, depth: number, cover?: boolean): number;
getDistanceToFitSphere(radius: number): number;Manual Fitting Examples:
// Calculate and apply distance manually
const box = new THREE.Box3().setFromObject(mesh);
const size = box.getSize(new THREE.Vector3());
const center = box.getCenter(new THREE.Vector3());
// Calculate distance
const distance = cameraControls.getDistanceToFitBox(size.x, size.y, size.z);
// Apply manually with custom target
await Promise.all([
cameraControls.setTarget(center.x, center.y, center.z, true),
cameraControls.dollyTo(distance, true)
]);
// Sphere fitting manually
geometry.computeBoundingSphere();
const sphere = geometry.boundingSphere;
const sphereDistance = cameraControls.getDistanceToFitSphere(sphere.radius);
await Promise.all([
cameraControls.setTarget(sphere.center.x, sphere.center.y, sphere.center.z, true),
cameraControls.dollyTo(sphereDistance, true)
]);Combine fitting with viewport configuration for advanced layout scenarios:
// Set viewport before fitting
cameraControls.setViewport(0, 0, 800, 600);
await cameraControls.fitToBox(mesh, true);
// Reset viewport after fitting
cameraControls.setViewport(null);
// Multi-viewport fitting
const leftViewport = new THREE.Vector4(0, 0, 400, 600);
const rightViewport = new THREE.Vector4(400, 0, 400, 600);
// Fit for left viewport
cameraControls.setViewport(leftViewport);
await cameraControls.fitToBox(leftObject, true);
// Fit for right viewport
cameraControls.setViewport(rightViewport);
await cameraControls.fitToBox(rightObject, true);// Check for valid geometry before fitting
if (mesh.geometry && mesh.geometry.boundingBox) {
await cameraControls.fitToBox(mesh, true);
} else {
console.warn('Mesh has no bounding box for fitting');
mesh.geometry.computeBoundingBox();
await cameraControls.fitToBox(mesh, true);
}
// Handle empty or degenerate boxes
const box = new THREE.Box3().setFromObject(mesh);
if (!box.isEmpty()) {
await cameraControls.fitToBox(box, true);
} else {
console.warn('Object has empty bounding box');
}
// Ensure minimum padding for small objects
const minPadding = 0.05;
await cameraControls.fitToBox(mesh, true, {
paddingLeft: Math.max(calculatedPadding, minPadding),
paddingRight: Math.max(calculatedPadding, minPadding),
paddingTop: Math.max(calculatedPadding, minPadding),
paddingBottom: Math.max(calculatedPadding, minPadding)
});Install with Tessl CLI
npx tessl i tessl/npm-camera-controls