-
-
Notifications
You must be signed in to change notification settings - Fork 108
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
feat: 503 conditional rendering of primitives #514
feat: 503 conditional rendering of primitives #514
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Glad to have this update!
I only found a small bit of code that looks like it can be simplified. I won't hold up the PR.
@@ -39,7 +39,8 @@ export const nodeOps: RendererOptions<TresObject, TresObject> = { | |||
if (props?.object === undefined) logError('Tres primitives need a prop \'object\'') | |||
const object = props.object as TresObject |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
props.object
, object
and instance
all point to the same JS object here, so it looks like this can be simplified:
[....]
if (tag === 'primitive') {
if (props.object === undefined) logError('Tres primitives need a prop \'object\'')
instance = props.object as TresObject
instance.userData.tres__primitive = true
}
else {
[...]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @andretchen0, thanks for checking it, I ended up modifying this part because we need to clone the prop.object
to avoid overwriting the original three object when swapping happens.
Do you need a hand with the failing tests and linter? |
It's pretty much done @andretchen0 if you take another look before merging 🙏🏻 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if it can be worked around, but cloning the Three objects means that references in setup
are lost. Details in the comments.
@@ -39,7 +39,8 @@ export const nodeOps: RendererOptions<TresObject, TresObject> = { | |||
if (props?.object === undefined) logError('Tres primitives need a prop \'object\'') | |||
const object = props.object as TresObject | |||
name = object.type | |||
instance = Object.assign(object, { type: name, attach: props.attach, primitive: true }) | |||
instance = Object.assign(object.clone(), { type: name }) as TresObject |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if there's a workaround, but using a clone here means that references in a component's setup won't work.
I've added a comment to playground/src/pages/primitives.vue with some code that demonstrates the problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@andretchen0 Thats a good point thanks for raising it, the problem with the reference comes when we try to make the :object
reactive to swap the instance, which completely changes the ref.
Not sure how to deal with that.
color: '#82DBC5', | ||
}), | ||
) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In nodeOps.ts
, I mentioned that cloning means that setup references are lost. Below is some demonstration code that can be pasted here.
useRenderLoop().onLoop(() => {
torus.rotateX(0.01)
torusKnot.rotateY(0.01)
})
Both torus
and torusKnot
continue to exist after the clone
, but they no longer point to the on-screen objects, so the on-screen objects don't update.
If the objects aren't clone()
d in nodeOps
, the on-screen shapes will rotate – but other errors pop up.
I don't know if there's a workaround here. Just pointing it out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@andretchen0 This argument makes me think we should abandon the idea of trying to make :object
prop reactive, swiping instances without losing the ref is getting too complicated.
Users could still use primitives with conditional rendering, I wanted primitive
to work similar to component
but honestly at this point I don't know how to achieve it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand the internals of Tres well enough to have an opinion on the feasibility here.
I'll start reading the source after I finish <AnimatedSprite />
for Cientos.
I wanted primitive to work similar to component but honestly at this point I don't know how to achieve it.
I agree that this is a worthwhile goal. It'd be great if it "just works".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@andretchen0 let me know if you are up to a pair-programming call to discuss the internals when you get free let me know, we definitely can use your knowledge and your quality feedback inside of the custom renderer code. 💚
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure thing. That could be fun!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @andretchen0 I created a thread on the core team discord channel discussing this but to give a summary:
I found a way of making it work (animations), the only constraint is that the object passed through :object
needs to be of the same type (mesh -> mesh) (group -> group). If we do (mesh -> group), it doesn't work anymore.
Screen_Recording_2024-02-02_at_12.43.44.mov
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I'll go read the Discord.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, I'm not sure if this can be worked around, but there's still a problem with references in <script>
.
if (node) { | ||
let root = node | ||
let key = prop | ||
if (node.__tres.primitive && key === 'object' && prevValue !== null) { | ||
// If the prop 'object' is changed, we need to re-instance the object and swap the old one with the new one | ||
const newInstance = nodeOps.createElement('primitive', undefined, undefined, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like this doesn't break references in <script>
per se. The :object
s still exist. But they're copied. That means something like the following (probably) wouldn't have the user's desired effect in the playground example:
useRenderLoop().onLoop(({elapsed}) => {
torus.position.y = Math.sin(elapsed)
})
Whenever the primitive object becomes torus
, torus
is copied, along with its attributes. But the copy doesn't move like torus
moves, because it's a different Torus
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@andretchen0 we will need to explicitly mention on the docs that the user needs to animate the ref
(primitiveRef) but not the object
(torus) following the same DX as they were using a <TorusMesh ref="torusRef" />
Screen.Recording.2024-02-08.at.10.14.03.mov
There are several reasons why we need to clone
the object:
- Since we soft-swipe the instances when
:object
changes (we maintain the reference but copy all the attributes) that would modify the original object from the setup leading to unwanted side effects.
It is not possible to pass aref
orreactive
as @JaimeTorrealba mentioned in the thread. With clone, we can ensure that swiping between models is possible.
This is what happens without the clone
Screen.Recording.2024-02-08.at.10.24.31.mov
It's either has this limitation of having to use the ref coming from the custom renderer to animate or :object
can't be reactive and users will need to use v-if
and v-else
to swipe models:
<primitive v-if="showA" :object="modelA" />
<primitive v-else :object="modelB" />
I balance through the first option.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the PR as it currently stands is an improvement. I don't want to stand in the way of improvements.
I approved the PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sort of error is an itch I can't stop scratching, so if you want to keep scratching ...
Since we soft-swipe the instances when :object changes (we maintain the reference but copy all the attributes) that would modify the original object from the setup leading to unwanted side effects.
Can you elaborate on unwanted side effects?
Technical issues aside, there's a point that isn't clear for me conceptually. To illustrate:
<script>
...
const userMesh = new Mesh(...)
userMesh.position.x = 100
const userMesh2 = new Mesh(...)
userMesh2.position.x = 200
</script>
<template>
...
<primitive ref="primitiveRef" :object="userMesh" :position-x="300" />
...
</template>
- Should
userMesh
's x be 300? (300 is the value in theprimitive
) - And later, if the user does
primitiveRef.value.object = userMesh2
, does that setuserMesh2
's x to 300?
For R3F, the answer to both is "yes", it seems. Here's a StackBlitz. Notice that the rotation of the TorusRing increments with every frame, but resets whenever it's made the primitive.object
. And the Torus would normally be placed at x = 2000, but its x is set to the primitive's value.
That seems like the right solution to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Line 197 in 8408351
const newInstance = nodeOps.createElement('primitive', undefined, undefined, { |
I understand that this will make a clone, which is required at the moment, but is there a reason why it's necessary to go through nodeOps.createElement
? Is it updating something on the Vue side, maybe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you elaborate on unwanted side effects?
One side-effect is that is not possible to pass a ref or reactive as @JaimeTorrealba mentioned in the thread on Discord. With clone, we can ensure that swiping between models is possible.
Should userMesh's x be 300? (300 is the value in the primitive)
Yes, the prop has priority because it will overwrite the mesh positions on the setup.
And later, if the user does primitiveRef.value.object = userMesh2, does that set userMesh2's x to 300?
Users shouldn't overwrite vue template refs, wouldn't it be kind of an antipattern? I also see the users complaining about having to do this extra step of primitiveRef.value.object = userMesh2
vs :object
being reactive.
const newInstance = nodeOps.createElement('primitive', undefined, undefined, {
That was to not having to duplicate the same code of the createElement inside of the pathProp
since primitives have some special treatment, like localstate flags and such.
* feat: 474 vue chrome devtools plugin (#479) * feat: vue chrome devtools * feat: editable scenes from devtools * chore(lint): fix lint errors * feat: highlight material * chore(lint): fix * chore: release v4.0.0-next.0 * feat: update to three `v160` and vue `v3.4` (#488) * fix(types): added `Object3DEventMap` to `Object3D` generics for point event handling (#491) * feat: 140 on demand rendering (#497) * feat: conditional rendering * chore: remove subscribe system * feat: on-demand automatic invalidation with prop changes * feat: invalidate once first when is `renderMode !== 'always'` * docs: performance page, on-demand rendering * chore: fix windowsize issue * chore(lint): fix maximum line length issues * feat: invalidate on-demand on window resize * feat: add advance method for manual mode * feat: fix manual first render with advance * docs: performance manual mode * docs: add badge with version * chore: correct typos and PR suggestions * chore: tell dont ask fix * feat: render state instead of internal * feat: remove default camera warning (#499) * feat: remove annoying defautl camera warning * chore: remove logWarning * feat: 492 set tone mapping default to acesfilmictonemapping (#498) * feat: set ACESFilmicToneMapping as default toneMapping * chore: usage of nullish coealescing operator instead of ternaries * feat: 516 localstate for custom renderer node instances instead of userdata (#522) * feat: conditional rendering * chore: remove subscribe system * feat: on-demand automatic invalidation with prop changes * feat: invalidate once first when is `renderMode !== 'always'` * docs: performance page, on-demand rendering * chore: fix windowsize issue * chore(lint): fix maximum line length issues * feat: invalidate on-demand on window resize * feat: add advance method for manual mode * feat: fix manual first render with advance * docs: performance manual mode * docs: add badge with version * chore: correct typos and PR suggestions * chore: tell dont ask fix * feat: render state instead of internal * feat: add __tres local state to nodeOps instances * feat: add context to root on instances localstate * feat: camera registration ops from node local state ctx * feat: event handling registration from localState of nodes * feature: disposable flag on node localstate * feat: remove userData from types * chore: remove unused import * fix(test): fake localstate `.__tres` on tests * fix(types): fix nodeOps instances localstate type * fix: camera aspect * Update orthographic camera aspect when screen size updates * Give user a "manual" flag to keep Tres from updating camera * feat: 503 conditional rendering of primitives (#514) * feat(nodeOps): switch instance logic for reactive `object` prop * chore: playground primitives with models * chore: fix linter * chore: fix tests and linters, primitive object is now reactive * chore: refactor instance swaping logic to overwrite set and copy properties * chore: tests * chore: remove console.log * chore: remove unused import watch * feat: add primitive conditional to patch object prop * fix: `nodeOps` is now a function (#579) * fix: `nodeOps` is now a function * chore(test): updated tests for `nodeOps` * chore: next package json version * chore: release v4.0.0-next.1 * fix: refactor nodeOps to return methods at the end of the function (#602) * fix: refactor nodeOps to return methods at the end of the function * chore: fix lint * chore: internal playground organisation (#601) * chore: new internal playground org and testing pages * chore: fix lint * chore: better styling of playground landing page * chore: lint * chore: deps update * chore: internal primitive model test playground * chore: fix lint * chore: release v4.0.0-next.2 * chore: misc routes * fix: do not change pierced props case (#608) * chore: lint fix * chore: problem with package version * chore: fix lint * chore: rebuild pnpm-lock * test(nodeOps): clean up tests * test(nodeOps): organize tests * test: add coverage plugin * test: add coverage to package.json script * test(nodeOps): improve test coverage * feat: devtools renderer improvements (#614) * feat: renderer programs when selecting scene on devtools * feat: renderer.info * chore: fix lint * docs: devtools update * chore: fix lint issues * feat(events)!: pointerevents manager and state (#529) * new file: playground/src/components/Box.vue new file: playground/src/pages/raycaster/Propogation.vue * Started work on interactive Event Propogation playground example modified: src/components/TresCanvas.vue * Import and use `useEventStore` * defineEmits for all expected pointer events so we may emit propogated events off of the canvasa modified: src/composables/index.ts new file: src/composables/useEventStore/index.ts * Started work on an event store. I'm not sure this counts as a store just yet * Wired up majority of pointer events * Added event propogation * Does not require using userData scene props or nodeOps for registering objects to scene modified: src/composables/useRaycaster/index.ts * Added new event listeners to power newly supported pointer events. We now check whole scene/children when calling intersectObjects. * Created new EventHooks for new events * Added `forceUpdate` function that allows for pointer-move events to work without mouth movement (good for when camera is moving but mouse is not) modified: src/core/nodeOps.ts * Added supported events to array so they don't get received as props * (temporarily) unhook current pointer event solution to iterate on useEventStore modified: src/utils/index.ts * Added Camel-to-kebab case util * Support multiple event listeners, add support for .stop event modifier * Set stopProgation variable to false by default, whoops * fix typo * fix: remove `createGlobalState` from `useEventStore`, allowing events to work while multiple TresCanvas' are being used * fix(perf): remove extraneous intersectObjects/getIntersects calls by moving intersects into a ref that is updated on pointer-move * chore(lint): fix lint issues * feat: enhance events manager to include duplicates checking, pointer-missed support, and forced updating Per file changelog: modified: playground/src/components/Box.vue * Added a pointer-missed handler for testing modified: playground/src/pages/TheBasic.vue * uses forceUpdate from EventManager to fire events even when the mouse hasn't moved modified: playground/src/pages/raycaster/Propagation.vue * Didn't mean to undo the lint changes, adds a pointer-missed event on the canvas for extra testing modified: src/components/TresCanvas.vue * Adds `pointer-missed` as possible event for canvas emits modified: src/composables/index.ts * Update export deleted: src/composables/useEventStore/index.ts * Rename `useEventStore` to `useTresEventManager` modified: src/composables/useRaycaster/index.ts * Check for empty intersects on hit test, wire up pointerMissed events eventHook * Fix forceUpdate to call onPointerMove instead of triggering an EventHook modified: src/composables/useTresContextProvider/index.ts * Add TresEventManager type new file: src/composables/useTresEventManager/index.ts * add onPointerMissed * create (de)registerPointerMissedObj methods so we can track objects in the scene listening to this event * Note: These are passed to nodeOps via TresContext * Implement duplicates checking for eventPropogation modified: src/core/nodeOps.ts * register/deregister pointerMissed objects * chore: lint * docs: new event docs * chore: fix lint * feat: enhance event object details and use in Box example to change material color. Add ability to force event system updates even when mouse hasn't moved. Enhance pointer-enter/leave events. Update types Box.vue * Added pointer-missed handler * set the materials flash color using the object coming off of the event instead of a ref UseRaycaster * Flesh out event details to include * all mouse event properties * intersections * tres camera * camera raycaster * source event * mouse position delta * stopPropagating stub * and unprojectedPoint (this needs work, cant get the math to work) UseTresContextProvider * Add TresEventManager type to TresContext useTresEventManager * Add forceUpdate method to allow apps to force an event system update even when the mouse hasnt moved * Add pointerMissed event * Properly implement pointer-enter/pointer-leave events * Before now, pointer-enter | leave were only called on first object in intersection, now we execute the events for all entered/left objects * Use stopPropagating property included on event object * chore: lint * chore: fix lint issues --------- Co-authored-by: alvarosabu <[email protected]> * feat: 499 better memory management (#606) * chore: memory management playground * feat: recursively free cpu and gpu memory allocation on remove * chore: clumsy attempt to dispose on unmount * chore: lint fix * feat: remove scene root on disposal * chore: fix lint * docs: added disposal guide on `performance` docs * chore: fix lint * chore: type issues (#663) * fix: fix some internal types * chore: fix linters * fix: typescript issues on event manager * chore: release v4.0.0-rc.0 * fix: make on* callbacks settable (#672) * fix: make on- callbacks settable * test: test setting not calling * feat: 633 use loop (#673) * feat: createRenderLoop unique to context * feat: onLoop returns current state * feat: ensuring callback excecution with index order * feat: take control of render loop logic * docs: updated composable docs * feat: change error to deprecation warning towards v5 * chore: add link to new composable docs on deprecation warning * chore: remove depcreation warning of existing useRenderLoop * feat: `useFrame` and `useRender` instead of `onLoop` * chore: fix lint * feat: applied useFrame to directives * chore: fix lint * feat: `useUpdate` instead of `useFrame` and useRender pausing. * chore: testing fbo * feat: reserve index 1 for late-updates * chore: fix lint * feat: useLoop composable for the win * chore: change onLoop name for register * chore: unit tests for loop * chore: change order for registration to make index optional * chore: fix lint * feat: pauseRender and resumeRender * docs: useLoop guide * docs: updated basic animations recipe to `useLoop` * docs: correct pause render methods on docs * Update docs/api/composables.md Co-authored-by: Tino Koch <[email protected]> * Update docs/api/composables.md Co-authored-by: Tino Koch <[email protected]> * Update docs/api/composables.md Co-authored-by: Tino Koch <[email protected]> * Update docs/api/composables.md Co-authored-by: Tino Koch <[email protected]> * Update docs/api/composables.md Co-authored-by: Tino Koch <[email protected]> * Update docs/api/composables.md Co-authored-by: Tino Koch <[email protected]> * Update docs/api/composables.md Co-authored-by: Tino Koch <[email protected]> * chore: refactor subscribers to `priorityEventHooks` * Update docs/api/composables.md Co-authored-by: Tino Koch <[email protected]> * feat: just return `off` on the loop registration methods * docs: update docs to add `off` unregister callback method * feat: remove `v-rotate` * docs: added context warning for `v-always-look-at` * Update docs/api/composables.md Co-authored-by: Tino Koch <[email protected]> * Update docs/api/composables.md Co-authored-by: Tino Koch <[email protected]> * chore: remove leftover of isntance.provide * chore: remove subscribers from context * chore: abstract `wrapCallback` and move render loop register to `useRender` * chore: fix lint * chore: testing off * Revert "chore: abstract `wrapCallback` and move render loop register to `useRender`" This reverts commit 24cec65. * chore: return bound `off` method and use createPriorityEvent for render with defaultFn fallback * feat: deprecate and remove `vAlwaysLookAt` and `vRotate` BREAKING_CHANGE: Directives `vAlwaysLookAt` and `vRotate` due incompatibility with new `useLoop` and the refactor of the render loop logic. * feat: set context to loop to avoid wrapping the callbacks * feat: dispose render hook before taking over --------- Co-authored-by: Tino Koch <[email protected]> * chore(playground): adding missing import and removing the directives that were deprecated * chore(playground): use new composable on animations * fix(utils): reorder object disposal to avoid issue with Helper `dispose` methods (#683) * chore: updated deps * chore: release v4.0.0-rc.1 * fix: manual rendering blank (#685) * fix: increate time to advance on manual mode * chore: correct playground * fix: 686 useloop callback state missing controls (#687) * fix(loop): take plain snapshots of ctx * fix: types for useloop * chore: lint * docs: add RectAreaLightHelper to vLightHelper docs * chore(deps): update deps 24-0-2024 * chore: release v4.0.0-rc.2 * fix: start loop if user calls useRenderLoop (#695) * docs: change motivation * chore(deps): last update before release --------- Co-authored-by: Peter <[email protected]> Co-authored-by: Garrett Walker <[email protected]> Co-authored-by: Tino Koch <[email protected]> Co-authored-by: Jaime Torrealba <[email protected]> Co-authored-by: Jaime A Torrealba C <[email protected]>
Closes #503
Object
prop is not reactivev-if