Skip to content

Add EasyCam-like behavior to orbitControl() as an option #6175

@inaridarkfox4231

Description

@inaridarkfox4231

Increasing Access

The default behavior of EasyCam is to always rotate in the direction the pointer moves, unlike orbitControl like p5.js, Three.js, vRoidHub.

2023-06-01.01-45-11.mp4

There are two types of orbitControl().

First, p5.js and Three.js orbitControl() rotates only in the basic vertical direction. It rotates horizontally as well, but it feels like it rotates around a circle made by slicing a sphere horizontally, so when you rotate ±90°, the entire screen rotates around the center. So it doesn't always rotate in the direction the pointer moves.

However, this orbitControl() always keeps the horizontal, so the screen never tilts sideways. Therefore, if the top and bottom are clear, it is useful. When viewing models, it may be difficult to view people or animals from various angles if the screen is tilted. very convenient.

On the other hand, if you have a model in the middle and just want to view it from different angles, the limited rotation can be inconvenient. In such a case, this EasyCam camera may be better.

Since the two orbitControls are used for different purposes, it is not possible to say which one is better. I think the current orbtiControl() should be the default, and would be in trouble if it wasn't, but I thought it would be nice to have other options.

To change it, use _orbitFree(dx, dy), which we'll see later, and rewrite the part that performs the rotation in orbitControl() as follows:

  if (this._renderer.rotateVelocity.magSq() > 0.000001) {
    this._renderer._curCamera._orbitFree(
      -this._renderer.rotateVelocity.x,
      this._renderer.rotateVelocity.y
    );
    // damping
    this._renderer.rotateVelocity.mult(damping);
  } else {
    this._renderer.rotateVelocity.set(0, 0);
  }

Using this _orbitFree(dx, dy) always rotates the camera in the direction of (dx, dy). You can achieve the behavior like EasyCam.

2023-06-01.02-10-38.mp4

However, it is not directly rewritten. I want the default to be the current behavior. Therefore, we introduce a property called "free" to the option introduced when supporting touch, and set the default to false.

  // If option.free is true, it will always rotate freely in the direction
  // the pointer moves. default value is false (normal behavior)
  const { free = false } = options;

And only if this is true will _orbitFree() be used.

  if (this._renderer.rotateVelocity.magSq() > 0.000001) {
    // if free, it will always rotate freely in the direction the pointer moves
    if (free) {
      this._renderer._curCamera._orbitFree(
        -this._renderer.rotateVelocity.x,
        this._renderer.rotateVelocity.y
      );
    } else {
      this._renderer._curCamera._orbit(
        this._renderer.rotateVelocity.x,
        this._renderer.rotateVelocity.y,
        0
      );
    }
    // damping
    this._renderer.rotateVelocity.mult(damping);
  } else {
    this._renderer.rotateVelocity.set(0, 0);
  }

When we use it, I think we'll call it like this:

  orbitControl(1, 1, 1, {free:true});

Most appropriate sub-area of p5.js?

  • Accessibility
  • Color
  • Core/Environment/Rendering
  • Data
  • DOM
  • Events
  • Image
  • IO
  • Math
  • Typography
  • Utilities
  • WebGL
  • Build Process
  • Unit Testing
  • Internalization
  • Friendly Errors
  • Other (specify if possible)

Feature request details

The implementation of _orbitFree(dx, dy) looks like this:

p5.Camera.prototype._orbitFree = function(dx, dy) {
  // Calculate the vector and its magnitude from the center to the viewpoint
  const diffX = this.eyeX - this.centerX;
  const diffY = this.eyeY - this.centerY;
  const diffZ = this.eyeZ - this.centerZ;
  let camRadius = Math.hypot(diffX, diffY, diffZ);
  // front vector. unit vector from center to eye.
  const front = new p5.Vector(diffX, diffY, diffZ).normalize();
  // up vector. camera's up vector.
  const up = new p5.Vector(this.upX, this.upY, this.upZ);
  // side vector. Right when viewed from the front. (like x-axis)
  const side = new p5.Vector.cross(up, front).normalize();
  // down vector. Bottom when viewed from the front. (like y-axis)
  const down = new p5.Vector.cross(front, side);
  
  // side vector and down vector are no longer used as-is.
  // Create a vector representing the direction of rotation
  // in the form cos(direction)*side + sin(direction)*down.
  // Make the current side vector into this.
  const directionAngle = Math.atan2(dy, dx);
  down.mult(Math.sin(directionAngle));
  side.mult(Math.cos(directionAngle)).add(down);
  // The amount of rotation is the size of the vector (dx, dy).
  const rotAngle = Math.sqrt(dx*dx + dy*dy);
  // The vector that is orthogonal to both the front vector and
  // the rotation direction vector is the rotation axis vector.
  const axis = new p5.Vector.cross(front, side);

  // If the axis vector is likened to the z-axis, the front vector is
  // the x-axis and the side vector is the y-axis. Rotate the up and front
  // vectors respectively by thinking of them as rotations around the z-axis.

  // Calculate the components by taking the dot product and
  // calculate a rotation based on that.
  const c = Math.cos(rotAngle);
  const s = Math.sin(rotAngle);
  const dotFront = up.dot(front);
  const dotSide = up.dot(side);
  const ux = dotFront * c + dotSide * s;
  const uy = -dotFront * s + dotSide * c;
  const uz = up.dot(axis);
  up.x = ux * front.x + uy * side.x + uz * axis.x;
  up.y = ux * front.y + uy * side.y + uz * axis.y;
  up.z = ux * front.z + uy * side.z + uz * axis.z;
  // We won't be using the side vector and the front vector anymore,
  // so let's make the front vector into the vector from the center to the new eye.
  side.mult(-s);
  front.mult(c).add(side).mult(camRadius);

  // it's complete. let's update camera.
  this.camera(
    front.x + this.centerX,
    front.y + this.centerY,
    front.z + this.centerZ,
    this.centerX, this.centerY, this.centerZ,
    up.x, up.y, up.z
  );
}

First, dx and dy are vectors in the direction of rotation, and we write them as vectors orthogonal to front vector. The magnitude of this is the rotation angle. The vector pointing from the center to the eye and the up vector of the camera are each rotated in this direction.

orbitFree

For calculation, take the axis vector of rotation as a vector orthogonal to the direction of rotation, create a coordinate system, and calculate directly. I'm not familiar with quaternions, so I decided to do it directly with linear algebra (EasyCam uses quaternions, but it's hard to prepare quarternion class, so it's unreal).

I think the amount of calculation is not much different from _orbit().

I will add explanation of free-option to the orbitControl() reference. I'm also going to have a unit test to make sure it comes to the same camera after a full rotation.

Here is the demo.
p5.Editor
OpenProcessing
Default of free-options are true in these codes. It's just for testing, I'll set it to false in the actual implementation.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions