Skip to content

Instantly share code, notes, and snippets.

@Made-of-Clay
Created November 17, 2025 13:17
Show Gist options
  • Select an option

  • Save Made-of-Clay/d9548ad1e85f640b18a77860bf5ccad7 to your computer and use it in GitHub Desktop.

Select an option

Save Made-of-Clay/d9548ad1e85f640b18a77860bf5ccad7 to your computer and use it in GitHub Desktop.
Three.js bindShot Helper
import { PerspectiveCamera } from 'three';
/**
* @typedef {Object} ShotMap
* @property {number[]} position
* @property {number[]} lookAt
*/
/** this is a helper
* @param {THREE.PerspectiveCamera} camera
* @param {Record<string, ShotMap>} shots
* @param {GSAP} [gsap] Optional GSAP instance
*/
export function bindShots(camera, orbitControls, shots, gsap) {
if (!camera) throw new ReferenceError('No camera provided');
if (!shots) throw new ReferenceError('No shots provided');
gsap = gsap || window.gsap;
if (!gsap) throw new ReferenceError('GSAP not found');
const sections = document.querySelectorAll('[data-shot]');
if (!sections.length) return console.log('No [data-shot] elements found');
const anim = { duration: 1.2, ease: 'ease-in-out' };
const getQuat = (pos, look) => {
const tempCam = new PerspectiveCamera();
tempCam.position.set(...pos);
tempCam.lookAt(...look);
const clone = tempCam.quaternion.clone();
return clone;
};
const moveCamera = (shot) => {
const { position, lookAt } = shot;
const q = getQuat(position, lookAt);
const posObj = { x: position[0], y: position[1], z: position[2] };
gsap.to(camera.position, { ...posObj, ...anim });
gsap.to(camera.quaternion, {
x: q.x,
y: q.y,
z: q.z,
w: q.w,
...anim,
onUpdate: () => camera.updateMatrixWorld(),
});
};
const observer = new IntersectionObserver(
(entries) => entries.forEach((e) => {
if (e.isIntersecting) {
const name = e.target.getAttribute('data-shot');
const shot = shots[name];
if (shot) moveCamera(shot);
}
}),
{ threshold: 0.5 }
);
sections.forEach((s) => observer.observe(s));
}
export function initLoggingCameraPosition({ controls, camera }) {
function logCameraPosition() {
if (!areOrbitControlsEnabled()) {
return;
}
const { position } = camera;
const { target } = controls;
const positionImpl = [position.x, position.y, position.z].map(Math.round);
const targetImpl = [target.x, target.y, target.z].map(Math.round);
const data = {
position: positionImpl,
target: targetImpl
};
try {
const json = JSON.stringify(data);
navigator.clipboard.writeText(json);
} catch (error) {
console.error(error);
}
}
// throttle logging
let timeoutId = null;
function throttleLogCameraPosition() {
if (timeoutId) clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
logCameraPosition();
timeoutId = null;
}, 250);
}
controls.addEventListener("change", throttleLogCameraPosition);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment