Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

I want to improve the method of orbitControl () that translates the camera #6107

Closed
1 of 17 tasks
inaridarkfox4231 opened this issue Apr 18, 2023 · 9 comments · Fixed by #6116
Closed
1 of 17 tasks

I want to improve the method of orbitControl () that translates the camera #6107

inaridarkfox4231 opened this issue Apr 18, 2023 · 9 comments · Fixed by #6116

Comments

@inaridarkfox4231
Copy link
Contributor

Increasing Access

In the current specification, you can move the camera parallel by pressing the right mouse button while using orbitControl().

However, I think this is difficult to understand intuitively. This is because the movement range of the fixation point is limited to the direction parallel to the XZ plane. Therefore, when moving vertically, it moves backwards and forwards instead of vertically.

For example, blender's default behavior is to hold down the shift key and press the middle mouse button to move the camera so that the object moves in the direction the mouse is moving. So I think it should be done that way.

Another issue is that the distance traveled is specified along the default camera, so it doesn't behave well at different scales. At small scales it moves too much and at large scales it moves very little.

This is also no problem in blender. Also, the above problem does not occur with easyCam.js.

2023-04-19.00-14-07.mp4

The video below demonstrates how the current right button translation method works. At the end, if you use the right button on a small scale, the object will fly away.

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 enhancement details

The idea is to first find the view coordinates of a point in the view coordinate system that is at the same depth as the gaze point, corresponding to the position moved on the canvas by the mouse movement vector.

Translate this into a global position change using the camera's coordinate system.

Finally, use setPosition() to add this vector to each of the viewpoint and gaze point, and you're done.
At that time, we want the object to move in the direction of the mouse movement, so we reverse the obtained vector in the direction of the camera movement.

   const uP = this._renderer.uPMatrix;
/* ----------------------------------------------------------- */
    } else if (this.mouseButton === this.RIGHT) {

      const local = cam._getLocalAxes(); // normalize portions along X/Z axes
      const uPmat = uP.mat4;

      const diffX = cam.eyeX - cam.centerX;
      const diffY = cam.eyeY - cam.centerY;
      const diffZ = cam.eyeZ - cam.centerZ;
      const viewZ = Math.sqrt(diffX * diffX + diffY * diffY + diffZ * diffZ);

      // In normalized device coordinates, the y-axis is oriented in the opposite direction
      // to that of the mouse, so it is multiplied by a negative sign.
      const ndcX = (this.mouseX - this.pmouseX)*2/this.width;
      const ndcY = -(this.mouseY - this.pmouseY)*2/this.height;

      let viewX, viewY;

      // It distinguishes cases depending on whether it is ortho() or not.
      // You can see that by looking at the 15th component.
      if(uPmat[15] === 0){
        viewX = ((uPmat[8] + ndcX)/uPmat[0]) * viewZ;
        viewY = ((uPmat[9] + ndcY)/uPmat[5]) * viewZ;
      }else{
        viewX = (ndcX - uPmat[12])/uPmat[0];
        viewY = (ndcY - uPmat[13])/uPmat[5];
      }

      // reverse the obtained vector in the direction so that the object move in the direction of the mouse movement
      const moveX = -local.x[0] * viewX - local.y[0] * viewY;
      const moveY = -local.x[1] * viewX - local.y[1] * viewY;
      const moveZ = -local.x[2] * viewX - local.y[2] * viewY;

      cam.setPosition(cam.eyeX + moveX, cam.eyeY + moveY, cam.eyeZ + moveZ);
    }

The result of applying this patch is below: (Note that I have applied a patch that makes scaling smooth with the mouse wheel, because it is easier to verify. This has not been merged yet.)

2023-04-19.00-33-50.mp4

I don't know if there are any disadvantages due to moving the gaze point not parallel to the XZ plane. I would like to leave it to the user's judgment after implementing this. I feel I should do this.

If such a disadvantage exists, I am considering withdrawing this proposal.

@inaridarkfox4231
Copy link
Contributor Author

here is sample code.
improve panning

@davepagurek
Copy link
Contributor

I think this change makes sense! it makes right-click-dragging more intuitive when the view has been rotated, and makes the interaction more consistent with other tools.

@inaridarkfox4231
Copy link
Contributor Author

Thanks for reply! I would like to raise a pull request based on this suggestion.

@davepagurek
Copy link
Contributor

Thanks! I'll assign this to you.

@inaridarkfox4231
Copy link
Contributor Author

After that, I found a bug in ortho() and frustum() that doesn't behave well when the absolute values ​​of the left and right arguments are different, so I fixed it (even when the absolute values ​​of top and bottom are different). I ended up with code like this:

 else if (this.mouseButton === this.RIGHT) {

      var local = cam._getLocalAxes();

      const diffX = cam.eyeX - cam.centerX;
      const diffY = cam.eyeY - cam.centerY;
      const diffZ = cam.eyeZ - cam.centerZ;
      // get spherical coorinates for current camera position about origin
      const viewZ = Math.sqrt(diffX * diffX + diffY * diffY + diffZ * diffZ);

      // position vector of the center
      let cv = createVector(cam.centerX, cam.centerY, cam.centerZ);
      const cMat = cam.cameraMatrix.mat4;
      const uP = this._renderer.uPMatrix.mat4;
      // Calculate the normalized device coordinates of the center
      cv = _applyMatrixToVector3(cMat, cv);
      cv = _applyMatrixToVector3(uP, cv, true);
      // Normalize mouse movement distance
      const ndcX = (this.mouseX - this.pmouseX)*2/this.width;
      const ndcY = -(this.mouseY - this.pmouseY)*2/this.height;
      
      // Move the center by this distance
      // in the normalized device coordinate system
      cv.x -= ndcX;
      cv.y -= ndcY;
      
      // Calculate the translation vector
      // in the direction perpendicular to the line of sight of center
      let dx, dy;
      if(uP[15] === 0){
        dx = ((uP[8] + cv.x)/uP[0]) * viewZ;
        dy = ((uP[9] + cv.y)/uP[5]) * viewZ;
      }else{
        dx = (cv.x - uP[12])/uP[0];
        dy = (cv.y - uP[13])/uP[5];
      }
      cam.setPosition(
        cam.eyeX + dx * local.x[0] + dy * local.y[0],
        cam.eyeY + dx * local.x[1] + dy * local.y[1],
        cam.eyeZ + dx * local.x[2] + dy * local.y[2]
      );
    }
