Skip to content

Commit be486cb

Browse files
authored
fix: make FilamentAPIContextProvider so consumer can implement offscreen recording (#198)
1 parent 2122ab9 commit be486cb

File tree

5 files changed

+43
-40
lines changed

5 files changed

+43
-40
lines changed

package/example/Shared/src/AnimationTransitionsRecording.tsx

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,24 @@
11
import * as React from 'react'
2-
import { useCallback } from 'react'
2+
import { useCallback, useMemo } from 'react'
33
import { Button, Dimensions, PixelRatio, StyleSheet, View } from 'react-native'
44
import {
55
Float3,
66
useModel,
77
useAnimator,
88
getAssetFromModel,
9-
FilamentProvider,
109
useFilamentContext,
11-
useDisposableResource,
1210
useSkybox,
1311
useRecorder,
1412
useRecorderRenderLoop,
13+
Camera,
14+
FilamentAPIContextProvider,
1515
} from 'react-native-filament'
16-
import { useDefaultLight } from './hooks/useDefaultLight'
17-
import { getAssetPath } from './utils/getAssetPasth'
1816
import { useRunOnJS, useSharedValue } from 'react-native-worklets-core'
17+
import PenguGlb from '~/assets/pengu.glb'
18+
import PirateGlb from '~/assets/pirate.glb'
19+
import { DefaultLight } from './components/DefaultLight'
1920
import Video from 'react-native-video'
2021

21-
const penguModelPath = getAssetPath('pengu.glb')
22-
const pirateHatPath = getAssetPath('pirate.glb')
23-
2422
// Camera config:
2523
const cameraPosition: Float3 = [0, 0, 8]
2624
const cameraTarget: Float3 = [0, 0, 0]
@@ -34,39 +32,37 @@ const DURATION = 3 // seconds
3432

3533
function Renderer() {
3634
const { camera } = useFilamentContext()
37-
useDefaultLight()
3835
useSkybox({ color: '#88defb' })
3936

40-
const pengu = useModel({ source: penguModelPath })
41-
const penguAsset = getAssetFromModel(pengu)
42-
const pirateHat = useModel({ source: pirateHatPath })
37+
const pengu = useModel(PenguGlb)
38+
const penguAnimator = useAnimator(pengu)
39+
const pirateHat = useModel(PirateGlb)
4340
const pirateHatAsset = getAssetFromModel(pirateHat)
44-
const pirateHatAnimator = useDisposableResource(() => {
45-
if (pirateHatAsset == null || penguAsset == null) {
46-
return undefined
47-
}
48-
return Promise.resolve(pirateHatAsset.createAnimatorWithAnimationsFrom(penguAsset))
49-
}, [pirateHatAsset, penguAsset])
41+
const pirateHatInstance = useMemo(() => pirateHatAsset?.getInstance(), [pirateHatAsset])
5042

51-
const penguAnimator = useAnimator(penguAsset)
43+
// Sync pirate hat animation with pengu
44+
React.useEffect(() => {
45+
if (penguAnimator == null || pirateHatInstance == null) return
46+
const id = penguAnimator.addToSyncList(pirateHatInstance)
47+
return () => {
48+
penguAnimator.removeFromSyncList(id)
49+
}
50+
}, [penguAnimator, pirateHatInstance])
5251

5352
const renderCallback = useCallback(
5453
(passedSeconds: number) => {
5554
'worklet'
5655
camera.lookAt(cameraPosition, cameraTarget, cameraUp)
5756

58-
if (pirateHatAnimator == null || penguAnimator == null) {
57+
if (penguAnimator == null) {
5958
return
6059
}
6160

6261
// Update the animators to play the current animation
6362
penguAnimator.applyAnimation(0, passedSeconds)
64-
pirateHatAnimator.applyAnimation(0, passedSeconds)
65-
6663
penguAnimator.updateBoneMatrices()
67-
pirateHatAnimator.updateBoneMatrices()
6864
},
69-
[camera, pirateHatAnimator, penguAnimator]
65+
[camera, penguAnimator]
7066
)
7167

7268
const framesToRender = DURATION * FPS
@@ -145,7 +141,13 @@ function Renderer() {
145141

146142
return (
147143
<View style={styles.container}>
148-
{videoUri != null ? (
144+
{videoUri == null ? (
145+
// Render our scene that we want to record
146+
<>
147+
<Camera />
148+
<DefaultLight />
149+
</>
150+
) : (
149151
<Video
150152
style={{ flex: 1 }}
151153
paused={false}
@@ -156,17 +158,18 @@ function Renderer() {
156158
onLoad={() => console.log('On load')}
157159
onEnd={() => console.log('On end')}
158160
/>
159-
) : null}
161+
)}
160162
<Button onPress={onStartRecording} title={'Start recording'} />
161163
</View>
162164
)
163165
}
164166

165167
export function AnimationTransitionsRecording() {
166168
return (
167-
<FilamentProvider>
169+
// Provide the API necessary for recording (accessing the RNF apis) in a react context
170+
<FilamentAPIContextProvider>
168171
<Renderer />
169-
</FilamentProvider>
172+
</FilamentAPIContextProvider>
170173
)
171174
}
172175

package/example/Shared/src/App.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { GestureHandlerRootView } from 'react-native-gesture-handler'
66

77
import { AnimationTransitions } from './AnimationTransitions'
88
import { CameraPan } from './CameraPan'
9+
import { AnimationTransitionsRecording } from './AnimationTransitionsRecording'
910
// import { PhysicsCoin } from './PhysicsCoin'
1011
// import { FadeOut } from './FadeOut'
1112
// import { CastShadow } from './CastShadow'
@@ -14,7 +15,6 @@ import { CameraPan } from './CameraPan'
1415
// import { ScaleEffect } from './ScaleEffect'
1516
// import { FadingLightExample } from './FadingLightExample'
1617
// import { ChangeGoldenMaterials } from './ChangeGoldenMaterial'
17-
// import { AnimationTransitionsRecording } from './AnimationTransitionsRecording'
1818

1919
function NavigationItem(props: { name: string; route: string }) {
2020
const navigation = useNavigation()
@@ -47,8 +47,8 @@ function HomeScreen() {
4747
<ScrollView style={{ flex: 1 }}>
4848
<NavigationItem name="▶️ Animation Transitions" route="AnimationTransitions" />
4949
<NavigationItem name="📸 Camera Pan" route="CameraPan" />
50-
{/* <NavigationItem name="📹 Offscreen recording" route="AnimationTransitionsRecording" />
51-
<NavigationItem name="💰 Physics Coin" route="PhysicsCoin" />
50+
<NavigationItem name="📹 Offscreen recording" route="AnimationTransitionsRecording" />
51+
{/* <NavigationItem name="💰 Physics Coin" route="PhysicsCoin" />
5252
<NavigationItem name="😶‍🌫️ Fade Out" route="FadeOut" />
5353
<NavigationItem name="🎨 Change Materials" route="ChangeMaterials" />
5454
<NavigationItem name="🌑 Cast Shadow" route="CastShadow" />
@@ -84,9 +84,9 @@ function App() {
8484
}}
8585
/>
8686
<Stack.Screen name="CameraPan" component={CameraPan} />
87+
<Stack.Screen name="AnimationTransitionsRecording" component={AnimationTransitionsRecording} />
8788
{/* TODO: Migrate */}
88-
{/* <Stack.Screen name="AnimationTransitionsRecording" component={AnimationTransitionsRecording} />
89-
<Stack.Screen name="PhysicsCoin" component={PhysicsCoin} />
89+
{/* <Stack.Screen name="PhysicsCoin" component={PhysicsCoin} />
9090
<Stack.Screen name="FadeOut" component={FadeOut} />
9191
<Stack.Screen name="ChangeMaterials" component={ChangeGoldenMaterials} />
9292
<Stack.Screen name="CastShadow" component={CastShadow} />

package/src/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,4 @@ export * from './react/RenderCallbackContext'
3131
export * from './react/EnvironmentalLight'
3232
export * from './react/Light'
3333
export * from './react/Skybox'
34+
export * from './react/FilamentAPIContextProvider'

package/src/react/Filament.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,9 @@ function FilamentViewWithRenderCallbacks({ children, ...forwardProps }: PropsWit
4747
export function Filament({ children, enableTransparentRendering, style, ...props }: Props) {
4848
return (
4949
<FilamentAPIContextProvider {...props}>
50-
<RenderCallbackContext.RenderContextProvider>
51-
<FilamentViewWithRenderCallbacks enableTransparentRendering={enableTransparentRendering} style={style}>
52-
{children}
53-
</FilamentViewWithRenderCallbacks>
54-
</RenderCallbackContext.RenderContextProvider>
50+
<FilamentViewWithRenderCallbacks enableTransparentRendering={enableTransparentRendering} style={style}>
51+
{children}
52+
</FilamentViewWithRenderCallbacks>
5553
</FilamentAPIContextProvider>
5654
)
5755
}

package/src/react/FilamentAPIContextProvider.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useWorklet } from 'react-native-worklets-core'
66
import React from 'react'
77
import { Configurator, RendererConfigProps, ViewConfigProps } from './Configurator'
88
import { FilamentContext, FilamentContextType } from './FilamentContext'
9+
import { RenderCallbackContext } from './RenderCallbackContext'
910

1011
export type FilamentProviderProps = PropsWithChildren<
1112
Omit<EngineProps, 'context'> &
@@ -18,7 +19,7 @@ export type FilamentProviderProps = PropsWithChildren<
1819
/**
1920
* Creates an engine and all filament APIs and provides them to the children using the react context.
2021
*
21-
* @private
22+
* @note You only need this for doing offscreen recording. For an on-screen surface use the `Filament` component.
2223
*/
2324
export function FilamentAPIContextProvider({ children, fallback, config, backend, frameRateOptions, ...viewProps }: FilamentProviderProps) {
2425
// First create the engine, which we need to create (almost) all other filament APIs
@@ -81,7 +82,7 @@ export function FilamentAPIContextProvider({ children, fallback, config, backend
8182
return (
8283
<FilamentContext.Provider value={value}>
8384
<Configurator rendererProps={rendererProps} viewProps={viewProps}>
84-
{children}
85+
<RenderCallbackContext.RenderContextProvider>{children}</RenderCallbackContext.RenderContextProvider>
8586
</Configurator>
8687
</FilamentContext.Provider>
8788
)

0 commit comments

Comments
 (0)