javascript - Three.js Acercar para Ajustar con desplazamiento
Estoy intentando crear una función de zoom-para-encajar que asegure que una lista de puntos se ajuste perfectamente al área de dibujo, mientras también se agregan desplazamientos configurables en todos los lados de la imagen. Es decir, hacer zoom-para-encajar en un área del marco en lugar de toda el área del visor:
(nota que los desplazamientos en esta imagen no son precisos)
Estoy usando una cámara en perspectiva aquí. La función debe actualizar la posición de la cámara pero no sus parámetros ni la dirección de la vista.
Encontré una función de zoom para ajustar que funciona bien*, pero estoy teniendo problemas para implementar los desplazamientos.
Mi primer enfoque de simplemente compensar las coordenadas de los puntos (usando el sistema de coordenadas de la cámara) no funcionó. Se muestra más de la imagen, pero mis puntos seleccionados no terminan en los bordes del área. Esto tiene sentido en retrospectiva, ya que la distorsión de perspectiva moverá los puntos lejos de sus posiciones previstas.
¿Puede alguien ayudar con una posible solución para cómo calcular correctamente la distancia y posición de la cámara?
* Three.js no viene con una función de acercar-para-encajar, pero hay muchos ejemplos y preguntas en línea sobre cómo implementar esta lógica. El mejor para este tipo de caso de uso probablemente sea CameraViewBox. He adoptado su ejemplo a mi caso de uso en este fiddle:
import * as THREE from 'https://cdn.skypack.dev/[email ';
import { OrbitControls } from 'https://cdn.skypack.dev/[email /examples/jsm/controls/OrbitControls.js';
let camera, controls, scene, renderer, material;
let isDragging = false;
let cameraViewBox;
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
const meshes = [];
const selection = new Set();
const selectedMaterial = new THREE.MeshPhongMaterial({ color: 0xff0000, flatShading: true });
const floorPlane = new THREE.Plane(new THREE.Vector3(0, 1, 0));
init();
animate();
function init() {
scene = new THREE.Scene();
scene.background = new THREE.Color(0xcccccc);
scene.fog = new THREE.FogExp2(0xcccccc, 0.002);
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(400, 200, 0);
// Create the cameraViewBox
cameraViewBox = new THREE.CameraViewBox();
cameraViewBox.setViewFromCamera(camera);
// controls
controls = new OrbitControls(camera, renderer.domElement);
controls.minDistance = 100;
controls.maxDistance = 500;
controls.maxPolarAngle = Math.PI / 2;
// world
const geometry = new THREE.BoxGeometry(1, 1, 1);
geometry.translate(0, 0.5, 0);
material = new THREE.MeshPhongMaterial({
color: 0xffffff,
flatShading: true
});
for (let i = 0; i < 500; i++) {
const mesh = new THREE.Mesh(geometry, material);
mesh.position.x = Math.random() * 1600 - 800;
mesh.position.y = 0;
mesh.position.z = Math.random() * 1600 - 800;
mesh.scale.x = 20;
mesh.scale.y = Math.random() * 80 + 10;
mesh.scale.z = 20;
mesh.updateMatrix();
mesh.matrixAutoUpdate = false;
scene.add(mesh);
meshes.push(mesh);
}
// lights
const dirLight1 = new THREE.DirectionalLight(0xffffff);
dirLight1.position.set(1, 1, 1);
scene.add(dirLight1);
const dirLight2 = new THREE.DirectionalLight(0x002288);
dirLight2.position.set(-1, -1, -1);
scene.add(dirLight2);
const ambientLight = new THREE.AmbientLight(0x222222);
scene.add(ambientLight);
window.addEventListener('resize', onWindowResize);
// Add DOM events
renderer.domElement.addEventListener('mousedown', onMouseDown, false);
window.addEventListener('mousemove', onMouseMove, false);
renderer.domElement.addEventListener('mouseup', onMouseUp, false);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
// Add selection support
function onMouseDown() {
isDragging = false;
}
function onMouseMove() {
isDragging = true;
}
function onMouseUp(event) {
if (isDragging) {
isDragging = false;
return;
} else {
isDragging = false;
}
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
var intersects = raycaster.intersectObjects(meshes);
if (intersects.length > 0) {
var mesh = intersects[0].object;
if (selection.has(mesh)) {
mesh.material = material;
selection.delete(mesh);
} else {
mesh.material = selectedMaterial;
selection.add(mesh);
}
}
}
function centerOnSelection() {
if (selection.size === 0) {
return;
}
cameraViewBox.setViewFromCamera(camera);
cameraViewBox.setFromObjects(Array.from(selection));
cameraViewBox.getCameraPositionAndTarget(camera.position, controls.target, floorPlane);
controls.update();
}
Respuesta #1
Ahora podía resolver esto por mí mismo hasta cierto punto. Es sorprendentemente fácil si comenzamos con desplazamientos simétricos:
Usar un ángulo de FOV más estrecho (verde) para calcular la posición de la cámara desplazará los puntos proyectados por cierta cantidad en la imagen final. Si encontramos el ángulo correcto, los puntos terminan en el desplazamiento exacto que estamos buscando.
Podemos calcular este ángulo usando trigonometría básica. Calculamos la distancia al plano de Coordenadas de Dispositivo Normalizadas (es decir, altura/anchura de -1 a 1; azul en la imagen) y luego aplicamos el desplazamiento (valor porcentual que varía de 0.0 a 1.0) y creamos un nuevo ángulo:
tan(FOV / 2) = 1 / dist => dist = 1 / tan(FOV / 2)tan(FOVg / 2) = (1 - offset) / dist => FOVg = atan((1 - offset) / dist) * 2Repite esto para el FOV horizontal (modificado por la relación de aspecto), usando el mismo valor de desplazamiento o uno diferente. Luego aplica la lógica existente de zoom para ajustar dadas estas nuevas ángulos.
Este enfoque funciona bien para desplazamientos simétricos. Lo mismo probablemente sería posible para desplazamientos asimétricos calculando 4 ángulos nuevos individuales. La parte complicada es calcular la posición correcta de la cámara y el zoom usando esos...

