-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Event system shouldn't assume a single, builtin Raycaster #2117
Comments
also quite unhappy with the current implementation tbh. while useCamera hacks things like HUD's it won't fix gl.skizzor or (sub-)events on planes that portal content. would you want to implement a draft? pls target v8 branch though. |
Sure thing, just wanted to get opinions on this first :) |
In light of Andrew's PR, I think it might make sense to talk about adding a Scene primitive already, I think we have been gravitating towards this for a while.
the Scene object would:
some examples: GUI: <MyModel />
<Scene autoClear={false} orthographic camera={{ zoom: 100 }}>
<Text>Hello GUI</Text>
</Scene> FBO stuff: const fbo = useFBO()
<Scene renderTarget={fbo} > ... </Scene>
<mesh>
...
<meshBasicMaterial map={fbo.texture />
</mesh> This could also be made to work with the invalidate API (invalidate per scene or global, for example). Now the default Canvas could be expressed as Canvas (webgl context) + Scene (scene graph concerns) ( probably transparent to the user? ) Pros:
Cons:
Notes
|
Re: @gsimone's Scene component, yesterday I brought up exactly this to Paul which resulted in this beautiful but hacky user land prototype, thought I'd paste it here so it doesn't get lost. This is a generalized solution that lets you hijack the whole r3f context. I do agree that ideally we'd also have an abstraction for the render target, which this solution doesn't provide. import { ReactNode, useMemo } from "react";
import { RootState, useStore, context } from "@react-three/fiber";
function resolveState(
state:
| Partial<RootState>
| ((state: Partial<RootState>) => Partial<RootState>),
current: RootState
) {
return typeof state === "function" ? state(current) : state;
}
interface InjectProps {
children: ReactNode;
state: Partial<RootState>;
}
export const Inject = ({ children, state }: InjectProps) => {
const useOriginalStore = useStore();
const injected = useMemo(() => {
const useInjected = Object.assign(
(sel: any) => {
// Fetch fresh state
const current = useOriginalStore.getState();
// Execute the useStore hook with the selector once, to maintain reactivity, result doesn't matter
// @ts-ignore
useOriginalStore(sel);
// Inject data
const altered = { ...current, ...resolveState(state, current) };
// And return the result, either selected or raw
return sel ? sel(altered) : altered;
},
{
getState: () => {
const current = useOriginalStore.getState();
return { ...current, ...resolveState(state, current) };
},
subscribe: (callback: any) => {
return useOriginalStore.subscribe((current) =>
callback({ ...current, ...resolveState(state, current) })
);
},
}
) as typeof useOriginalStore;
return useInjected;
}, [useOriginalStore, state]);
// Wrap the hijacked store into a new provider using r3f's default context
return <context.Provider value={injected}>{children}</context.Provider>;
}; |
Another thing we talked about is maybe probably tying the event layer creation to |
Btw I'd like to direct everyone to the event layer PR (#2133) for any discussion strictly about event layers, since this issue wasn't updated with newer ideas incorporated in that PR. |
This would be great - it would allow me to remove some hacky stuff in my own code (didn't know about Does this require two BTW here's an example that does weird stuff with cameras. It does a transition from ortho -> perspective by lerping the projection matrices while also lerping camera position and rotation. It was previously breaking lots of raycaster stuff. I think we got that all resolved now, but it's still probably a useful testing ground for raycaster/camera changes since there's a weird intermediate pers/ortho matrix during the transition (there's also still a few bugs in the code, especially if you keeping hitting space 😅). |
I like the idea of a
Yeah, this is a good point. I think it's important to consider the terms used in the greater CG/graphics/three.js community when choosing names here as it can lead to confusion for people coming from a gamedev background instead of a webdev background, or people referring to the three.js docs and then looking for matching functionality here - see my confusion over the term "viewport", for example: #1892. It can also be a limitation if we later decide functionality of the "actual" term is needed, then we need to create another new name leading to even more mismatch in terms. |
@gsimone this isn't straight forward, as well as the inject hacks. the problem is this:
but events do not come from within the component tree, they don't have access to it or even have context, it's just addEventListener on the canvas dom node. when they raycast they need to access the camera from root-state. they don't know that some mesh had an onion layer way up the component tree and iterating upwards doesn't seem feasible. a solution could be https://github.com/facebook/react/tree/main/packages/react-reconciler#getchildhostcontextparenthostcontext-type-rootcontainer theoretically a node can inject a context (any object) that is then known throughout the sub graph. react-dom uses that for svg's. <inject camera={foo} />
<group>
<group>
<ComponentThatNeedsTheSystemCamera /> but the api is a bit strange: getChildHostContext(parentHostContext, type, rootContainer) only gives me a string type, no access to props. but maybe i can create an empty getChildHostContext(type) {
if (type === "inject") return {}
createInstance(type, props, context) {
const instance = new THREE[type]
if (type === "inject") {
context.type = type
context.props = props
instance.r3f.context = context
}
prepareUpdate(type, props, context) {
if (type === "inject") {
context.type = type
context.props = props
instance.r3f.context = context
} Sorry for all this reconciler gibberish 😅 |
i looked into the reconciler stuff and sadly, it won't work: facebook/react#24138 |
Continuing scene/inject discussion in #2142. |
This looks to be implemented with #2153 of v8, but happy to re-open if anything comes up. |
Edit: for future discussion see #2133, where these ideas are being refined and implemented.
The title is a little vague so let me explain.
How the current event system works is:
store.interaction
store.interaction
are raycast using the builtinRaycaster
and default cameraThis system works correctly for most of the use cases, however it fails if certain objects need to use a different
Raycaster
, for example to render an overlay scene for a gizmo. For example, in this Drei example, if we were to add pointer event handlers to the main mesh callinge.stopPropagation()
, the gizmo would stop being interactive when it's covering the geometry, since the event system assumes that it is using the sameRaycaster
and according to the hit, it is behind the mesh. Even if we don't calle.stopPropagation()
on the mesh, we would have the issue that we wouldn't be able to stop clicks from propagating to the main scene when the gizmo is clicked.The above use case is common enough that Drei provides a
useCamera
helper that helps use aRaycaster
set from the perspective of a camera different from the default one. However,useCamera
is a massive hack, because it works by overriding theraycast
method of meshes with one that ignores the passedRaycaster
and instead uses theRaycaster
created by theuseCamera
hook from the passed camera. Meaning r3f's event system has no knowledge of this and will fully assume that these meshes are also raycast with the internalRaycaster
, which results in the above described incorrect behavior.Proposal
Instance
declaration with the optionaleventLayer
property that takes a{ raycaster: Raycaster, priority: number }
object.store.interaction
by raycaster.e.stopPropagation
also stops the event from propagating to lower priority raycaster groups.Objects without an
eventLayer
prop would be grouped to the builtin default raycaster using the default camera.This solution also has the advantage that current projects using
useCamera
could be adapted with minimal effort:useCamera
would still create its own raycaster, however instead of returning a hackedraycast
method, it'd return theeventLayer
object that can be passed to meshes.What do you think? I'm curious to hear any corrections, extensions or counter-proposals! I'm sure I left some things out and some parts might be unclear, please ask any questions you might have.
The text was updated successfully, but these errors were encountered: