From ad116df73be7dd91679b68a1782c2fcbdbc2679d Mon Sep 17 00:00:00 2001
From: Alexander Rose <alexander.rose@weirdbyte.de>
Date: Fri, 31 Mar 2023 23:37:52 -0700
Subject: [PATCH] fix camera project/unproject

- was wrong when using offset viewport
---
 CHANGELOG.md                          |  2 ++
 src/mol-canvas3d/_spec/camera.spec.ts | 37 +++++++++++++++++++++++++++
 src/mol-canvas3d/camera.ts            |  2 +-
 src/mol-canvas3d/camera/util.ts       |  4 +--
 src/mol-canvas3d/passes/pick.ts       |  2 +-
 5 files changed, 43 insertions(+), 4 deletions(-)
 create mode 100644 src/mol-canvas3d/_spec/camera.spec.ts

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 51c32f326..cca3f467d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@ Note that since we don't clearly distinguish between a public and private interf
 
 
 ## [Unreleased]
+
 - Handle resizes of viewer element even when window remains the same size
 - Throttle canvas resize events
 - Selection toggle buttons hidden if selection mode is off
@@ -16,6 +17,7 @@ Note that since we don't clearly distinguish between a public and private interf
 - Apply bumpiness as lightness variation with `ignoreLight`
 - Remove `JSX` reference from `loci-labels.ts`
 - Fix overpaint/transparency/substance smoothing not updated when geometry changes
+- Fix camera project/unproject when using offset viewport
 
 ## [v3.32.0] - 2023-03-20
 
diff --git a/src/mol-canvas3d/_spec/camera.spec.ts b/src/mol-canvas3d/_spec/camera.spec.ts
new file mode 100644
index 000000000..84cbdbcdb
--- /dev/null
+++ b/src/mol-canvas3d/_spec/camera.spec.ts
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Vec3, Vec4 } from '../../mol-math/linear-algebra';
+import { Mat4 } from '../../mol-math/linear-algebra/3d/mat4';
+import { Viewport, cameraProject, cameraUnproject } from '../camera/util';
+
+describe('camera', () => {
+    it('project/unproject', () => {
+        const proj = Mat4.perspective(Mat4(), -1, 1, 1, -1, 1, 100);
+        const invProj = Mat4.invert(Mat4(), proj);
+
+        const c = Vec4();
+        const po = Vec3();
+
+        const vp = Viewport.create(0, 0, 100, 100);
+        const pi = Vec3.create(0, 0, 1);
+        cameraProject(c, pi, vp, proj);
+        expect(Vec4.equals(c, Vec4.create(50, 50, 2.020202, -1))).toBe(true);
+        cameraUnproject(po, c, vp, invProj);
+        expect(Vec3.equals(po, pi)).toBe(true);
+
+        Vec3.set(pi, 0.5, 0.5, 1);
+        cameraProject(c, pi, vp, proj);
+        cameraUnproject(po, c, vp, invProj);
+        expect(Vec3.equals(po, pi)).toBe(true);
+
+        Viewport.set(vp, 50, 50, 100, 100);
+        Vec3.set(pi, 0.5, 0.5, 1);
+        cameraProject(c, pi, vp, proj);
+        cameraUnproject(po, c, vp, invProj);
+        expect(Vec3.equals(po, pi)).toBe(true);
+    });
+});
\ No newline at end of file
diff --git a/src/mol-canvas3d/camera.ts b/src/mol-canvas3d/camera.ts
index 72b788c23..702006bf2 100644
--- a/src/mol-canvas3d/camera.ts
+++ b/src/mol-canvas3d/camera.ts
@@ -194,7 +194,7 @@ class Camera implements ICamera {
     getPixelSize(point: Vec3) {
         // project -> unproject of `point` does not exactly return the same
         // to get a sufficiently accurate measure we unproject the original
-        // clip position in addition to the one shifted bey one pixel
+        // clip position in addition to the one shifted by one pixel
         this.project(tmpClip, point);
         this.unproject(tmpPos1, tmpClip);
         tmpClip[0] += 1;
diff --git a/src/mol-canvas3d/camera/util.ts b/src/mol-canvas3d/camera/util.ts
index 573d12313..5929b716e 100644
--- a/src/mol-canvas3d/camera/util.ts
+++ b/src/mol-canvas3d/camera/util.ts
@@ -77,7 +77,7 @@ export function cameraProject(out: Vec4, point: Vec3, viewport: Viewport, projec
 
     // transform into window coordinates, set fourth component to 1 / clip.w as in gl_FragCoord.w
     out[0] = (tmpVec4[0] + 1) * width * 0.5 + x;
-    out[1] = (1 - tmpVec4[1]) * height * 0.5 + y; // flip Y
+    out[1] = (tmpVec4[1] + 1) * height * 0.5 + y;
     out[2] = (tmpVec4[2] + 1) * 0.5;
     out[3] = w === 0 ? 0 : 1 / w;
     return out;
@@ -92,7 +92,7 @@ export function cameraUnproject(out: Vec3, point: Vec3 | Vec4, viewport: Viewpor
     const { x, y, width, height } = viewport;
 
     const px = point[0] - x;
-    const py = (height - point[1] - 1) - y;
+    const py = point[1] - y;
     const pz = point[2];
 
     out[0] = (2 * px) / width - 1;
diff --git a/src/mol-canvas3d/passes/pick.ts b/src/mol-canvas3d/passes/pick.ts
index 8df182f66..0dd647321 100644
--- a/src/mol-canvas3d/passes/pick.ts
+++ b/src/mol-canvas3d/passes/pick.ts
@@ -358,7 +358,7 @@ export class PickHelper {
 
         const z = this.getDepth(xp, yp);
         // console.log('z', z);
-        const position = Vec3.create(x, viewport.height - y, z);
+        const position = Vec3.create(x, y, z);
         if (StereoCamera.is(camera)) {
             const halfWidth = Math.floor(viewport.width / 2);
             if (x > viewport.x + halfWidth) {
-- 
GitLab