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

RTTR: NaN values in state.viewport #3133

Closed
AbsharHassan opened this issue Dec 29, 2023 · 2 comments · Fixed by #3137
Closed

RTTR: NaN values in state.viewport #3133

AbsharHassan opened this issue Dec 29, 2023 · 2 comments · Fixed by #3137
Labels
bug Something isn't working React Three Test Renderer

Comments

@AbsharHassan
Copy link
Contributor

Hi, I am writing unit tests for an R3F application by using the ReactThreeTestRenderer and I am running into a strange issue where the viewport object (and other state, such as camera, obtained by calling the useThree()) is randomly populated with NaN values, such as the width property. Because of this, all the R3F code that is related to the viewport in any way does not perform as expected and cannot be tested correctly.

I have run the test on different machines to ensure this behaviour is not related to my specific setup. I have also created a minimal reproduction of the issue in this devbox.

The code for the test is as follows:

import React from 'react'
import { useThree } from '@react-three/fiber'
import ReactThreeTestRenderer from '@react-three/test-renderer'

describe('ViewportDemonstration', () => {
  it('should return a NaN value for viewport.width', async () => {
    let viewportResult

    const Mesh = () => {
      viewportResult = useThree((state) => state.viewport)

      return (
        <mesh>
          <boxGeometry args={[2, 2]} />
          <meshBasicMaterial />
        </mesh>
      )
    }

    await ReactThreeTestRenderer.create(<Mesh />)

    console.log(viewportResult)

    expect(viewportResult?.width).toBeNaN()
  })
})

the returned viewport object is:

{
    initialDpr: 1,
    dpr: 1,
    width: NaN,
    height: 7.673269879789604,
    top: 0,
    left: 0,
    aspect: NaN,
    distance: 5,
    factor: NaN,
    getCurrentViewport: [Function: getCurrentViewport]
}

I have also combed the source files for the test-renderer: while there is indeed a test for the useThree() hook, the viewport and other states like camera are not tested. This leads me to believe that perhaps this specific value of viewport is intentional or an inherent limitation of the ReactThreeTestRenderer. However, in this case, why do some properties in the viewport, such as the height property, have completely valid values while others have NaN values?

Note: the issue remains even if:

  • width and height values are explicitly passed in the ReactThreeTestRenderer.create function like so:
await ReactThreeTestRenderer.create(<Mesh />, { width: 1280, height: 800 })
  • the getCurrentViewport function is used:
let viewportResult = useThree((state) => state.viewport).getCurrentViewport()
  • another state is accessed, such as the camera. It too has NaN values in the projectionMatrix and aspect and more.

Package Versions:

react: 18.2.0
react-dom: 18.2.0
jest: 29.7.0
three: 0.160.0
@react-three/fiber: 8.15.12
@react-three/test-renderer: 8.2.0

@CodyJasonBennett your input would be highly valuable. Thank you

@CodyJasonBennett
Copy link
Member

NaN suggests that values are being passed as zero somehow.

@CodyJasonBennett CodyJasonBennett added the bug Something isn't working label Dec 29, 2023
@AbsharHassan
Copy link
Contributor Author

@CodyJasonBennett thank you for your response.

I did some digging into how fiber calculates the viewport and discovered that, essentially, the returned width is calculated like this:

const w = h * (width / height)
return { width: w, height: h, top, left, factor: width / w, distance, aspect }

This is coming from the definition of the getCurrentViewport function, as can be seen in the source files. The code for the function is as follows:

function getCurrentViewport(
  camera: Camera = get().camera,
  target: THREE.Vector3 | Parameters<THREE.Vector3['set']> = defaultTarget,
  size: Size = get().size
): Omit<Viewport, 'dpr' | 'initialDpr'> {
  const { width, height, top, left } = size
  const aspect = width / height
  if (target instanceof THREE.Vector3) tempTarget.copy(target)
  else tempTarget.set(...target)
  const distance = camera.getWorldPosition(position).distanceTo(tempTarget)
  if (isOrthographicCamera(camera)) {
    return {
      width: width / camera.zoom,
      height: height / camera.zoom,
      top,
      left,
      factor: 1,
      distance,
      aspect,
    }
  } else {
    const fov = (camera.fov * Math.PI) / 180 // convert vertical fov to radians
    const h = 2 * Math.tan(fov / 2) * distance // visible height
    const w = h * (width / height)
    return {
      width: w,
      height: h,
      top,
      left,
      factor: width / w,
      distance,
      aspect,
    }
  }
}

The width calculation is dependent on values being attained from the state's size object. The width and height properties from the size object are both equal to 0 (as you mentioned). Since

// height is equal to 0
const w = h * (width / height)

This results in a division by 0, leading to the width being given a NaN value.

Now, if we take a closer look at the official useThree() hook test I mentioned in the issue, we can see that the test for the size object expects it to be populated with 0 values for height and width, as can be seen here:

expect(result.size).toEqual({ height: 0, width: 0, top: 0, left: 0, updateStyle: false })

As such, I wonder if viewport having NaN values is indeed intended behavior or a limitation of the ReactThreeTestRenderer, since the calculation of the properties of the viewport directly depend upon the properties of the size state, which in turn is officially EXPECTED to have zeros?

A potential quick fix I have in mind (based on the getCurrentViewport() function logic) is to make the default camera of the ReactThreeTestRenderer orthographic. This will get rid of the NaN values, however the width and height properties will then have 0 as their values, which does not seem useful for testing. Please let me know what you think. Thank you.

AbsharHassan added a commit to AbsharHassan/react-three-fiber that referenced this issue Jan 2, 2024
This commit resolves the issue where width and height properties were not properly structured as part of a size object in the configure function, causing errors in viewport calculation. Now, the function correctly creates and includes a size object with width, height, top, and left properties, ensuring accurate viewport handling as well as camera handling.

Closes pmndrs#3133
AbsharHassan added a commit to AbsharHassan/react-three-fiber that referenced this issue Jan 2, 2024
Updated the test for the useThree hook to accurately check the size property. The test now uses specific dimensions (width: 1280, height: 800) to create the component, ensuring that the size object is correctly tested against expected values. This change improves the test's effectiveness in catching potential size-related issues in the configure function.

Related to pmndrs#3133
@CodyJasonBennett CodyJasonBennett linked a pull request Jan 2, 2024 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working React Three Test Renderer
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants