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

lookAt() Not working when used in onModelLoaded callback #175

Closed
obolland opened this issue Nov 18, 2021 · 12 comments
Closed

lookAt() Not working when used in onModelLoaded callback #175

obolland opened this issue Nov 18, 2021 · 12 comments

Comments

@obolland
Copy link

As title suggest, lookAt function doesn't work as expected when used inside the onModelLoaded callback. Does it need to be used in a different way?
Would be nice to get this working

@brianzinn
Copy link
Owner

I suspect it is not due to loader and callback, but the forward direction. Have a look here:

https://github.com/brianzinn/react-babylonjs/blob/master/storybook/stories/babylonjs/GUI/with2DGUI.stories.js#L135

notice the extra parameters and the yaw/pitch/roll corrections in radians:

https://doc.babylonjs.com/typedoc/classes/babylon.abstractmesh#lookat

@obolland
Copy link
Author

obolland commented Nov 18, 2021

I've experimented with the yaw/pitch/roll to no avail. What works for one co-ordinate does not work for another. Also weirdly, if I set the model's position via the position props e.g...

          <Model
            sceneFilename="something.gltf"
            rootUrl='path'
            position={new Vector3(10, 0, 10)}
            onModelLoaded={onModelLoaded}
          />

and then use the lookAt() function inside the onModelLoaded callback, the model orientates itself differently than if I set the model's position inside of the onModelLoaded callback instead of using the API for setting the position, despite the positions being identical. Something odd is happening...

Also worth noting that if I load the model instead inside of the onSceneMount callback using SceneLoader.ImportMesh() and then use the lookAt() function, it works exactly as expected using the same model and position

EDIT I now have it working, but only if the position is set from within the onModelLoaded callback. If the position is set via props, looAt() doesn't work for some reason 🤷‍♂️

@brianzinn
Copy link
Owner

brianzinn commented Nov 18, 2021

I would need to see your callback, but the Model uses the rootModel to assign all properties. Model loaders other than glTF do not necessary have a "root" mesh, so a root mesh is created and position/transform are applied on that mesh. I don't know if that could be the underlying cause. There is not a lot going on in the Model itself except calling a hook that calls SceneLoader (this ties into React.Suspense for the fallback):
https://github.com/brianzinn/react-babylonjs/blob/master/src/customComponents/Model.tsx

Have a look in your callback here:

const onModelLoaded = (loadedModel) => console.log(loadedModel.rootMesh);

I know there can be differences between setParent() and .parent = ... - setParent maintains position in world space. Main thing to note is that setting position via a prop may be different than setting position on the model itself.

Does that explain the difference to you?

@obolland
Copy link
Author

Yes, thank you @brianzinn 👍😊
Loving react-babylonjs by the way

@brianzinn
Copy link
Owner

brianzinn commented Nov 18, 2021

Maybe for 'gltf' I can just use the __root__ mesh. I think it's just an empty mesh with -1 z scaling for babylon right-handed.

@obolland
Copy link
Author

obolland commented Nov 26, 2021

So what you said made sense until I looked closer and now I'm at a loss again...
The model in question is a gltf model. If I apply a position via the props or apply a position via the callback, the exact same position is applied to the rootMesh, as expected. However, the lookAt() function applies a different rotation value to the rootMesh depending on whether the position was set via props or the callback. I don't know how lookAt() works behind the scenes, but I'm totally at a loss as to why/how this is happening!
My code looks more or less like this...

           <Model
            sceneFilename="myModel.gltf"
            rootUrl="./assets/models/"
            position={new Vector3(20, 0, 20)}
            onModelLoaded={(model, lookAtPoint) => onModelLoaded(model, lookAtPoint)}
          />

and the onModelLoaded callback...

      export const onModelLoaded = (model, lookAtPoint) => {
      const myModel = model.rootMesh
      // arrow.position = new Vector3(20, 0, 20)
      myModel.lookAt(lookAtPoint, Math.PI / 2);
 }

