UNPKG

@threlte/core

Version:

A 3D framework for the web, built on top of Svelte and Three.js

123 lines (122 loc) 4.06 kB
import { useThrelte } from '../../../context/compounds/useThrelte.js'; import { resolvePropertyPath } from '../../../utilities/index.js'; const ignoredProps = new Set(['$$scope', '$$slots', 'type', 'args', 'attach', 'instance']); const updateProjectionMatrixKeys = new Set([ 'fov', 'aspect', 'near', 'far', 'left', 'right', 'top', 'bottom', 'zoom' ]); /** * Only scalar values are memoized, objects and arrays are considered * non-equa by default, to ensure reactivity works as you would * expect in svelte. * @param value * @returns */ export const memoizeProp = (value) => { // scalar values are memoized if (typeof value === 'string') return true; if (typeof value === 'number') return true; if (typeof value === 'boolean') return true; if (typeof value === 'undefined') return true; if (value === null) return true; // objects and arrays cannot be reliably memoized return false; }; const createSetter = (target, key, value) => { if (!Array.isArray(value) && typeof value === 'number' && typeof target[key] === 'object' && target[key] !== null && typeof target[key]?.setScalar === 'function' && // colors do have a setScalar function, but we don't want to use it, because // the hex notation (i.e. 0xff0000) is very popular and matches the number // type. So we exclude colors here. !target[key]?.isColor) { // edge case of setScalar setters return (target, key, value) => { target[key].setScalar(value); }; } else { if (typeof target[key]?.set === 'function' && typeof target[key] === 'object' && target[key] !== null) { // if the property has a "set" function, we can use it if (Array.isArray(value)) { return (target, key, value) => { target[key].set(...value); }; } else { return (target, key, value) => { target[key].set(value); }; } } else { // otherwise, we just set the value return (target, key, value) => { target[key] = value; }; } } }; export const useProps = () => { const { invalidate } = useThrelte(); const memoizedProps = new Map(); const memoizedSetters = new Map(); const setProp = (instance, propertyPath, value, manualCamera) => { if (memoizeProp(value)) { const memoizedProp = memoizedProps.get(propertyPath); if (memoizedProp && memoizedProp.instance === instance && memoizedProp.value === value) { return; } memoizedProps.set(propertyPath, { instance, value }); } const { key, target } = resolvePropertyPath(instance, propertyPath); if (value !== undefined && value !== null) { const memoizedSetter = memoizedSetters.get(propertyPath); if (memoizedSetter) { memoizedSetter(target, key, value); } else { const setter = createSetter(target, key, value); memoizedSetters.set(propertyPath, setter); setter(target, key, value); } } else { createSetter(target, key, value)(target, key, value); } if (manualCamera) return; if (updateProjectionMatrixKeys.has(key) && (target.isPerspectiveCamera || target.isOrthographicCamera)) { target.updateProjectionMatrix(); } }; const updateProp = (instance, key, value, pluginsProps, manualCamera) => { if (!ignoredProps.has(key) && !pluginsProps?.includes(key)) { setProp(instance, key, value, manualCamera); } invalidate(); }; return { updateProp }; };