pmndrs / drei

🥉 useful helpers for react-three-fiber
https://docs.pmnd.rs/drei
MIT License
8.21k stars 670 forks source link

Synchronization issues when firing events with a dynamic number of instances #1260

Open Amatewasu opened 1 year ago

Amatewasu commented 1 year ago

Hi, thank you a lot for this awesome library!

Problem description:

According to the Drei Instances documentation, we can "make them [instances] conditional, mount/unmount them, lazy load them".

In order to get good performances with a lot of shapes, I instance them. When the user interacts with one instance, I load its own geometry (in order to get rid of most of the complexity related to instances) and remove the instance from the instances list.

However, by doing that, the onPointerOver is called twice both with the same instanceId but this instanceId does not correspond to the right object during the second call.

Relevant code:

Here's a code sandbox that reproduces the issue: https://codesandbox.io/s/multiple-instanceid-issue-forked-melpg8?file=/src/App.js

And a gif that depicts the issue: issue_dre_hover_instances_and_no_instances

To reproduce:

If you change instanced={!hoveredZones.has(zoneId)} to instanced={true} (L72), the code works properly. Here's a codesandbox that describes that the code works properly in this case.

Here is the code (from the codesandbox) mounting the instance or the separated plane geometry:

  return instanced ? (
    <Instance
      color={color}
      position={position3D}
      scale={scale3D}
      onPointerOver={(e) => {
        console.log(`onPointerOver (instanced) ${zone.name}, instanceId: ${e.instanceId}, intersections: ${e.intersections}`)

        onPointerOver(e, zone)
      }}
      onPointerOut={(e) => {
        onPointerOut(e, zone)
      }}
    />
  ) : (
    <Plane
      args={[1, 1]}
      material-color={color}
      material-opacity={hoveredOpacity}
      material-transparent={true}
      scale={scale3D}
      position={position3D}
      onPointerOver={(e) => {
        console.log(`onPointerOver (non instanced) ${zone.name}, instanceId: ${e.instanceId}, intersections: ${e.intersections}`)

        onPointerOver(e, zone)
      }}
      onPointerOut={(e) => {
        onPointerOut(e, zone)
      }}
    />
  )
Amatewasu commented 1 year ago

For those having the same issue, I've found a pretty good workaround: CodeSandbox.

The idea is the following:

Here's the relevant piece of code:

<group
    onPointerOver={(e) => {
      console.log(`onPointerOver (group) ${zone.name}, instanceId: ${e.instanceId}, intersections: ${e.intersections}`)

      onPointerOver(e, zone)
    }}
    onPointerOut={(e) => {
      onPointerOut(e, zone)
    }}>
    <Instance color={color} position={position3D} scale={instanced ? scale3D : [0, 0, 0]} />
    {!instanced && (
      <Plane
        args={[1, 1]}
        material-color={color}
        material-opacity={hoveredOpacity}
        material-transparent={true}
        scale={scale3D}
        position={position3D}
      />
    )}
  </group>