/*  ------------------------------  */
function _applyMatrixToVector3(m, v, divideFlag=false){
  const a = m[0]*v.x + m[4]*v.y + m[8]*v.z + m[12];
  const b = m[1]*v.x + m[5]*v.y + m[9]*v.z + m[13];
  const c = m[2]*v.x + m[6]*v.y + m[10]*v.z + m[14];
  const d = m[3]*v.x + m[7]*v.y + m[11]*v.z + m[15];
  if(divideFlag){
    return createVector(a/d,b/d,c/d);
  }else{
    return createVector(a,b,c);
  }
}

At that time, there was no function to apply a matrix to a vector, so I prepared a global function because the processing is complicated even though it is only used here. Is it permissible to write like this?

@davepagurek
Copy link
Contributor

Maybe it's worth making it a method on p5.Matrix instead of a global function so that we make that class more useful as a nice side effect? It's inconvenient that p5.Vector doesn't have a vec4 equivalent though, so maybe we can make a few methods:

  • multiplyVec4(x, y, z, w): [number, number, number, number] as a generic way to pass in 4 values and get an array of 4 values out, to work around p5.Vector
  • multiplyPoint(vec: p5.Vector): Internally calls multiplyVec4(vec.x, vec.y, vec.z, 1) and puts the x, y, and z back into a vector at the end
  • multiplyAndNormalizePoint(vec: p5.Vector): Internally calls multiplyVec4(vec.x, vec.y, vec.z, 1) and puts the x/w, y/w, and z/w back into a vector at the end
  • multiplyDirection(vec: p5.Vector): Internally calls multiplyVec4(vec.x, vec.y, vec.z, 0) and puts the x, y, and z back into a vector at the end. I don't think we need this here but this is also a fairly common operation and would be nice to have!

@inaridarkfox4231
Copy link
Contributor Author

inaridarkfox4231 commented Apr 22, 2023

I tried to write it by imitating what I saw, but is it like this...?

 /**
* apply a matrix to a vector with x,y,z,w components
* get the results in the form of an array
* @method multiplyVec4
* @param {Number}
* @return {Number[]}
*/
  multiplyVec4(x, y, z, w) {
    const result = new Array(4);

    result[0] = this.mat4[0] * x + this.mat4[4] * y + this.mat4[8] * z + this.mat4[12] * w;
    result[1] = this.mat4[1] * x + this.mat4[5] * y + this.mat4[9] * z + this.mat4[13] * w;
    result[2] = this.mat4[2] * x + this.mat4[6] * y + this.mat4[10] * z + this.mat4[14] * w;
    result[3] = this.mat4[3] * x + this.mat4[7] * y + this.mat4[11] * z + this.mat4[15] * w;

    return result;
  }

@inaridarkfox4231
Copy link
Contributor Author

inaridarkfox4231 commented Apr 22, 2023

I also tried the rest of the methods.

 /**
* Applies a matrix to a vector.
* The fourth component is set to 1.
* Returns a vector consisting of the first
* through third components of the result.
* 
* @method multiplyPoint
* @param {p5.Vector}
* @return {p5.Vector}
*/
  multiplyPoint(v) {
    const array = this.multiplyVec4(v.x, v.y, v.z, 1);
    return new p5.Vector(array[0], array[1], array[2]);
  }

 /**
* Applies a matrix to a vector.
* The fourth component is set to 1.
* Returns the result of dividing the 1st to 3rd components
* of the result by the 4th component as a vector.
* 
* @method multiplyAndNormalizePoint
* @param {p5.Vector}
* @return {p5.Vector}
*/
  multiplyAndNormalizePoint(v) {
    const array = this.multiplyVec4(v.x, v.y, v.z, 1);
    array[0] /= array[3];
    array[1] /= array[3];
    array[2] /= array[3];
    return new p5.Vector(array[0], array[1], array[2]);
  };

 /**
* Applies a matrix to a vector.
* The fourth component is set to 0.
* Returns a vector consisting of the first
* through third components of the result.
* 
* @method multiplyDirection
* @param {p5.Vector}
* @return {p5.Vector}
*/
  multiplyDirection(v) {
    const array = this.multiplyVec4(v.x, v.y, v.z, 0);
    return new p5.Vector(array[0], array[1], array[2]);
  }

Also, when I introduced this as a method of p5.Matrix in the sample and rewrote it, I was able to confirm that it worked properly.

@davepagurek
Copy link
Contributor

Looks good!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants