Saul Ackerman
Saul Ackerman

Reputation: 3

Three.js rotating the camera in elliptical shape

I am trying to get my camera perspectiveCamera to rotate around a scene that is elliptical or rectangular in shape. I cant seem to figure out a way to get the camera to automatically zoom out when it hits an edge so that the scene doesn't get cut off.

for visualization purposes I want pretty much this effect

I've been trying all day and short of hardcoding positions (which can't be the right solution) I'm falling short.

Upvotes: 0

Views: 197

Answers (2)

user128511
user128511

Reputation:

Copy of @prisoner849's with certain issues fixed for some definition of fixed. Thought about just editing that answer but thought I was making too many changes. Happy to take this down if this is inappropriate

  1. Fix discontinuity bug

  2. Remove dependency on window size

  3. Make code handle resize

  4. Handle mouse capture

    note: mouse capture doesn't work in an iframe on Chrome as of v69, nor Safari as implemented here. It does work in Firefox. In other words, click on the image, drag outside the window. If you're an iframe you'll stop getting events as soon as you drag outside the frame. If you're the top level frame you'll continue to get events event outside the window.

    This has a bunch of issues.

    If you leave the code as is the and you're in an iframe then if the user clicks and drags in the iframe, moves the mouse out of the iframe and then lets off the mouse button the dragging will be stuck to the mouse until the user clicks in the frame (the mouseup event never arrives)

    One solution to that is to add a listener to mouseout to remove all the listeners. The problem with that solution is as soon as the mouse touches the edge of the iframe or the window the mouse disconnects from dragging which is a horrible UX.

    One other possible solution is to only disconnect the mouse on mouseout in an iframe but that still has the issue that as soon as the user goes out of the iframe the mouse disconnects even if they don't let off the mouse and end up dragging back into the frame

    checking other sites it appears most sites choose to just let the mouse get stuck in dragging mode (embedded google maps shows this behavior as does the three.js camera controls as two examples).

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, 1, 1, 1000);
var renderer = new THREE.WebGLRenderer({
  antialias: true
});
var canvas = renderer.domElement
document.body.appendChild(canvas);

var light = new THREE.DirectionalLight(0xffffff, 1);
light.position.setScalar(10, 10, -10);
scene.add(light);
scene.add(new THREE.AmbientLight(0xffffff, 0.25));

scene.add(new THREE.GridHelper(20, 20, 0x404040, 0x404040));

// island
var boxGeo = new THREE.BoxBufferGeometry();
boxGeo.translate(0, 0.5, 0);
var boxMat = new THREE.MeshLambertMaterial({
  color: 0x909090
});
for (let x = -5; x < 6; x++) {
  for (let y = -3; y < 4; y++) {
    let box = new THREE.Mesh(boxGeo, boxMat);
    box.scale.y = THREE.Math.randInt(1, 3);
    box.position.set(x - 0.5, 0, y - 0.5);
    scene.add(box);
    let edgesGeo = new THREE.EdgesGeometry(boxGeo);
    let edges = new THREE.LineSegments(edgesGeo, new THREE.LineBasicMaterial({
      color: 0xaaaaaa
    }));
    box.add(edges);
  }
}

//path
var curve = new THREE.CatmullRomCurve3([
  new THREE.Vector3(0, 6, 6),
  new THREE.Vector3(12, 6, 0),
  new THREE.Vector3(0, 6, -6),
  new THREE.Vector3(-12, 6, 0)
])
curve.closed = true;

curve.getPoint(0, camera.position);
camera.lookAt(scene.position);

var mouseXOnMouseDown = 0;
var currPoint = 0;
var currPointOnMouseDown = 0;

canvas.addEventListener('mousedown', onCanvasMouseDown, true);

function onCanvasMouseDown(event) {
  event.preventDefault();
  event.stopPropagation();  
  window.addEventListener('mousemove', onDocumentMouseMove, true);
  window.addEventListener('mouseup', removeListeners, true);
  mouseXOnMouseDown = event.clientX;
  currPointOnMouseDown = currPoint;
}

function onDocumentMouseMove(event) {
  event.preventDefault();
  event.stopPropagation();
  var deltaMouseX = event.clientX - mouseXOnMouseDown;
  currPoint = THREE.Math.euclideanModulo(currPointOnMouseDown + deltaMouseX * 0.0005, 1);
}

