pex-gl / pex-math

Array-based vector, quaternion and matrix math with utils for PEX.
MIT License
14 stars 2 forks source link

Implement quat.targetTo #14

Closed vorg closed 8 months ago

vorg commented 6 years ago

This is too useful to not have it. Especially with the new pex-renderer where everything depends on transform (lights, camera, geometries) and it's hard to rotate meshes with Euler angles.

nicknikolov commented 6 years ago

I thought lookAt does also translation? What does it mean for a quaternion? Just the rotation part? Is it different from fromTo?

simonharrisco commented 6 years ago

Usually you specify a target point and it gives the quaternion rotation to face that point

vorg commented 6 years ago

@simonharrisco any suggestions on the name? What other engines are using? (unity, three, babylonjs)

simonharrisco commented 6 years ago

three, unity and babylon all use lookAt.

simonharrisco commented 6 years ago

however when you use lookAt in three and unity it rotates the object; it doesnt return the quat required to do the rotation.

nicknikolov commented 6 years ago

http://glmatrix.net/docs/module-mat4.html

Search for lookAt and targetTo

dmnsgn commented 6 years ago

Historical reasons: https://github.com/toji/gl-matrix/issues/225

vorg commented 6 years ago

This issue is mostly for pex-renderer where we have to orient things (directional lights, camera, meshes themselves). So right now i see 3 options:

var position = [2, 2, 2]
var target = [0, 0, 0]

directionalLightEntity.transform.set({ position: position })

//1. currently
var lightDir =  vec3.normalize(vec3.sub(vec3.copy(target), position))
var rotation = quat.fromTo(q.create(), [0, 0, 1], lightDir)
directionalLightEntity.transform.set({ rotation: rotation })

//2. quat.lookAt
var rotation  = quat.lookAt(q.create(), position, target)
directionalLightEntity.transform.set({ rotation: rotation })

//3. transform.lookAt
directionalLightEntity.transform.lookAt(target)
simonharrisco commented 6 years ago

2 feels more consistent with the rest of pex math

sketchpunk commented 6 years ago

I have a quaternion function that does a LookAt type thing that I ported from a C# example on pastebin. Original Example : https://pastebin.com/ubATCxJY

` static lookRotation(vDir, vUp, out = null){ var zAxis = new Vec3(vDir), //Forward up = new Vec3(vUp), xAxis = new Vec3(), //Right yAxis = new Vec3();

        zAxis.normalize();
        Vec3.cross(up,zAxis,xAxis);
        xAxis.normalize();
        Vec3.cross(zAxis,xAxis,yAxis); //new up

        //fromAxis - Mat3 to Quaternion
        var m00 = xAxis.x, m01 = xAxis.y, m02 = xAxis.z,
            m10 = yAxis.x, m11 = yAxis.y, m12 = yAxis.z,
            m20 = zAxis.x, m21 = zAxis.y, m22 = zAxis.z,
            t = m00 + m11 + m22,
            x, y, z, w, s;

        if(t > 0.0){
            s = Math.sqrt(t + 1.0);
            w = s * 0.5 ; // |w| >= 0.5
            s = 0.5 / s;
            x = (m12 - m21) * s;
            y = (m20 - m02) * s;
            z = (m01 - m10) * s;
        }else if((m00 >= m11) && (m00 >= m22)){
            s = Math.sqrt(1.0 + m00 - m11 - m22);
            x = 0.5 * s;// |x| >= 0.5
            s = 0.5 / s;
            y = (m01 + m10) * s;
            z = (m02 + m20) * s;
            w = (m12 - m21) * s;
        }else if(m11 > m22){
            s = Math.sqrt(1.0 + m11 - m00 - m22);
            y = 0.5 * s; // |y| >= 0.5
            s = 0.5 / s;
            x = (m10 + m01) * s;
            z = (m21 + m12) * s;
            w = (m20 - m02) * s;
        }else{
            s = Math.sqrt(1.0 + m22 - m00 - m11);
            z = 0.5 * s; // |z| >= 0.5
            s = 0.5 / s;
            x = (m20 + m02) * s;
            y = (m21 + m12) * s;
            w = (m01 - m10) * s;
        }

        out = out || new Quaternion();
        out[0] = x;
        out[1] = y;
        out[2] = z;
        out[3] = w;
        return out;

}`

