Skip to main content

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}
/>
)
}
warning

When disabling the multiplication with the current transform, you'll reset all previous transformations (including rotation and position) and only apply the new transformations.

warning

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.