function removeListeners() {
  window.removeEventListener('mousemove', onDocumentMouseMove, true);
  window.removeEventListener('mouseup', removeListeners, true);
}

render();

function resize(renderer) {
  const canvas = renderer.domElement;
  const width = canvas.clientWidth;
  const height = canvas.clientHeight;
  const needResize = canvas.width !== width || canvas.height !== height;
  if (needResize) {
    renderer.setSize(width, height, false);
  }
  return needResize;
}

function render() {
  if (resize(renderer)) {
    camera.aspect = canvas.clientWidth / canvas.clientHeight;
    camera.updateProjectionMatrix();
  }
  
  curve.getPoint(currPoint, camera.position);
  camera.lookAt(scene.position);
  renderer.render(scene, camera);
  requestAnimationFrame(render);
}
html, body {
  height: 100%;
  margin: 0;
}
canvas {
  width: 100%;
  height: 100%;
  display; block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/96/three.min.js"></script>

Upvotes: 1

prisoner849
prisoner849

Reputation: 17596

A very rough concept with a curve (code for mouse events is taken from here):

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
var renderer = new THREE.WebGLRenderer({
  antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

var light = new THREE.DirectionalLight(0xffffff, 1);
light.position.setScalar(10, 10, -10);
scene.add(light);
scene.add(new THREE.AmbientLight(0xffffff, 0.25));

scene.add(new THREE.GridHelper(20, 20, 0x404040, 0x404040));

// island
var boxGeo = new THREE.BoxBufferGeometry();
boxGeo.translate(0, 0.5, 0);
var boxMat = new THREE.MeshLambertMaterial({
  color: 0x909090
});
for (let x = -5; x < 6; x++) {
  for (let y = -3; y < 4; y++) {
    let box = new THREE.Mesh(boxGeo, boxMat);
    box.scale.y = THREE.Math.randInt(1, 3);
    box.position.set(x - 0.5, 0, y - 0.5);
    scene.add(box);
    let edgesGeo = new THREE.EdgesGeometry(boxGeo);
    let edges = new THREE.LineSegments(edgesGeo, new THREE.LineBasicMaterial({
      color: 0xaaaaaa
    }));
    box.add(edges);
  }
}

//path
var curve = new THREE.CatmullRomCurve3([
  new THREE.Vector3(0, 6, 6),
  new THREE.Vector3(12, 6, 0),
  new THREE.Vector3(0, 6, -6),
  new THREE.Vector3(-12, 6, 0)
])
curve.closed = true;

curve.getPoint(0, camera.position);
camera.lookAt(scene.position);

var mouseX = 0;
var mouseXOnMouseDown = 0;
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
var targetRotation = 0;
var targetRotationOnMouseDown = 0;
var currentPoint = 0;
var currPoint = 0;

document.addEventListener('mousedown', onDocumentMouseDown, false);

function onDocumentMouseDown(event) {
  event.preventDefault();
  document.addEventListener('mousemove', onDocumentMouseMove, false);
  document.addEventListener('mouseup', onDocumentMouseUp, false);
  document.addEventListener('mouseout', onDocumentMouseOut, false);
  mouseXOnMouseDown = event.clientX - windowHalfX;
  targetRotationOnMouseDown = targetRotation;
}

function onDocumentMouseMove(event) {
  mouseX = event.clientX - windowHalfX;
  targetRotation = targetRotationOnMouseDown + (mouseX - mouseXOnMouseDown) * 0.02;
  currPoint = (targetRotation - targetRotationOnMouseDown) * 0.05;
}

function onDocumentMouseUp(event) {
  document.removeEventListener('mousemove', onDocumentMouseMove, false);
  document.removeEventListener('mouseup', onDocumentMouseUp, false);
  document.removeEventListener('mouseout', onDocumentMouseOut, false);
}

function onDocumentMouseOut(event) {
  document.removeEventListener('mousemove', onDocumentMouseMove, false);
  document.removeEventListener('mouseup', onDocumentMouseUp, false);
  document.removeEventListener('mouseout', onDocumentMouseOut, false);
}

render();

function render() {
  requestAnimationFrame(render);
  curve.getPoint(currPoint, camera.position);
  camera.lookAt(scene.position);
  renderer.render(scene, camera);
}
body {
  overflow: hidden;
  margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/96/three.min.js"></script>

Upvotes: 1

Related Questions