vorg commented 5 years ago

That's an interesting point from @dmnsgn. TLDR: in glMatrix

It means that transform.lookAt makes sense if we interpret it as three.js does "Rotates the object to face a point in world space." https://threejs.org/docs/index.html#api/en/core/Object3D.lookAt

But quat.lookAt is misleading and would be better to be named quat.targetTo like glMatrix http://glmatrix.net/docs/mat4.js.html#line1484. The documentation is missing the definition of where front is though? "Generates a matrix that makes something look at something else.". I would assume it's -Z in OpenGL coordinate system.

vorg commented 2 years ago

if we assume something's "face" is on Z+ then targetTo can be implemented as

quat.targetTo = (q, position, target) => {
  quat.fromTo(q, [0, 0, 1], vec3.normalize(vec3.sub([...target], position)))
}
vorg commented 2 years ago

@dmnsgn bump! I'm using above ^^ function in pex-renderer examples and lib-renderer directional light node already

dmnsgn commented 2 years ago

Unity quaternion methods:

Naming equivalence:

gl-matrix quat.rotationTo === pex-math quat.fromTo

TargetTo:

gl-matrix has mat4.targetTo(out, eye, target, up): Generates a matrix that makes something look at something else.

But we're trying to avoid using a matrix

vorg commented 2 years ago

So the best option would be

mat4.targetTo = (out, eye, target, up = [0, 1, 0]) => {
  let eyex = eye[0];
  let eyey = eye[1];
  let eyez = eye[2];
  let upx = up[0];
  let upy = up[1];
  let upz = up[2];
  let z0 = eyex - target[0];
  let z1 = eyey - target[1];
  let z2 = eyez - target[2];
  let len = z0 * z0 + z1 * z1 + z2 * z2;
  if (len > 0) {
    len = 1 / Math.sqrt(len);
    z0 *= len;
    z1 *= len;
    z2 *= len;
  }
  let x0 = upy * z2 - upz * z1;
  let x1 = upz * z0 - upx * z2;
  let x2 = upx * z1 - upy * z0;
  len = x0 * x0 + x1 * x1 + x2 * x2;
  if (len > 0) {
    len = 1 / Math.sqrt(len);
    x0 *= len;
    x1 *= len;
    x2 *= len;
  }
  out[0] = x0;
  out[1] = x1;
  out[2] = x2;
  out[3] = 0;
  out[4] = z1 * x2 - z2 * x1;
  out[5] = z2 * x0 - z0 * x2;
  out[6] = z0 * x1 - z1 * x0;
  out[7] = 0;
  out[8] = z0;
  out[9] = z1;
  out[10] = z2;
  out[11] = 0;
  out[12] = eyex;
  out[13] = eyey;
  out[14] = eyez;
  out[15] = 1;
  return out;
};

and then

const mat4tmp = mat4.create();

const quat.targetTo = function (q, eye, target, up=[0, 1, 0]) {
  return quat.fromMat4(q, mat4TargetTo(mat4tmp, eye, target, upTmp));
};
dmnsgn commented 2 years ago

Added in v4.0.0-alpha.3.

vorg commented 2 years ago

So this version of quat.targetTo basically does mat4.looktAt but without translation to eye position. While still useful for orienting camera at a given position it's not suitable at rotating objects to face towards something as the opposite rotation is needed. Since we moved to using rotation for directional lights I would love to use the same function as for rotating objects. That assumes that directional light is facing Z+ by default. Which is opposite to e.g. ThreeJS where they use the same lookAt for both cameras and lights. From my understanding Unity's lights is also facing Z+.

quat.fromTo is insufficient as it's missing 'up' vector.

vorg commented 2 years ago

ThreeJS handles the naming problem by making it function of the object and reversing eye/target for meshes. Screenshot 2022-10-17 at 20 00 44

vorg commented 2 years ago

BabylonJS has

