UNPKG

react-native-filament

Version:

A real-time physically based 3D rendering engine for React Native

125 lines (113 loc) 5.5 kB
import { useEffect, useRef } from 'react'; import { useFilamentContext } from '../useFilamentContext'; import { areFloat3Equal, isWorkletSharedValue } from '../../utilities/helper'; import { useWorkletEffect } from '../useWorkletEffect'; import { Worklets } from 'react-native-worklets-core'; /** * Takes the next entity from the context and applies all transformations from the next * transformation context to it */ export function useApplyTransformations({ to: entity, transformProps, aabb }) { const { translate: position, scale, rotate, transformToUnitCube, multiplyWithCurrentTransform = true } = transformProps ?? {}; const { transformManager } = useFilamentContext(); // TODO: multiplying current transformations is a bit problematic with react. // E.g. in strict mode or concurrent rendering our effects can be called multiple times. // Running an effect multiple times with transformation multiplication can lead to unexpected results. const prevScale = useRef(null); const prevRotate = useRef(null); const prevPosition = useRef(null); useEffect(() => { if (entity == null) return; if (transformToUnitCube && aabb != null) { transformManager.transformToUnitCube(entity, aabb); } if (!isWorkletSharedValue(scale) && Array.isArray(scale) && (prevScale.current == null || !areFloat3Equal(scale, prevScale.current))) { transformManager.setEntityScale(entity, scale, multiplyWithCurrentTransform); prevScale.current = scale; } if (!isWorkletSharedValue(rotate) && Array.isArray(rotate) && (prevRotate.current == null || !areFloat3Equal(rotate, prevRotate.current))) { const [x, y, z] = rotate; transformManager.setEntityRotation(entity, x, [1, 0, 0], multiplyWithCurrentTransform); // Rotation across axis is one operation so we need to always multiply the remaining rotations: transformManager.setEntityRotation(entity, y, [0, 1, 0], true); transformManager.setEntityRotation(entity, z, [0, 0, 1], true); prevRotate.current = rotate; } if (!isWorkletSharedValue(position) && Array.isArray(position) && (prevPosition.current == null || !areFloat3Equal(position, prevPosition.current))) { transformManager.setEntityPosition(entity, position, multiplyWithCurrentTransform); prevPosition.current = position; } }, [aabb, entity, multiplyWithCurrentTransform, position, prevPosition, prevRotate, prevScale, rotate, scale, transformManager, transformToUnitCube]); const prevScaleShared = useRef(isWorkletSharedValue(scale) ? Worklets.createSharedValue(null) : null); const prevRotateShared = useRef(isWorkletSharedValue(rotate) ? Worklets.createSharedValue(null) : null); const prevPositionShared = useRef(isWorkletSharedValue(position) ? Worklets.createSharedValue(null) : null); // Effects for when a transform option is a shared value (SRT) useWorkletEffect(() => { 'worklet'; if (entity == null) return; const unsubscribers = []; // Generic handler for worklet transform values const createTransformHandler = (value, prevValueShared, updater) => { 'worklet'; if (value == null || !isWorkletSharedValue(value) || Array.isArray(value)) return null; const unsubscribe = value.addListener(() => { 'worklet'; // Check if value has changed to avoid duplicate applications in strict mode if (prevValueShared !== null && prevValueShared !== void 0 && prevValueShared.value && areFloat3Equal(value.value, prevValueShared.value)) { return; } updater(value.value); // Update previous value tracker if (prevValueShared) { prevValueShared.value = [value.value[0], value.value[1], value.value[2]]; } }); unsubscribers.push(unsubscribe); return value; }; // Set up handlers for each transform type const scaleHandler = createTransformHandler(scale, prevScaleShared.current, newScale => { 'worklet'; transformManager.setEntityScale(entity, [newScale[0], newScale[1], newScale[2]], multiplyWithCurrentTransform); }); const rotateHandler = createTransformHandler(rotate, prevRotateShared.current, newRotate => { 'worklet'; const [x, y, z] = newRotate; transformManager.setEntityRotation(entity, x, [1, 0, 0], multiplyWithCurrentTransform); // Rotation across axis is one operation so we need to always multiply the remaining rotations: transformManager.setEntityRotation(entity, y, [0, 1, 0], true); transformManager.setEntityRotation(entity, z, [0, 0, 1], true); }); const positionHandler = createTransformHandler(position, prevPositionShared.current, newPosition => { 'worklet'; transformManager.setEntityPosition(entity, newPosition, multiplyWithCurrentTransform); }); // Trigger initial values in order: scale -> rotation -> position if (scaleHandler) { scaleHandler.value = [scaleHandler.value[0], scaleHandler.value[1], scaleHandler.value[2]]; } if (rotateHandler) { rotateHandler.value = [rotateHandler.value[0], rotateHandler.value[1], rotateHandler.value[2]]; } if (positionHandler) { positionHandler.value = [positionHandler.value[0], positionHandler.value[1], positionHandler.value[2]]; } return () => { 'worklet'; unsubscribers.forEach(unsubscribe => unsubscribe()); }; }); } //# sourceMappingURL=useApplyTransformations.js.map