The rotation on the root mesh remains untouched in each approach.
I'm not sure if this is a bug or I'm just missing something? But any help would be much appreciated!

@brianzinn
Copy link
Owner

I’ll make a sandbox. Are you able to share your mesh?

@brianzinn
Copy link
Owner

hi @obolland I found the issue - turns out that I didn't need your model to find the problem. I believe you are calling .lookAt(...) and then the reconciler is setting the position.

const LookAtModel = ({ lookAtPosition, position, id }) => {
  let baseUrl = 'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/';

  const modelRef = useRef(null);

  const onModelLoaded = (model) => {
    console.log('model loaded', model, lookAtPosition);
    modelRef.current = model.rootMesh;
    // I'm setting the position here.
    modelRef.current.position = position;
    modelRef.current.lookAt(lookAtPosition.clone())
  }

  useEffect(() => {
    if (modelRef.current) {
      modelRef.current.lookAt(lookAtPosition);
    }
  }, [lookAtPosition])

  return (
    <Suspense fallback={<box name='fallback' position={position} />}>
      <Model rootUrl={`${baseUrl}Avocado/glTF/`} sceneFilename={`Avocado.gltf?id=${id}`} scaleToDimension={1} position={position} onModelLoaded={onModelLoaded} />
    </Suspense>
  )
}

export const LookAtStory = () => {
  const [lookAtPosition, setLookAtPosition] = useState(Vector3.Zero());

  return (
    <>
      <div style={{ flex: 1, display: 'flex' }}>
        <button onClick={() => setLookAtPosition(new Vector3(0, lookAtPosition.y === -2 ? 2 : -2, 0))}>Look Up, Down</button>
      </div>
      <div style={{ flex: 1, display: 'flex' }}>
        <Engine antialias adaptToDeviceRatio canvasId='babylonJS'>
          <Scene>
            <arcRotateCamera name='camera1' alpha={Math.PI / 2} beta={Math.PI / 2} radius={9.0} target={Vector3.Zero()} minZ={0.001} />
            <box position={lookAtPosition} size={1} />
            <hemisphericLight name='light1' intensity={0.7} direction={Vector3.Up()} />
            <LookAtModel lookAtPosition={lookAtPosition} position={new Vector3(3, 0, 3)} id='1' />
            <LookAtModel lookAtPosition={lookAtPosition} position={new Vector3(-3, 0, -3)} id='2' />
          </Scene>
        </Engine>
      </div>
    </>
  )
}

lookAt

Note that I am calling .position = ... before .lookAt(...) otherwise what is happening is that you are having the model look at a position and then the reconciler is moving the model (and the rotation doesn't change after the position).

I think the solution will be that I apply all of the props supplied (ie: position) and then call the onModelLoaded or apply the props before a new callback onAfterModelLoaded (or something), if I wanted to maintain backwards compatibility. I think it's ok to not maintain backwards compatibility. Can you confirm that fixes your issue and if so also share any thoughts on the callbacks?

@obolland
Copy link
Author

Thanks @brianzinn for looking at this. You're absolutely right. And yes, I can confirm that forcing the reconciler to apply the position changes to the model after props have been applied fixes the issue.
I had wrongly assumed that the props would be applied before the callback ran, so I would personally say that applying all props before calling onModelLoaded should be the expected behaviour. What's your opinion?
Thanks again,
Olly

@brianzinn
Copy link
Owner

Agree with you that would be expected behaviour - I'll apply props before callback and drop a release tonight. Cheers.

brianzinn added a commit that referenced this issue Nov 30, 2021
* WIP: add support for 2D grid - #173
* fix: Props applied before onModelLoaded matches (hopefully) expected behaviour - #175
@brianzinn
Copy link
Owner

Should be fixed - the props are also applied after the callback, so that might be interesting. I nearly went with an opt-out mechanism, but will see how it goes. Can you confirm it's working as expected @obolland ? cheers.

@obolland
Copy link
Author

Thanks @brianzinn 👍
So far so good! Working totally as expected. Great stuff 😊

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

No branches or pull requests

2 participants