Transforming objects in the scene
Transforming refers to changing the position, rotation or scale of an object in the scene.
Translate / Position
By default models are rendered at the origin [0, 0, 0]
. You can change the position of a model by providing a translate
prop:
import { Model } from "react-native-filament"
const x = 0
const y = -1
const z = 0
<Model translate={[x, y, z]} />
Units in filament are physically based, so 1 unit is 1 meter.
Rotation
You can rotate a model by providing a rotation
prop:
import { Model } from "react-native-filament"
const rotationInDegree = 45;
const angleInRadians = rotationInDegree * (Math.PI / 180);
const rotateOnAxis = [0, angleInRadians, 0]; // Rotate on the y-axis
<Model rotate={rotateOnAxis} />
The rotation
props expects a 3D vector (called Float3
in RNF), where each value represents the rotation in radians around the x, y and z axis respectively.
Scale
You can scale a model by providing a scale
prop for the x, y and z axis:
import { Model } from "react-native-filament"
const scale = 2;
<Model scale={[scale, scale, scale]} />
Matrix multiplication
By default the transformation props are being multiplied to the current transform of the model. They are being multiplied in the order scale
-> rotation
-> position
(SRT).
This means that if you first apply a scale of [2,2,2]
and then later apply a scale of [3,3,3]
the final scale will be [6,6,6]
. To avoid this behaviour you
can specify the multiplyWithCurrentTransform
prop and set it to false to disable this behaviour:
import { Model } from "react-native-filament"
function GrowingModel() {
const [scale, setScale] = useState(1);
const growModel = () => setScale(scale + 1);
return (
<Model
scale={[scale, scale, scale]}
// Disables multiplication with current, and grows the model by 1 unit each time growModel is called
multiplyWithCurrentTransform={false}
/>
)
}
When disabling the multiplication with the current transform, you'll reset all previous transformations (including rotation and position) and only apply the new transformations.
When matrix multiplication is enabled (default) all transform operations will be applied on a fast refresh. Keep this in mind during development if something starts to look off.
Animate the transform props
You may want to change the transformation props very frequently (e.g. every frame). To avoid performance issues, you should not update the props using useState
directly, but use shared values.
You may know shared values from reanimated
. We are using the shared values from react-native-worklets-core
, but they work very similary.
You can create a new shared value using useSharedValue
:
import { useSharedValue } from "react-native-worklets-core";
function MyScene() {
const rotation = useSharedValue(0);
// ...
}
You can pass a shared value to any of the transformation props:
function MyScene() {
const rotation = useSharedValue<Float3>([0, 0, 0]);
return <Model rotate={rotation} />
}
Now, lets update the rotation value every frame:
import { useCallback } from 'react'
import { useSharedValue } from "react-native-worklets-core"
import { RenderCallback, FilamentView, Model, Float3 } from "react-native-filament"
function MyScene() {
const rotation = useSharedValue<Float3>([0, 0, 0]);
const renderCallback: RenderCallback = useCallback(() => {
"worklet"
// Add a rotation of 1 degree every frame
const newY = rotation.value[1] + 0.01,
// We need to create a new array for the internal listeners to trigger
rotation.value = [0, newY, 0]
}, [rotation])
return (
<FilamentView renderCallback={renderCallback}>
<Model rotate={rotation} />
</FilamentView>
)
}
Transform to unit cube
There is a helper prop that you can use called transformToUnitCube
which will make the asset fit into a 1x1x1 (unit) cube.
Apply imperatively using TransformManager
Sometimes you have use cases where you want to update a model's transformation imperatively.
For this we can use the TransformManager
directly.
For this, you first need to render the model using the useModel
and the accompanying ModelRenderer
component:
import { useModel, ModelRenderer } from "react-native-filament"
function MyScene() {
const model = useModel(MyModel)
return <ModelRenderer model={model} />
}
ModelRenderer
accepts all props that Model
does.
The TransformManager
only works on entities, so we need to select one first. A model is made out of many entities. When transforming our model we usually want to transform all entities of the model (isValidElement. the whole model).
For that we can use the rootEntity
:
import { useModel } from "react-native-filament"
const model = useModel(MyModel)
const rootEntity = model.state === "loaded" ? model.rootEntity : undefined
Now we can use the TransformManager
to apply transformations. For example for rotating a model continuously, we can just apply a multipying rotation every frame:
import { useCallback } from 'react'
import { TransformManager, useModel, RenderCallback } from "react-native-filament"
function MyScene() {
const model = useModel(MyModel)
const rootEntity = model.state === "loaded" ? model.rootEntity : undefined
// We get the transformManager for the filament context.
// For this to work <MyScene /> needs to be wrapped with a <FilamentScene>
const { transformManager } = useFilamentContext()
const renderCallback: RenderCallback = useCallback(() => {
"worklet"
// We have to check if the model is ready yet:
if (rootEntity == null) return;
// Add a rotation of 1 degree every frame on the y-axis and
// multiply it with the current transform by setting `true` as last argument
transformManager.setEntityRotation(rootEntity, 0.01, [0, 1, 0], true)
}, [rootEntity, transformManager])
}
The transformManager
has lots of APIs, you can check them out in the API docs.
Setting all transform data yourself
Using the transformManager
its also possible to manually set a 4x4 matrix yourself, using a 16 element array:
transformManager.setTransform(entity, [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
])
Matrices in filament are column-major.