// CameraDeriva: câmera 3D que nunca para. `chaves` [{em, z, x?, y?, giro?}] levam o foco de uma chave à outra (suave), // e por baixo roda uma deriva (balanço, respiração de zoom, leve rotação em perspectiva) que não pára nunca. // z é o zoom (1 = enquadramento normal; 1.3 = 30% mais perto); x/y deslocam o foco em px; giro é a inclinação em graus. import React from 'react'; import {AbsoluteFill, useCurrentFrame, useVideoConfig} from 'remotion'; export type ChaveCamera = {em: number; z?: number; x?: number; y?: number; giro?: number}; export type CameraDerivaProps = {chaves?: ChaveCamera[]; deriva?: number; perspectiva?: number; children: React.ReactNode}; const suave = (t: number) => { const c = Math.min(1, Math.max(0, t)); return c * c * (3 - 2 * c); }; const lerp = (a: number, b: number, t: number) => a + (b - a) * t; export const posCameraDeriva = (f: number, chaves: ChaveCamera[], deriva = 1) => { const ks = chaves.length ? [...chaves].sort((a, b) => a.em - b.em) : [{em: 0, z: 1}]; let z = ks[0].z ?? 1, x = ks[0].x ?? 0, y = ks[0].y ?? 0, g = ks[0].giro ?? 0; for (let i = 0; i < ks.length - 1; i++) { const a = ks[i], b = ks[i + 1]; if (f >= a.em) { const t = suave((f - a.em) / Math.max(1, b.em - a.em)); z = lerp(a.z ?? 1, b.z ?? 1, t); x = lerp(a.x ?? 0, b.x ?? 0, t); y = lerp(a.y ?? 0, b.y ?? 0, t); g = lerp(a.giro ?? 0, b.giro ?? 0, t); } } const escala = z * (1 + deriva * (0.03 * Math.sin(f / 90) + 0.00015 * f)); return {escala, dx: Math.sin(f / 70) * 12 * deriva - x * escala, dy: Math.cos(f / 90) * 9 * deriva - y * escala, rotZ: g + Math.sin(f / 130) * 0.8 * deriva, rotX: 3 * Math.cos(f / 110) * deriva, rotY: 4 * Math.sin(f / 95) * deriva}; }; export const CameraDeriva: React.FC = ({chaves = [], deriva = 1, perspectiva = 2400, children}) => { const {width: L, height: A} = useVideoConfig(); const c = posCameraDeriva(useCurrentFrame(), chaves, deriva); return {children} ; };