diff --git a/package/cpp/bullet/RNFShapeWrapper.h b/package/cpp/bullet/RNFShapeWrapper.h index b40106ff..d1677e56 100644 --- a/package/cpp/bullet/RNFShapeWrapper.h +++ b/package/cpp/bullet/RNFShapeWrapper.h @@ -15,7 +15,7 @@ namespace margelo { */ class ShapeWrapper : public HybridObject { public: - explicit ShapeWrapper(const char* name, std::shared_ptr shape) : HybridObject(name), _shape(shape){}; + explicit ShapeWrapper(const char* name, std::shared_ptr shape) : HybridObject(name), _shape(shape) {}; void loadHybridMethods() override; diff --git a/package/cpp/core/RNFAABBWrapper.h b/package/cpp/core/RNFAABBWrapper.h index 86782c2a..0c3fc8a7 100644 --- a/package/cpp/core/RNFAABBWrapper.h +++ b/package/cpp/core/RNFAABBWrapper.h @@ -16,6 +16,10 @@ class AABBWrapper : public HybridObject { explicit AABBWrapper(const Aabb& aabb) : HybridObject("AABBWrapper"), _aabb(aabb) {} void loadHybridMethods() override; + Aabb getAabb() { + return _aabb; + } + private: // Exposed JS api std::vector getCenter(); std::vector getHalfExtent(); diff --git a/package/cpp/core/RNFFilamentAssetWrapper.cpp b/package/cpp/core/RNFFilamentAssetWrapper.cpp index f16a78a6..0bb472e2 100644 --- a/package/cpp/core/RNFFilamentAssetWrapper.cpp +++ b/package/cpp/core/RNFFilamentAssetWrapper.cpp @@ -18,7 +18,7 @@ void FilamentAssetWrapper::loadHybridMethods() { registerHybridMethod("getEntities", &FilamentAssetWrapper::getEntities, this); registerHybridGetter("renderableEntityCount", &FilamentAssetWrapper::getRenderableEntityCount, this); registerHybridMethod("getRenderableEntities", &FilamentAssetWrapper::getRenderableEntities, this); - registerHybridGetter("boundingBox", &FilamentAssetWrapper::getBoundingBox, this); + registerHybridMethod("getBoundingBox", &FilamentAssetWrapper::getBoundingBox, this); registerHybridMethod("getFirstEntityByName", &FilamentAssetWrapper::getFirstEntityByName, this); registerHybridMethod("getInstance", &FilamentAssetWrapper::getInstance, this); registerHybridMethod("getAssetInstances", &FilamentAssetWrapper::getAssetInstances, this); diff --git a/package/cpp/core/RNFFilamentInstanceWrapper.cpp b/package/cpp/core/RNFFilamentInstanceWrapper.cpp index 0147e8c6..0c8b5354 100644 --- a/package/cpp/core/RNFFilamentInstanceWrapper.cpp +++ b/package/cpp/core/RNFFilamentInstanceWrapper.cpp @@ -10,12 +10,18 @@ namespace margelo { void FilamentInstanceWrapper::loadHybridMethods() { + registerHybridGetter("entityCount", &FilamentInstanceWrapper::getEntityCount, this); registerHybridMethod("getEntities", &FilamentInstanceWrapper::getEntities, this); registerHybridMethod("getRoot", &FilamentInstanceWrapper::getRoot, this); registerHybridMethod("createAnimator", &FilamentInstanceWrapper::createAnimator, this); registerHybridMethod("getBoundingBox", &FilamentInstanceWrapper::getBoundingBox, this); } +int FilamentInstanceWrapper::getEntityCount() { + size_t count = _instance->getEntityCount(); + return static_cast(count); +} + std::vector> FilamentInstanceWrapper::getEntities() { std::vector> entities; const Entity* entityArray = _instance->getEntities(); diff --git a/package/cpp/core/RNFFilamentInstanceWrapper.h b/package/cpp/core/RNFFilamentInstanceWrapper.h index 92626178..51a0b13c 100644 --- a/package/cpp/core/RNFFilamentInstanceWrapper.h +++ b/package/cpp/core/RNFFilamentInstanceWrapper.h @@ -30,6 +30,7 @@ class FilamentInstanceWrapper : public HybridObject { } private: // Public JS API + int getEntityCount(); std::vector> getEntities(); std::shared_ptr getRoot(); /** diff --git a/package/cpp/core/RNFNameComponentManagerWrapper.h b/package/cpp/core/RNFNameComponentManagerWrapper.h index 12720cba..657cbe98 100644 --- a/package/cpp/core/RNFNameComponentManagerWrapper.h +++ b/package/cpp/core/RNFNameComponentManagerWrapper.h @@ -16,7 +16,7 @@ class NameComponentManagerWrapper : public PointerHolder { explicit NameComponentManagerWrapper(std::shared_ptr nameComponentManager) : PointerHolder(TAG, nameComponentManager) {} - void loadHybridMethods() override{}; + void loadHybridMethods() override {}; std::shared_ptr getManager() { return pointee(); diff --git a/package/cpp/core/RNFSceneWrapper.cpp b/package/cpp/core/RNFSceneWrapper.cpp index c1df0f42..86b249e4 100644 --- a/package/cpp/core/RNFSceneWrapper.cpp +++ b/package/cpp/core/RNFSceneWrapper.cpp @@ -6,7 +6,9 @@ namespace margelo { void margelo::SceneWrapper::loadHybridMethods() { registerHybridMethod("addEntity", &SceneWrapper::addEntity, this); + registerHybridMethod("addEntities", &SceneWrapper::addEntities, this); registerHybridMethod("removeEntity", &SceneWrapper::removeEntity, this); + registerHybridMethod("removeEntities", &SceneWrapper::removeEntities, this); registerHybridMethod("addAssetEntities", &SceneWrapper::addAssetEntities, this); registerHybridMethod("removeAssetEntities", &SceneWrapper::removeAssetEntities, this); registerHybridGetter("entityCount", &SceneWrapper::getEntityCount, this); @@ -22,6 +24,15 @@ void margelo::SceneWrapper::addEntity(std::shared_ptr entity) { pointee()->addEntity(entity->getEntity()); } +void SceneWrapper::addEntities(std::vector> entities) { + std::unique_lock lock(_mutex); + + size_t count = entities.size(); + std::vector entityArray = entityWrapperVectorToEntityVector(entities, count); + const Entity* entityArrayPtr = entityArray.data(); + pointee()->addEntities(entityArrayPtr, count); +} + void SceneWrapper::removeEntity(std::shared_ptr entity) { std::unique_lock lock(_mutex); @@ -32,6 +43,15 @@ void SceneWrapper::removeEntity(std::shared_ptr entity) { pointee()->remove(entity->getEntity()); } +void SceneWrapper::removeEntities(std::vector> entities) { + std::unique_lock lock(_mutex); + + size_t count = entities.size(); + std::vector entityArray = entityWrapperVectorToEntityVector(entities, count); + const Entity* entityArrayPtr = entityArray.data(); + pointee()->removeEntities(entityArrayPtr, count); +} + void SceneWrapper::addAsset(std::shared_ptr asset) { std::unique_lock lock(_mutex); @@ -87,4 +107,19 @@ int SceneWrapper::getEntityCount() { return pointee()->getEntityCount(); } +std::vector SceneWrapper::entityWrapperVectorToEntityVector(std::vector> entities, size_t count) { + if (entities.empty()) { + throw std::invalid_argument("Entities are empty"); + } + + std::vector entityArray; + entityArray.reserve(count); + for (const auto& entityWrapper : entities) { + Entity entity = entityWrapper->getEntity(); + entityArray.push_back(entity); + } + + return entityArray; +} + } // namespace margelo diff --git a/package/cpp/core/RNFSceneWrapper.h b/package/cpp/core/RNFSceneWrapper.h index 8c441f09..036c21b5 100644 --- a/package/cpp/core/RNFSceneWrapper.h +++ b/package/cpp/core/RNFSceneWrapper.h @@ -26,7 +26,9 @@ class SceneWrapper : public PointerHolder { private: // Public JS API void addEntity(std::shared_ptr entity); + void addEntities(std::vector> entities); void removeEntity(std::shared_ptr entity); + void removeEntities(std::vector> entities); /** * Removed all entities associated with the provided asset from the scene. */ @@ -37,5 +39,8 @@ class SceneWrapper : public PointerHolder { void addAssetEntities(std::shared_ptr asset); int getEntityCount(); + +private: // Internal + std::vector entityWrapperVectorToEntityVector(std::vector> entities, size_t count); }; } // namespace margelo diff --git a/package/cpp/core/RNFTransformManagerImpl.cpp b/package/cpp/core/RNFTransformManagerImpl.cpp index 3483f895..dc36c4db 100644 --- a/package/cpp/core/RNFTransformManagerImpl.cpp +++ b/package/cpp/core/RNFTransformManagerImpl.cpp @@ -3,6 +3,7 @@ // #include "RNFTransformManagerImpl.h" +#include "RNFAABBWrapper.h" #include "core/utils/RNFConverter.h" #include #include @@ -133,19 +134,17 @@ void TransformManagerImpl::updateTransformByRigidBody(Entity entity, std::shared } /** - * Sets up a root transform on the current model to make it fit into a unit cube. + * Sets up a root transform on the root to make it fit into a cube of the size of 1 unit. */ -void TransformManagerImpl::transformToUnitCube(std::shared_ptr asset) { +void TransformManagerImpl::transformToUnitCube(Entity rootEntity, Aabb aabb) { std::unique_lock lock(_mutex); TransformManager& transformManager = _engine->getTransformManager(); - Aabb aabb = asset->getBoundingBox(); math::details::TVec3 center = aabb.center(); math::details::TVec3 halfExtent = aabb.extent(); float maxExtent = max(halfExtent) * 2.0f; float scaleFactor = 2.0f / maxExtent; math::mat4f transform = math::mat4f::scaling(scaleFactor) * math::mat4f::translation(-center); - Entity rootEntity = asset->getRoot(); TransformManager::Instance instance = getInstance(rootEntity, transformManager); transformManager.setTransform(instance, transform); } diff --git a/package/cpp/core/RNFTransformManagerImpl.h b/package/cpp/core/RNFTransformManagerImpl.h index ac2a30ce..3a7e4c7d 100644 --- a/package/cpp/core/RNFTransformManagerImpl.h +++ b/package/cpp/core/RNFTransformManagerImpl.h @@ -28,7 +28,7 @@ class TransformManagerImpl { void setEntityRotation(Entity entity, double angleRadians, std::vector axisVec, bool multiplyCurrent); void setEntityScale(Entity entity, std::vector scaleVec, bool multiplyCurrent); void updateTransformByRigidBody(Entity entity, std::shared_ptr rigidBody); - void transformToUnitCube(std::shared_ptr asset); + void transformToUnitCube(Entity rootEntity, Aabb aabb); private: // Internal void updateTransform(math::mat4 transform, Entity entity, bool multiplyCurrent); diff --git a/package/cpp/core/RNFTransformManagerWrapper.cpp b/package/cpp/core/RNFTransformManagerWrapper.cpp index 94a8e444..803a76a1 100644 --- a/package/cpp/core/RNFTransformManagerWrapper.cpp +++ b/package/cpp/core/RNFTransformManagerWrapper.cpp @@ -60,9 +60,17 @@ void TransformManagerWrapper::updateTransformByRigidBody(std::shared_ptrupdateTransformByRigidBody(entity, rigidBody); } -void TransformManagerWrapper::transformToUnitCube(std::shared_ptr assetWrapper) { - std::shared_ptr asset = assetWrapper->getAsset(); - pointee()->transformToUnitCube(asset); +void TransformManagerWrapper::transformToUnitCube(std::shared_ptr rootEntityWrapper, + std::shared_ptr aabbWrapper) { + if (!aabbWrapper) { + [[unlikely]]; + throw std::invalid_argument("AABB is null"); + } + + Aabb aabb = aabbWrapper->getAabb(); + Entity rootEntity = getEntity(rootEntityWrapper); + + pointee()->transformToUnitCube(rootEntity, aabb); } Entity TransformManagerWrapper::getEntity(std::shared_ptr entityWrapper) { diff --git a/package/cpp/core/RNFTransformManagerWrapper.h b/package/cpp/core/RNFTransformManagerWrapper.h index d415eb59..b9e8923f 100644 --- a/package/cpp/core/RNFTransformManagerWrapper.h +++ b/package/cpp/core/RNFTransformManagerWrapper.h @@ -27,7 +27,7 @@ class TransformManagerWrapper : public PointerHolder { void setEntityRotation(std::shared_ptr entity, double angleRadians, std::vector axisVec, bool multiplyCurrent); void setEntityScale(std::shared_ptr entity, std::vector scaleVec, bool multiplyCurrent); void updateTransformByRigidBody(std::shared_ptr entityWrapper, std::shared_ptr rigidBody); - void transformToUnitCube(std::shared_ptr assetWrapper); + void transformToUnitCube(std::shared_ptr rootEntityWrapper, std::shared_ptr aabbWrapper); private: // Internal Entity getEntity(std::shared_ptr entityWrapper); diff --git a/package/example/Shared/index.js b/package/example/Shared/index.js index f12f52dd..c91a5d7b 100644 --- a/package/example/Shared/index.js +++ b/package/example/Shared/index.js @@ -10,10 +10,12 @@ console.log(`Using react-native-worklets-core@${version}`) import { setLogger } from 'react-native-filament' // A function that can wrap a console log call to add a prefix const prefix = '[filament-logger]' -const prefixLog = (logFn) => (...messages) => { - const date = new Date() - logFn(prefix, `[${date.toLocaleTimeString()} ${date.getMilliseconds().toString().padStart(3, 0)}]`, ...messages) -} +const prefixLog = + (logFn) => + (...messages) => { + const date = new Date() + logFn(prefix, `[${date.toLocaleTimeString()} ${date.getMilliseconds().toString().padStart(3, 0)}]`, ...messages) + } setLogger({ debug: prefixLog(console.debug), log: prefixLog(console.log), diff --git a/package/example/Shared/src/AnimationTransitions.tsx b/package/example/Shared/src/AnimationTransitions.tsx index 8d70cab2..4de02772 100644 --- a/package/example/Shared/src/AnimationTransitions.tsx +++ b/package/example/Shared/src/AnimationTransitions.tsx @@ -1,7 +1,7 @@ import { useNavigation } from '@react-navigation/native' import * as React from 'react' import { Alert, Button, ScrollView, StyleSheet, View } from 'react-native' -import { FilamentContext, FilamentView, Camera, Model, Animator, AnimationItem, Entity, DefaultLight, Group } from 'react-native-filament' +import { FilamentContext, FilamentView, Camera, Model, Animator, AnimationItem, Entity, DefaultLight } from 'react-native-filament' import { useSharedValue } from 'react-native-worklets-core' import HipHopGirlGlb from '~/assets/hiphopgirl.glb' import { SafeAreaView } from 'react-native-safe-area-context' diff --git a/package/example/Shared/src/App.tsx b/package/example/Shared/src/App.tsx index 70db4c8d..eeafd30e 100644 --- a/package/example/Shared/src/App.tsx +++ b/package/example/Shared/src/App.tsx @@ -10,11 +10,11 @@ import { AnimationTransitionsRecording } from './AnimationTransitionsRecording' import { ImageExample } from './ImageExample' import { LoadFromFile } from './LoadFromFile' import { NoneTransparent } from './NoneTransparent' +import { MultipleInstances } from './MultipleInstances' // import { ChangeMaterials } from './ChangeMaterials' // import { PhysicsCoin } from './PhysicsCoin' // import { FadeOut } from './FadeOut' // import { CastShadow } from './CastShadow' -// import { MultipleInstances } from './MultipleInstances' // import { WorkletExample } from './WorkletExample' // import { ScaleEffect } from './ScaleEffect' // import { FadingLightExample } from './FadingLightExample' @@ -55,9 +55,9 @@ function HomeScreen() { + {/* - */} @@ -92,13 +92,13 @@ function App() { + {/* TODO: Migrate */} {/* - */} diff --git a/package/example/Shared/src/MultipleInstances.tsx b/package/example/Shared/src/MultipleInstances.tsx index 20940ad0..44563983 100644 --- a/package/example/Shared/src/MultipleInstances.tsx +++ b/package/example/Shared/src/MultipleInstances.tsx @@ -1,122 +1,46 @@ -import React, { useEffect, useMemo, useRef } from 'react' -import { Platform, StyleSheet, View } from 'react-native' -import { - FilamentView, - Float3, - useBuffer, - useCamera, - useEngine, - useModel, - useRenderCallback, - useScene, - useView, -} from 'react-native-filament' +import React from 'react' +import { StyleSheet } from 'react-native' +import { Animator, Camera, DefaultLight, FilamentContext, FilamentView, Model, ModelInstance } from 'react-native-filament' +import DroneGlb from '~/assets/buster_drone.glb' -const penguModelPath = Platform.select({ - android: 'custom/pengu.glb', - ios: 'pengu.glb', -})! +const instances = [0, 1, 2, 3] +const instanceCount = instances.length -const indirectLightPath = Platform.select({ - android: 'custom/default_env_ibl.ktx', - ios: 'default_env_ibl.ktx', -})! - -// Camera config: -const cameraPosition: Float3 = [0, 1, 10] -const cameraTarget: Float3 = [0, 0, 0] -const cameraUp: Float3 = [0, 1, 0] -const focalLengthInMillimeters = 28 -const near = 0.1 -const far = 1000 +function Renderer() { + return ( + + + + + + {instances.map((index) => { + // Calculate positions for a 2x2 grid + // Assuming each instance occupies a 1x1 area, adjust the multiplier for larger sizes + const spacingMultiplier = 4 + const x = (index % 2) * spacingMultiplier - spacingMultiplier / 2 + const y = Math.floor(index / 2) * spacingMultiplier - spacingMultiplier / 2 + const z = 0 // Keep z the same if you're not using it for depth positioning + + return ( + + + + ) + })} + + + ) +} export function MultipleInstances() { - const engine = useEngine() - const view = useView(engine) - const camera = useCamera(engine) - const scene = useScene(engine) - - //#region Setup lights - const light = useBuffer({ source: indirectLightPath }) - useEffect(() => { - if (light == null) return - // create a default light - engine.setIndirectLight(light) - - // Create a directional light for supporting shadows - const directionalLight = engine.createLightEntity('directional', 6500, 10000, 0, -1, 0, true) - engine.getScene().addEntity(directionalLight) - return () => { - // TODO: Remove directionalLight from scene - } - }, [engine, light]) - //#endregion - - const pengu = useModel({ engine: engine, source: penguModelPath, instanceCount: 4 }) - const penguAsset = pengu.state === 'loaded' ? pengu.asset : undefined - useEffect(() => { - if (penguAsset == null) return - - // This will apply to all instances - engine.transformToUnitCube(penguAsset) - - // Add all instances to the scene - const instances = penguAsset.getAssetInstances() - for (let i = 0; i < instances.length; i++) { - const instance = instances[i] - const root = instance?.getRoot() - - // Calculate positions for a 2x2 grid - // Assuming each instance occupies a 1x1 area, adjust the multiplier for larger sizes - const spacingMultiplier = 4 - const x = (i % 2) * spacingMultiplier - spacingMultiplier / 2 - const y = Math.floor(i / 2) * spacingMultiplier - spacingMultiplier / 2 - const z = 0 // Keep z the same if you're not using it for depth positioning - - // Move the instance - engine.setEntityPosition(root!, [x, y, z], true) - - scene.addEntity(root!) - } - }, [engine, penguAsset, scene]) - - const allAnimators = useMemo(() => { - if (penguAsset == null) return undefined - - const instances = penguAsset.getAssetInstances() - return instances.map((instance) => instance.getAnimator()) - }, [penguAsset]) - - const prevAspectRatio = useRef(0) - - useRenderCallback(engine, (_timestamp, _startTime, passedSeconds) => { - const aspectRatio = view.aspectRatio - if (prevAspectRatio.current !== aspectRatio) { - prevAspectRatio.current = aspectRatio - // Setup camera lens: - camera.setLensProjection(focalLengthInMillimeters, aspectRatio, near, far) - } - - // Animate all instances - allAnimators?.forEach((animator, index) => { - animator?.applyAnimation(index, passedSeconds) - animator?.updateBoneMatrices() - }) - - camera.lookAt(cameraPosition, cameraTarget, cameraUp) - }) - return ( - - - + + + ) } const styles = StyleSheet.create({ - container: { - flex: 1, - }, filamentView: { flex: 1, backgroundColor: 'lightblue', diff --git a/package/ios/src/RNFAppleFilamentProxy.mm b/package/ios/src/RNFAppleFilamentProxy.mm index 3827fb9c..7923259e 100644 --- a/package/ios/src/RNFAppleFilamentProxy.mm +++ b/package/ios/src/RNFAppleFilamentProxy.mm @@ -49,11 +49,11 @@ auto managedBuffer = std::make_shared(data); return std::make_shared(managedBuffer); } - + // Check if we want to load from file path: if ([filePath hasPrefix:@"file://"]) { filePath = [filePath substringFromIndex:7]; - + // Load the data from the file NSError* errorPtr; NSData* bufferData = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedIfSafe error:&errorPtr]; diff --git a/package/src/hooks/internal/useApplyTransformations.tsx b/package/src/hooks/internal/useApplyTransformations.tsx index 6e2bcfbd..1c8cd9af 100644 --- a/package/src/hooks/internal/useApplyTransformations.tsx +++ b/package/src/hooks/internal/useApplyTransformations.tsx @@ -1,7 +1,7 @@ import { useContext, useEffect, useRef } from 'react' import { TransformationProps, TransformContext } from '../../react/TransformContext' import { useFilamentContext } from '../../react/Context' -import { Entity, Float3 } from '../../types' +import { AABB, Entity, Float3 } from '../../types' import { areFloat3Equal } from '../../utilities/helper' import { useMergeTransformationProps } from './useMergeTransformationProps' @@ -9,19 +9,22 @@ type Params = { // If null it will not take the entity from the context, as it indicates that it will be provided through the param to: Entity | null transformProps?: TransformationProps + // If transformToUnitCube is true, the aabb is required + aabb?: AABB } /** * Takes the next entity from the context and applies all transformations from the next * transformation context to it */ -export function useApplyTransformations({ to: entity, transformProps }: Params): void { +export function useApplyTransformations({ to: entity, transformProps, aabb }: Params): void { const transformPropsFromContext = useContext(TransformContext) const { position, scale, rotate, + transformToUnitCube, multiplyWithCurrentTransform = true, } = useMergeTransformationProps(transformProps, transformPropsFromContext) @@ -33,10 +36,13 @@ export function useApplyTransformations({ to: entity, transformProps }: Params): const prevRotate = useRef() const prevPosition = useRef(null) - console.log({ prevScale }) useEffect(() => { if (entity == null) return + if (transformToUnitCube && aabb != null) { + transformManager.transformToUnitCube(entity, aabb) + } + if (scale != null && (prevScale.current == null || !areFloat3Equal(scale, prevScale.current))) { transformManager.setEntityScale(entity, scale, multiplyWithCurrentTransform) prevScale.current = scale @@ -54,5 +60,17 @@ export function useApplyTransformations({ to: entity, transformProps }: Params): transformManager.setEntityPosition(entity, position, multiplyWithCurrentTransform) prevPosition.current = position } - }, [entity, multiplyWithCurrentTransform, position, prevPosition, prevRotate, prevScale, rotate, scale, transformManager]) + }, [ + aabb, + entity, + multiplyWithCurrentTransform, + position, + prevPosition, + prevRotate, + prevScale, + rotate, + scale, + transformManager, + transformToUnitCube, + ]) } diff --git a/package/src/hooks/useModel.ts b/package/src/hooks/useModel.ts index ec18bbc4..c12bd405 100644 --- a/package/src/hooks/useModel.ts +++ b/package/src/hooks/useModel.ts @@ -108,7 +108,7 @@ export function useModel(source: BufferSource, props?: ModelProps): FilamentMode const boundingBox = useMemo(() => { if (asset == null) return undefined - return asset.boundingBox + return asset.getBoundingBox() }, [asset]) if (assetBuffer == null || asset == null || boundingBox == null) { diff --git a/package/src/index.tsx b/package/src/index.tsx index 4e002df0..76d5c957 100644 --- a/package/src/index.tsx +++ b/package/src/index.tsx @@ -29,6 +29,8 @@ export * from './bullet' export { useFilamentContext } from './react/Context' export { FilamentViewWithRenderCallbacks as FilamentView } from './react/FilamentViewWithRenderCallbacks' export * from './react/Model' +export * from './react/ModelInstance' +export * from './react/Group' export * from './react/Animator' export * from './react/Camera' export * from './react/RenderCallbackContext' @@ -38,4 +40,3 @@ export * from './react/DefaultLight' export * from './react/Skybox' export * from './react/FilamentContext' export * from './react/BackgroundImage' -export * from './react/Group' diff --git a/package/src/react/Animator.tsx b/package/src/react/Animator.tsx index 5f5e733b..f2c21a52 100644 --- a/package/src/react/Animator.tsx +++ b/package/src/react/Animator.tsx @@ -1,10 +1,10 @@ import React, { useEffect } from 'react' -import { ParentModelAssetContext } from './ParentModelAssetContext' -import { FilamentAsset } from '../types' +import { FilamentInstance } from '../types' import { RenderCallbackContext } from './RenderCallbackContext' import { useAnimator } from '../hooks/useAnimator' import { ISharedValue, useSharedValue } from 'react-native-worklets-core' import usePrevious from '../hooks/usePrevious' +import { ParentInstancesContext } from './ParentInstancesContext' export type AnimationItem = { index: number @@ -43,21 +43,25 @@ type Props = { * */ export function Animator(props: Props) { - const parentAsset = React.useContext(ParentModelAssetContext) - - if (parentAsset == null) { - throw new Error('Animator must be used inside a component.') + const instances = React.useContext(ParentInstancesContext) + if (instances == null) { + throw new Error('Animator must be used inside a or component.') + } + const instance = instances[0] + if (instance == null) { + // Should never happen + throw new Error('No instances found for the parent Model component. This is a bug.') } - return + return } type ImplProps = Props & { - asset: FilamentAsset + instance: FilamentInstance } -function AnimatorImpl({ asset, animationIndex: animationIndexProp = 0, transitionDuration = 0, onAnimationsLoaded }: ImplProps) { - const animator = useAnimator(asset) +function AnimatorImpl({ instance, animationIndex: animationIndexProp = 0, transitionDuration = 0, onAnimationsLoaded }: ImplProps) { + const animator = useAnimator(instance) // State for cross fading animations const prevAnimationIndex = useSharedValue(undefined) diff --git a/package/src/react/Model.tsx b/package/src/react/Model.tsx index 6fc2412d..6036e256 100644 --- a/package/src/react/Model.tsx +++ b/package/src/react/Model.tsx @@ -2,7 +2,6 @@ import React, { PropsWithChildren, useCallback, useContext, useEffect, useMemo } import { BufferSource } from '../hooks/useBuffer' import { ModelProps as UseModelProps, useModel } from '../hooks/useModel' import { ParentModelAssetContext } from './ParentModelAssetContext' -import { ParentEntityContext } from './ParentEntityContex' import { getAssetFromModel } from '../utilities/getAssetFromModel' import { useFilamentContext } from './Context' import { GestureResponderEvent } from 'react-native' @@ -10,7 +9,8 @@ import { Logger } from '../utilities/logger/Logger' import { Entity } from '../types' import { TouchHandlerContext } from './TouchHandlerContext' import { useApplyTransformations } from '../hooks/internal/useApplyTransformations' -import { extractTransformationProps, TransformationProps, TransformContext } from './TransformContext' +import { extractTransformationProps, TransformationProps } from './TransformContext' +import { ParentInstancesContext } from './ParentInstancesContext' type ModelProps = TransformationProps & UseModelProps & { @@ -34,14 +34,15 @@ export function Model({ children, source, onPress, ...restProps }: PropsWithChil const model = useModel(source, modelProps) const asset = getAssetFromModel(model) + const rootEntity = useMemo(() => { if (asset === undefined) { return null } return asset.getRoot() }, [asset]) - - useApplyTransformations({ transformProps: transformProps, to: rootEntity }) + const boundingBox = model.state === 'loaded' ? model.boundingBox : undefined + useApplyTransformations({ transformProps: transformProps, to: rootEntity, aabb: boundingBox }) const renderableEntities = useMemo(() => { // The entities are only needed for touch events, so only load them if a touch handler is provided @@ -89,12 +90,21 @@ export function Model({ children, source, onPress, ...restProps }: PropsWithChil } }, [addTouchHandler, onPress, onTouchStart]) + const instances = useMemo(() => { + if (asset === undefined) { + return null + } + return asset.getAssetInstances() + }, [asset]) + if (asset == null || rootEntity == null) { return null } return ( - {children} + {/* TODO: do we need this? I think we should always work from the instances */} + {/* {children} */} + {children} ) } diff --git a/package/src/react/ModelInstance.tsx b/package/src/react/ModelInstance.tsx new file mode 100644 index 00000000..2148c80a --- /dev/null +++ b/package/src/react/ModelInstance.tsx @@ -0,0 +1,57 @@ +import React from 'react' +import { PropsWithChildren, useMemo } from 'react' +import { FilamentInstance } from '../types' +import { ParentInstancesContext, useParentInstancesContext } from './ParentInstancesContext' +import { TransformationProps } from './TransformContext' +import { useApplyTransformations } from '../hooks/internal/useApplyTransformations' + +type ModelInstanceProps = PropsWithChildren< + TransformationProps & { + index: number + } +> + +/** + * You can render multiple instances of the same model. If you use a model multiple times this is recommended, + * as it will only be loaded once into memory. + * This component helps with applying configs to a specific instance of a model. + * + * @example + * ```tsx + * + * + * + * + * ``` + * + */ +export function ModelInstance({ index, ...restProps }: ModelInstanceProps) { + const instances = useParentInstancesContext() + const instance = instances[index] + if (instance == null) { + throw new Error(`The parent Model component has ${instances.length} instances. You tried to use index ${index}`) + } + + return +} + +type Props = PropsWithChildren< + TransformationProps & { + instance: FilamentInstance + } +> + +function ModelInstanceImpl({ instance, children, ...transformProps }: Props) { + const rootEntity = useMemo(() => { + return instance.getRoot() + }, [instance]) + const boundingBox = useMemo(() => { + return instance.getBoundingBox() + }, [instance]) + + const instances = useMemo(() => [instance], [instance]) + + useApplyTransformations({ transformProps, to: rootEntity, aabb: boundingBox }) + + return {children} +} diff --git a/package/src/react/ParentInstancesContext.ts b/package/src/react/ParentInstancesContext.ts new file mode 100644 index 00000000..13e769e5 --- /dev/null +++ b/package/src/react/ParentInstancesContext.ts @@ -0,0 +1,12 @@ +import React from 'react' +import { FilamentInstance } from '../types' + +export const ParentInstancesContext = React.createContext(null) + +export function useParentInstancesContext() { + const parentInstances = React.useContext(ParentInstancesContext) + if (parentInstances == null) { + throw new Error('useParentInstancesContext must be used inside a Model component') + } + return parentInstances +} diff --git a/package/src/react/TransformContext.tsx b/package/src/react/TransformContext.tsx index 3f88151d..2b18e104 100644 --- a/package/src/react/TransformContext.tsx +++ b/package/src/react/TransformContext.tsx @@ -10,10 +10,8 @@ export type TransformationProps = { } multiplyWithCurrentTransform?: boolean - // TODO: this only works on assets :thinking: /** * Transforms the given entity to fit into a unit cube (1,1,1) at the origin (0,0,0). - * @param entity The entity to transform */ transformToUnitCube?: boolean } diff --git a/package/src/types/FilamentAsset.ts b/package/src/types/FilamentAsset.ts index 04b471d5..bb8395e2 100644 --- a/package/src/types/FilamentAsset.ts +++ b/package/src/types/FilamentAsset.ts @@ -1,37 +1,9 @@ -import { AABB } from './Boxes' -import type { Animator } from './Animator' -import type { Entity } from './Entity' +import { Entity } from './Entity' import { FilamentInstance } from './FilamentInstance' +import { FilamentInstanceBase } from './FilamentInstanceBase' import { PointerHolder } from './PointerHolder' -import { NameComponentManager } from './NameComponentManager' - -export interface FilamentAsset extends PointerHolder { - /** - * Gets the transform root for the asset, which has no matching glTF node. - * - * This node exists for convenience, allowing users to transform the entire asset. For instanced - * assets, this is a "super root" where each of its children is a root in a particular instance. - * This allows users to transform all instances en masse if they wish to do so. - */ - getRoot(): Entity - - createAnimator(nameComponentManager: NameComponentManager): Animator - /** - * Reclaims CPU-side memory for URI strings, binding lists, and raw animation data. - */ - releaseSourceData(): void - - /** - * Gets the list of entities, one for each glTF node. All of these have a Transform component. - * Some of the returned entities may also have a Renderable component and/or a Light component. - */ - getEntities: () => Entity[] - - /** - * Gets the number of entities returned by entities. - */ - readonly entityCount: number +export interface FilamentAsset extends PointerHolder, FilamentInstanceBase { /** * Gets the list of entities in the asset that have renderable components. */ @@ -42,15 +14,12 @@ export interface FilamentAsset extends PointerHolder { */ readonly renderableEntityCount: number + getFirstEntityByName(name: string): Entity | undefined + /** - * Gets the bounding box computed from the supplied min / max values in glTF accessors. - * - * This does not return a bounding box over all FilamentInstance, it's just a straightforward - * AAAB that can be determined at load time from the asset data. + * Reclaims CPU-side memory for URI strings, binding lists, and raw animation data. */ - readonly boundingBox: AABB - - getFirstEntityByName(name: string): Entity | undefined + releaseSourceData(): void /** * Convenience method to get the first instance. diff --git a/package/src/types/FilamentInstance.ts b/package/src/types/FilamentInstance.ts index 82e32d22..36499767 100644 --- a/package/src/types/FilamentInstance.ts +++ b/package/src/types/FilamentInstance.ts @@ -1,7 +1,4 @@ -import { AABB } from './Boxes' -import { Animator } from './Animator' -import { Entity } from './Entity' -import { NameComponentManager } from './NameComponentManager' +import { FilamentInstanceBase } from './FilamentInstanceBase' /** * Every asset loaded has at least one FilamentInstance. You can load multiple instances of the same asset. @@ -12,9 +9,4 @@ import { NameComponentManager } from './NameComponentManager' * Renderable components. * */ -export interface FilamentInstance { - getEntities(): Entity[] - getRoot(): Entity - createAnimator(nameComponentManager: NameComponentManager): Animator - getBoundingBox(): AABB -} +export interface FilamentInstance extends FilamentInstanceBase {} diff --git a/package/src/types/FilamentInstanceBase.ts b/package/src/types/FilamentInstanceBase.ts new file mode 100644 index 00000000..ff70980d --- /dev/null +++ b/package/src/types/FilamentInstanceBase.ts @@ -0,0 +1,36 @@ +import { AABB } from './Boxes' +import { Animator } from './Animator' +import { Entity } from './Entity' +import { NameComponentManager } from './NameComponentManager' + +export interface FilamentInstanceBase { + /** + * Gets the transform root for the asset, which has no matching glTF node. + * + * This node exists for convenience, allowing users to transform the entire asset. For instanced + * assets, this is a "super root" where each of its children is a root in a particular instance. + * This allows users to transform all instances en masse if they wish to do so. + */ + getRoot(): Entity + + createAnimator(nameComponentManager: NameComponentManager): Animator + + /** + * Gets the list of entities, one for each glTF node. All of these have a Transform component. + * Some of the returned entities may also have a Renderable component and/or a Light component. + */ + getEntities: () => Entity[] + + /** + * Gets the bounding box computed from the supplied min / max values in glTF accessors. + * + * This does not return a bounding box over all FilamentInstance, it's just a straightforward + * AAAB that can be determined at load time from the asset data. + */ + getBoundingBox(): AABB + + /** + * Gets the number of entities returned by entities. + */ + readonly entityCount: number +} diff --git a/package/src/types/Scene.ts b/package/src/types/Scene.ts index a9d645ba..eaa6e078 100644 --- a/package/src/types/Scene.ts +++ b/package/src/types/Scene.ts @@ -33,11 +33,21 @@ export interface Scene extends PointerHolder { */ addEntity(entity: Entity): void + /** + * Adds multiple entities to the scene. + */ + addEntities(entities: Entity[]): void + /** * Removes the Renderable from the Scene. **/ removeEntity(entity: Entity): void + /** + * Removes multiple entities from the scene. + */ + removeEntities(entities: Entity[]): void + /** * Adds all entities associated with the given asset to the scene. */ diff --git a/package/src/types/TransformManager.ts b/package/src/types/TransformManager.ts index 9b652304..6608b647 100644 --- a/package/src/types/TransformManager.ts +++ b/package/src/types/TransformManager.ts @@ -1,8 +1,8 @@ import { RigidBody } from '../bullet' import { Entity } from './Entity' -import { FilamentAsset } from './FilamentAsset' import { PointerHolder } from './PointerHolder' import { Float3 } from './Math' +import { AABB } from './Boxes' /** * A 4x4 column-major matrix. @@ -102,7 +102,7 @@ export interface TransformManager extends PointerHolder { * Transforms the given entity to fit into a unit cube at the origin (0,0,0). * @param entity The entity to transform */ - transformToUnitCube(entity: FilamentAsset): void + transformToUnitCube(rootEntity: Entity, boundingBox: AABB): void /** * Sets the position of an entity. diff --git a/package/src/types/index.ts b/package/src/types/index.ts index 9efafa0a..530e2f9a 100644 --- a/package/src/types/index.ts +++ b/package/src/types/index.ts @@ -8,13 +8,14 @@ export * from './View' export * from './SwapChain' export * from './Math' export { Animator as FilamentAnimator } from './Animator' +export * from './FilamentInstanceBase' export * from './FilamentAsset' +export * from './FilamentInstance' export * from './RenderableManager' export * from './Material' export * from './MaterialInstance' export * from './TransformManager' export * from './Boxes' -export * from './FilamentInstance' export * from './LightConfig' export * from './Options' export * from './LightManager'