Tresjs / tres

Declarative ThreeJS using Vue Components
https://tresjs.org
MIT License
2.27k stars 107 forks source link

onBeforeCompile prop of material doesn't work #360

Closed enpitsuLin closed 6 months ago

enpitsuLin commented 1 year ago

Describe the bug

For class Material we have onBeforeCompile method, R3f provide onBeforeCompile prop to use that (example), and I think Tresjs should have something like this.

I tried to use @beforeCompile will warn image

and onBeforeCompile both don't work, I guess this might be a bug or this is a feature doesn't implement yet?

I provide a reproduction with a Codesandbox copy from the official playground, just run and see no stdout which should have

Reproduction

https://codesandbox.io/p/sandbox/hopeful-goodall-y86d74?file=%2Fsrc%2FApp.vue%3A44%2C9

Steps to reproduce

No response

System Info

No response

Used Package Manager

npm

Code of Conduct

alvarosabu commented 1 year ago

Try <Material on-before-compile="[...args]" />

enpitsuLin commented 1 year ago

@alvarosabu onBeforeCompile is a function that was not iterable

I assign it to the property manually then it works.

watchPostEffect(() => {
  if (materialRef.value) materialRef.value.onBeforeCompile = onBeforeCompile;
});
alvarosabu commented 1 year ago

@Tinoooo would make sense that we map on events to native vue events?

Tinoooo commented 1 year ago

@Tinoooo would make sense that we map on events to native vue events?

@alvarosabu Yes, I think this could be a handy feature ☺️

ThimoDEV commented 7 months ago

@Tinoooo , @alvarosabu Where would I have to start searching to map those events. Is that a responsibility of the custom renderer?

andretchen0 commented 7 months ago

Just chiming in.

Ran into this as well, but in my case with onBeforeRender. I think it'd be handy to be able to set the callback via props.

andretchen0 commented 7 months ago

@ThimoDEV

Where would I have to start searching to map those events.

I'd start with patchProp in src/core/nodeOps.ts.

andretchen0 commented 7 months ago

Looking into this, with a scene like ...

<Object3D :on-before-render="console.log">
  ...
</Object3D>

... and stepping through src/core/nodeOps.ts, you end up at this line.

Here it is in the debugger, with some watch expressions on the right showing the pertinent values:

Screenshot 2024-04-08 at 01 16 56

It's calling ...

node[finalKey](value)    // i.e., object3d['onBeforeRender'](console.log)

... but what's desired here (at least in this issue, if not generally) is setting ...

node[finalKey] = value   // i.e., object3d['onBeforeRender'] = console.log

Context

Looking through the git history, that chunk of code has been calling rather than setting ever since it was added to the codebase. Here are the relevant commits:

Questions

andretchen0 commented 7 months ago

Context

I came across at least one common use case for calling here.

Something like <PerspectiveCamera :look-at="[1, 2, 3]" />. Under the hood, that needs to call lookAt().

Notably, this doesn't appear to work in R3F/Drei. In R3F, you have to get a reference to the camera and call lookAt(). That's probably a more coherent approach, since templates are supposed to be declarative.

But since we're already calling we should probably continue to call, at least where it's obvious that setting isn't desired.

Fix?

To accommodate setting functions, in src/core/nodeOps.ts, we could do something like changing this:

      if (isFunction(target)) {
        // don't call pointer event callback functions
        if (!supportedPointerEvents.includes(prop)) {
          if (Array.isArray(value)) { node[finalKey](...value) }
          else { node[finalKey](value) }
        }
        return
      }

... to ...

      if (isFunction(target)) {
        // don't call pointer event callback functions
        if (!supportedPointerEvents.includes(prop)) {
          if (isFunction(value)) {
              // NOTE: Both target and value are functions. Set target to value.
              node[finalKey] = value
          }
          else if (Array.isArray(value)) { node[finalKey](...value) }
          else { node[finalKey](value) }
        }
        return
      }

Breaking

That will break code like:

<Object3D :traverse="myTraverseCallback" />

But to me anyway, this clearly not declarative and doesn't belong in a template.

However, if we wanted to, we could work around it for this case and the other on... callbacks by being more specific with the predicate:

          if (isFunction(value) && finalKey.startsWith('on')) {
              node[finalKey] = value
          }

That keeps almost all of the current functionality in place, but it seems overly specific, making it hard to intuit without digging into the source.


Feedback?

Any thoughts here?

enpitsuLin commented 6 months ago

So it's some thing like that we are calling lootAt() and traverse() by props rather method on instance, is there other case like that?

we could work around it for this case and the other on... callbacks by being more specific with the predicate:

I think it's best solution.

In my opinion, almost use case about on... property in Object3D should be used to be assigned, not to calling them, it was a callback runs inner that can be override by user

andretchen0 commented 6 months ago

So it's some thing like that we are calling lootAt() and traverse() by props rather method on instance, is there other case like that?

I haven't found others exactly like that in Mesh. But similar to that is:

<TresMesh :position="[1, 2, 3]" />

In order to pretend that's declarative, we have to call rather than set behind the scenes. Something like:

mesh.position.set(... [1, 2, 3])

and not

mesh.position = [1, 2, 3]
andretchen0 commented 6 months ago

I think it's best solution.

I'll submit a PR.

enpitsuLin commented 6 months ago

seem done, it's closed