public static LookDirectionRHToRef<T extends Matrix>(
forward: DeepImmutable<Vector3>, 
up: DeepImmutable<Vector3>,
result: T): T {
    const right = MathTmp.Vector3[2];
    Vector3.CrossToRef(up, forward, right);

    // Generate the rotation matrix.
    Matrix.FromValuesToRef(
          right._x, right._y, right._z, 0.0,
          up._x, up._y, up._z, 0.0, 
          forward._x, forward._y, forward._z, 
          0.0, 0, 0, 0, 1.0, 
          result);
    return result;
};
vorg commented 2 years ago

Which translates to

const tempVec1 = vec3.create();
const tempVec2 = vec3.create();
const tempMat41 = mat4.create();

function quatFromPointToPointFacingZPos(q, eye, target, up) {
  const forward = tempVec1;
  const right = tempVec2;

  vec3.normalize(vec3.sub(vec3.set(forward, target), eye));
  vec3.cross(vec3.set(right, up), forward);

  const m = tempMat41;
  m[0] = right[0];
  m[1] = right[1];
  m[2] = right[2];
  m[3] = 0.0;
  m[4] = up[0];
  m[5] = up[1];
  m[6] = up[2];
  m[7] = 0.0;
  m[8] = forward[0];
  m[9] = forward[1];
  m[10] = forward[2];
  m[11] = 0.0;
  m[12] = 0.0;
  m[13] = 0.0;
  m[14] = 0.0;
  m[15] = 1.0;
  return quat.fromMat4(q, m);
}
vorg commented 2 years ago

And optimised and cleaned up for readability

function quatFromPointToPointFacingZPos(q, eye, target, up) {
  let forwardX = target[0] - eye[0];
  let forwardY = target[1] - eye[1];
  let forwardZ = target[2] - eye[2];

  let upX = up[0];
  let upY = up[1];
  let upZ = up[2];

  let len = forwardX * forwardX + forwardY * forwardY + forwardZ * forwardZ;
  if (len > 0) {
    len = 1 / Math.sqrt(len);
    forwardX *= len;
    forwardY *= len;
    forwardZ *= len;
  }

  let rightX = upY * forwardZ - upZ * forwardY;
  let rightY = upZ * forwardX - upX * forwardZ;
  let rightZ = upX * forwardY - upY * forwardX;

  const m = TEMP_MAT4;
  m[0] = rightX;
  m[1] = rightY;
  m[2] = rightZ;
  m[3] = 0.0;
  m[4] = upX;
  m[5] = upY;
  m[6] = upZ;
  m[7] = 0.0;
  m[8] = forwardX;
  m[9] = forwardY;
  m[10] = forwardZ;
  m[11] = 0.0;
  m[12] = 0.0;
  m[13] = 0.0;
  m[14] = 0.0;
  m[15] = 1.0;
  return quat.fromMat4(q, m);
}
dmnsgn commented 1 year ago

So how should we name this? fromPointToPointFacingZPos is a bit of a mouthful. Is mat/quat.lookDirection not descriptive enough? Do we need to explicit that the rotation is Z forward and that Z is following Right Handed system.

Also Unity has Quaternion.LookRotation

vorg commented 1 year ago

The point of targetTo is hassle free call where i just give two points (position, target) and the subtraction, normalisation, conversion happens inside the library. quat.fromPointToPoint is good enough for me.

quat.lookDirection is setting new quaternion from a right, up, forward coordinate system. I would love to have it separately mat4/quat.lookDirection for making quaterions from TBN (Tangent, Bitangent, Normal or Right Up Forward) vector triples which i found useful few times this week. We could then remove quat.fromTo or add missing up vector param.

Btw one thing to watch up from quat.lookDirection is that calling it lazy "lazy up" quat.lookDirection(AminusB, [0, 1, 0] might generate invalid rotations as basis vectors require to be orthonormal to each other. I had such bug in lib-renderer/DirectionallLight. Of course we could always double checking by calculating right vector internally and then recomputing up again but that's hidden cost although i don't wanna do that in the userspace either... (in which case i should probably use fromPointToPoint anyway.