Skip to content

Asymmetric -2*f_x/screenWidth in cameraProjectionMatrix — origin of X-mirror unclear #35

@kalwalt

Description

@kalwalt

Code reference: improve-viewMatrix_GL / dev.

Summary

WebARKitGL.cpp::cameraProjectionMatrix writes
projectionMatrix[0] = -2.0f * f_x / screenWidth (with a leading minus), while the symmetric Y formula
projectionMatrix[5] = 2.0f * f_y / screenHeight has no negation. The asymmetry doesn't match the standard
pinhole-to-OpenGL derivation (projectionMatrix[0] = +2 * fx / width), but in practice removing the negation
breaks AR overlay placement for a static-image example
— overlays land on the wrong screen half. So the
negation is doing real work; it just isn't obvious what X-mirror it's compensating for.

Empirical evidence

Tested in webarkit/webarkit-testing#31's static-image Teblid example (no webcam, no display mirror):

proj[0] sign Rendered position Axis rotation (THREE.AxesHelper)
-2*f_x/w (current) ✅ overlay sits on the actual marker (left panel of the printout) red X points "wrong" (toward screwdriver, off the marker)
+2*f_x/w (textbook) ❌ overlay placed on the opposite screen half (right panel, no marker) red X rotated correctly to align with the marker's horizontal

So the negation is consistent for position but creates the impression of rotation errors — and the textbook
sign does the opposite. Looks like an X-mirror is being applied somewhere and the projection's negation
compensates for it.

What I've ruled out

  • Camera intrinsics (WebARKitCamera::setupCamera)
    build a standard pinhole [fx 0 cx; 0 fy cy; 0 0 1] with the principal point at the image center. No X flip there.
  • Display side — in the static-image example, the <img> is shown with object-fit: cover and no CSS
    transform: scaleX(-1). The canvas overlay has the same layout. Both consume the image's native orientation.
  • Frame processingcontext_process.drawImage(image, 0, 0, vw, vh) does no mirror. getImageData(...) reads
    the raw canvas pixels and they go into the WASM frame buffer via HEAPU8.set at the buffer offset.
  • convert2Grayscale (WebARKitUtils.h)
    wraps the buffer in a cv::Mat of declared (rows, cols) and runs cv::cvtColor(..., COLOR_RGBA2GRAY) — no flip.
  • arglCameraViewRHf (JS path in WebARKitController.js) negates Y and Z rows only — no X negation.
  • cameraPoseFromPoints / solvePnPRansac / cv::hconcat(rMat, tvec, pose) are standard OpenCV; the
    resulting [R|t] is laid out the conventional way.

So the X mirror has to be hiding somewhere between solvePnP's output and the GL projection, and I can't pinpoint
where.

Working hypotheses (not verified)

  • Inherited artoolkit convention that paired with a horizontally mirrored webcam display, where the negation
    pre-compensates for the display flip (would explain why webcam examples look OK).
  • A subtle sign issue in how transMat/trans is read into the JS-side pose via transMatToGLMat and
    then composed with the projection.
  • A solvePnP rvec-to-rotation handedness that's offset by a 180° rotation around an unexpected axis.

What I'd like

A targeted look from someone who knows the artoolkit-derived pipeline well: where is the X-mirror that the
-2*f_x/w negation is silently compensating for? Until that's identified, the negation should stay (removing
it visibly breaks placement), but the asymmetry vs the symmetric Y formula is a real source of confusion and
blocks cleaning up the projection.

Refs

Test that locks in the current behavior

tests/webarkit_test.cc::TestCameraProjectionMatrix
already asserts projectionMatrix[0] == -1.7851850084276433. If the asymmetry is intentional, that assertion is
fine — but a comment in the source explaining why would save future readers from the same hunt.

Metadata

Metadata

Assignees

Labels

C/C++ codeconcerning the C/C++ code design and improvementsEmscriptenbugSomething isn't workingenhancementNew feature or request

Type

No fields configured for Bug.

Projects

Status
In progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions