diff --git a/package/android/CMakeLists.txt b/package/android/CMakeLists.txt index 96cf7bf7..b9c85840 100644 --- a/package/android/CMakeLists.txt +++ b/package/android/CMakeLists.txt @@ -36,6 +36,7 @@ add_library( ../cpp/core/CameraWrapper.cpp ../cpp/core/ViewWrapper.cpp ../cpp/core/SwapChainWrapper.cpp + ../cpp/core/FilamentAssetWrapper.cpp # Filament Utils ../cpp/core/utils/EntityWrapper.cpp diff --git a/package/cpp/core/CameraFovEnum.h b/package/cpp/core/CameraFovEnum.h new file mode 100644 index 00000000..3883d72d --- /dev/null +++ b/package/cpp/core/CameraFovEnum.h @@ -0,0 +1,35 @@ +// +// Created by Marc Rousavy on 22.02.24. +// + +#pragma once + +#include "jsi/EnumMapper.h" +#include + +namespace margelo { +using namespace filament; + +namespace EnumMapper { + static void convertJSUnionToEnum(const std::string& inUnion, Camera::Fov* outEnum) { + if (inUnion == "horizontal") + *outEnum = Camera::Fov::HORIZONTAL; + else if (inUnion == "vertical") + *outEnum = Camera::Fov::VERTICAL; + else + throw invalidUnion(inUnion); + } + static void convertEnumToJSUnion(Camera::Fov inEnum, std::string* outUnion) { + switch (inEnum) { + case filament::Camera::Fov::HORIZONTAL: + *outUnion = "horizontal"; + break; + case filament::Camera::Fov::VERTICAL: + *outUnion = "vertical"; + break; + default: + throw invalidEnum(inEnum); + } + } +} // namespace EnumMapper +} // namespace margelo \ No newline at end of file diff --git a/package/cpp/core/CameraWrapper.cpp b/package/cpp/core/CameraWrapper.cpp index e10d417b..7c47d654 100644 --- a/package/cpp/core/CameraWrapper.cpp +++ b/package/cpp/core/CameraWrapper.cpp @@ -1,10 +1,14 @@ #include "CameraWrapper.h" +#include "CameraFovEnum.h" void margelo::CameraWrapper::loadHybridMethods() { + registerHybridMethod("lookAtCameraManipulator", &CameraWrapper::lookAtCameraManipulator, this); registerHybridMethod("lookAt", &CameraWrapper::lookAt, this); + registerHybridMethod("setLensProjection", &CameraWrapper::setLensProjection, this); + registerHybridMethod("setProjection", &CameraWrapper::setProjection, this); } -void margelo::CameraWrapper::lookAt(std::shared_ptr cameraManipulator) { +void margelo::CameraWrapper::lookAtCameraManipulator(std::shared_ptr cameraManipulator) { if (!cameraManipulator) { throw std::invalid_argument("CameraManipulator is null"); } @@ -13,3 +17,23 @@ void margelo::CameraWrapper::lookAt(std::shared_ptr cameraMa cameraManipulator->getManipulator()->getLookAt(&eye, ¢er, &up); _camera->lookAt(eye, center, up); } + +void margelo::CameraWrapper::lookAt(std::vector eye, std::vector center, std::vector up) { + math::float3 eyeVec = {static_cast(eye[0]), static_cast(eye[1]), static_cast(eye[2])}; + math::float3 centerVec = {static_cast(center[0]), static_cast(center[1]), static_cast(center[2])}; + math::float3 upVec = {static_cast(up[0]), static_cast(up[1]), static_cast(up[2])}; + _camera->lookAt(eyeVec, centerVec, upVec); +} + +void margelo::CameraWrapper::setLensProjection(double fov, double aspect, double near, double far) { + _camera->setLensProjection(static_cast(fov), static_cast(aspect), static_cast(near), static_cast(far)); +} + +void margelo::CameraWrapper::setProjection(double fovInDegrees, double aspect, double near, double far, + std::string directionStr = "vertical") { + Camera::Fov direction; + EnumMapper::convertJSUnionToEnum(directionStr, &direction); + + _camera->setProjection(static_cast(fovInDegrees), static_cast(aspect), static_cast(near), static_cast(far), + direction); +} diff --git a/package/cpp/core/CameraWrapper.h b/package/cpp/core/CameraWrapper.h index b2c9bc88..574604f0 100644 --- a/package/cpp/core/CameraWrapper.h +++ b/package/cpp/core/CameraWrapper.h @@ -22,7 +22,11 @@ class CameraWrapper : public HybridObject { std::shared_ptr _camera; private: + void lookAt(std::vector eye, std::vector center, std::vector up); + void setLensProjection(double fov, double aspect, double near, double far); + // TODO(Hanno): Add directionStr , Camera::Fov directionStr = Camera::Fov::VERTICAL + void setProjection(double fovInDegrees, double aspect, double near, double far, std::string directionStr); // Convenience methods - void lookAt(std::shared_ptr cameraManipulator); + void lookAtCameraManipulator(std::shared_ptr cameraManipulator); }; } // namespace margelo \ No newline at end of file diff --git a/package/cpp/core/EngineWrapper.cpp b/package/cpp/core/EngineWrapper.cpp index 0e2cd859..f85f3a15 100644 --- a/package/cpp/core/EngineWrapper.cpp +++ b/package/cpp/core/EngineWrapper.cpp @@ -4,7 +4,10 @@ #include "EngineWrapper.h" +#include "LightEnum.h" #include "References.h" +#include "utils/Converter.h" + #include #include #include @@ -82,7 +85,7 @@ EngineWrapper::EngineWrapper(std::shared_ptr choreographer) { void EngineWrapper::loadHybridMethods() { registerHybridMethod("setSurfaceProvider", &EngineWrapper::setSurfaceProvider, this); registerHybridMethod("setRenderCallback", &EngineWrapper::setRenderCallback, this); - registerHybridMethod("createDefaultLight", &EngineWrapper::createDefaultLight, this); + registerHybridMethod("setIndirectLight", &EngineWrapper::setIndirectLight, this); registerHybridMethod("loadAsset", &EngineWrapper::loadAsset, this); @@ -92,6 +95,11 @@ void EngineWrapper::loadHybridMethods() { registerHybridMethod("getView", &EngineWrapper::getView, this); registerHybridMethod("getCamera", &EngineWrapper::getCamera, this); registerHybridMethod("getCameraManipulator", &EngineWrapper::getCameraManipulator, this); + registerHybridMethod("createLightEntity", &EngineWrapper::createLightEntity, this); + registerHybridMethod("transformToUnitCube", &EngineWrapper::transformToUnitCube, this); + registerHybridMethod("setEntityPosition", &EngineWrapper::setEntityPosition, this); + registerHybridMethod("setEntityRotation", &EngineWrapper::setEntityRotation, this); + registerHybridMethod("setEntityScale", &EngineWrapper::setEntityScale, this); } void EngineWrapper::setSurfaceProvider(std::shared_ptr surfaceProvider) { @@ -136,7 +144,8 @@ void EngineWrapper::surfaceSizeChanged(int width, int height) { _view->setViewport(0, 0, width, height); } - updateCameraProjection(); + // TODO: when the surface resizes we need to update the camera projection, but that one is owned by JS now. + // updateCameraProjection(); } void EngineWrapper::destroySurface() { @@ -227,8 +236,8 @@ std::shared_ptr EngineWrapper::createCamera() { return std::make_shared(camera); } -void EngineWrapper::loadAsset(std::shared_ptr modelBuffer) { - filament::gltfio::FilamentAsset* asset = _assetLoader->createAsset(modelBuffer->getData(), modelBuffer->getSize()); +std::shared_ptr EngineWrapper::loadAsset(std::shared_ptr modelBuffer) { + gltfio::FilamentAsset* asset = _assetLoader->createAsset(modelBuffer->getData(), modelBuffer->getSize()); if (asset == nullptr) { throw std::runtime_error("Failed to load asset"); } @@ -246,11 +255,14 @@ void EngineWrapper::loadAsset(std::shared_ptr modelBuffer) { _animator = asset->getInstance()->getAnimator(); asset->releaseSourceData(); - transformToUnitCube(asset); + auto sharedPtr = std::shared_ptr(asset, [](gltfio::FilamentAsset* asset) { + // TODO: destroy the asset + }); + return std::make_shared(sharedPtr); } // Default light is a directional light for shadows + a default IBL -void EngineWrapper::createDefaultLight(std::shared_ptr iblBuffer) { +void EngineWrapper::setIndirectLight(std::shared_ptr iblBuffer) { if (!_scene) { throw std::runtime_error("Scene not initialized"); } @@ -278,17 +290,23 @@ void EngineWrapper::createDefaultLight(std::shared_ptr iblBuffer IndirectLight::Builder().reflections(cubemap).irradiance(3, harmonics).intensity(30000.0f).build(*_engine); _scene->getScene()->setIndirectLight(_indirectLight); +} - // Add directional light for supporting shadows +std::shared_ptr EngineWrapper::createLightEntity(std::string lightTypeStr, double colorFahrenheit, double intensity, + double directionX, double directionY, double directionZ, bool castShadows) { auto lightEntity = _engine->getEntityManager().create(); - LightManager::Builder(LightManager::Type::DIRECTIONAL) - .color(Color::cct(6500.0f)) - .intensity(10000) - .direction({0, -1, 0}) - .castShadows(true) - .build(*_engine, lightEntity); - _scene->getScene()->addEntity(lightEntity); + // TODO(Marc): Fix enum converter + LightManager::Type lightType; + EnumMapper::convertJSUnionToEnum(lightTypeStr, &lightType); + + LightManager::Builder(lightType) + .color(Color::cct(static_cast(colorFahrenheit))) + .intensity(static_cast(intensity)) + .direction({directionX, directionY, directionZ}) + .castShadows(castShadows) + .build(*_engine, lightEntity); + return std::make_shared(lightEntity); } std::shared_ptr EngineWrapper::createCameraManipulator(int width, int height) { @@ -305,31 +323,9 @@ std::shared_ptr EngineWrapper::createCameraManipulator(int w /** * Sets up a root transform on the current model to make it fit into a unit cube. */ -void EngineWrapper::transformToUnitCube(filament::gltfio::FilamentAsset* asset) { +void EngineWrapper::transformToUnitCube(std::shared_ptr asset) { TransformManager& tm = _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); - EntityInstance transformInstance = tm.getInstance(asset->getRoot()); - tm.setTransform(transformInstance, transform); -} - -void EngineWrapper::updateCameraProjection() { - if (!_view) { - throw std::runtime_error("View not initialized"); - } - if (!_camera) { - throw std::runtime_error("Camera not initialized"); - } - - const double aspect = (double)_view->getView()->getViewport().width / _view->getView()->getViewport().height; - double focalLength = 28.0; - double near = 0.05; // 5cm - double far = 1000.0; // 1km - _camera->getCamera()->setLensProjection(focalLength, aspect, near, far); + asset->transformToUnitCube(tm); } void EngineWrapper::synchronizePendingFrames() { @@ -344,4 +340,42 @@ void EngineWrapper::synchronizePendingFrames() { _engine->destroy(fence); } +/** + * Internal method that will help updating the transform of an entity. + * @param transform The transform matrix to apply + * @param entity The entity to apply the transform to + * @param multiplyCurrent If true, the current transform will be multiplied with the new transform, otherwise it will be replaced + */ +void EngineWrapper::updateTransform(math::mat4 transform, std::shared_ptr entity, bool multiplyCurrent) { + if (!entity) { + throw std::invalid_argument("Entity is null"); + } + + TransformManager& tm = _engine->getTransformManager(); + EntityInstance entityInstance = tm.getInstance(entity->getEntity()); + auto currentTransform = tm.getTransform(entityInstance); + auto newTransform = multiplyCurrent ? (currentTransform * transform) : transform; + tm.setTransform(entityInstance, newTransform); +} + +// TODO(Marc): Ideally i want to do this in the entity wrapper, but i dont have access to the transform manager there +void EngineWrapper::setEntityPosition(std::shared_ptr entity, std::vector positionVec, bool multiplyCurrent) { + math::float3 position = Converter::VecToFloat3(positionVec); + auto translationMatrix = math::mat4::translation(position); + updateTransform(translationMatrix, entity, multiplyCurrent); +} + +void EngineWrapper::setEntityRotation(std::shared_ptr entity, double angleRadians, std::vector axisVec, + bool multiplyCurrent) { + math::float3 axis = Converter::VecToFloat3(axisVec); + auto rotationMatrix = math::mat4::rotation(angleRadians, axis); + updateTransform(rotationMatrix, entity, multiplyCurrent); +} + +void EngineWrapper::setEntityScale(std::shared_ptr entity, std::vector scaleVec, bool multiplyCurrent) { + math::float3 scale = Converter::VecToFloat3(scaleVec); + auto scaleMatrix = math::mat4::scaling(scale); + updateTransform(scaleMatrix, entity, multiplyCurrent); +} + } // namespace margelo diff --git a/package/cpp/core/EngineWrapper.h b/package/cpp/core/EngineWrapper.h index 6d7421d4..a62ef9e3 100644 --- a/package/cpp/core/EngineWrapper.h +++ b/package/cpp/core/EngineWrapper.h @@ -4,10 +4,24 @@ #pragma once +#include "jsi/HybridObject.h" + +#include "CameraWrapper.h" +#include "Choreographer.h" +#include "FilamentAssetWrapper.h" +#include "FilamentBuffer.h" +#include "RendererWrapper.h" +#include "SceneWrapper.h" #include "Surface.h" #include "SurfaceProvider.h" -#include +#include "SwapChainWrapper.h" +#include "ViewWrapper.h" +#include "core/utils/EntityWrapper.h" +#include "core/utils/ManipulatorWrapper.h" + +#include #include +#include #include #include #include @@ -48,12 +62,20 @@ class EngineWrapper : public HybridObject { void setRenderCallback(std::function)> callback); void renderFrame(double timestamp); - void transformToUnitCube(gltfio::FilamentAsset* asset); - void loadAsset(std::shared_ptr modelBuffer); - void createDefaultLight(std::shared_ptr modelBuffer); - void updateCameraProjection(); + void transformToUnitCube(std::shared_ptr asset); + std::shared_ptr loadAsset(std::shared_ptr modelBuffer); + void setIndirectLight(std::shared_ptr modelBuffer); + void synchronizePendingFrames(); + std::shared_ptr createLightEntity(std::string lightTypeStr, double colorFahrenheit, double intensity, double directionX, + double directionY, double directionZ, bool castShadows); + + void updateTransform(math::mat4 transform, std::shared_ptr entity, bool multiplyCurrent); + void setEntityPosition(std::shared_ptr entity, std::vector positionVec, bool multiplyCurrent); + void setEntityRotation(std::shared_ptr entity, double angleRadians, std::vector axisVec, bool multiplyCurrent); + void setEntityScale(std::shared_ptr entity, std::vector scaleVec, bool multiplyCurrent); + private: std::shared_ptr _engine; std::shared_ptr _surfaceProvider; @@ -71,7 +93,7 @@ class EngineWrapper : public HybridObject { std::shared_ptr _resourceLoader; const math::float3 defaultObjectPosition = {0.0f, 0.0f, 0.0f}; - const math::float3 defaultCameraPosition = {0.0f, 0.0f, 5.0f}; + const math::float3 defaultCameraPosition = {0.0f, 0.0f, 0.0f}; private: // Internals we create, but share the access with the user diff --git a/package/cpp/core/FilamentAssetWrapper.cpp b/package/cpp/core/FilamentAssetWrapper.cpp new file mode 100644 index 00000000..e49b125c --- /dev/null +++ b/package/cpp/core/FilamentAssetWrapper.cpp @@ -0,0 +1,33 @@ +#include "FilamentAssetWrapper.h" + +#include +#include + +namespace margelo { + +using namespace utils; + +void FilamentAssetWrapper::loadHybridMethods() { + registerHybridMethod("getRoot", &FilamentAssetWrapper::getRoot, this); +} + +/** + * Sets up a root transform on the current model to make it fit into a unit cube. + */ +void FilamentAssetWrapper::transformToUnitCube(TransformManager& transformManager) { + 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); + EntityInstance transformInstance = transformManager.getInstance(_asset->getRoot()); + transformManager.setTransform(transformInstance, transform); +} + +std::shared_ptr FilamentAssetWrapper::getRoot() { + Entity rootEntity = _asset->getRoot(); + return std::make_shared(rootEntity); +} + +} // namespace margelo \ No newline at end of file diff --git a/package/cpp/core/FilamentAssetWrapper.h b/package/cpp/core/FilamentAssetWrapper.h new file mode 100644 index 00000000..dd63f9f8 --- /dev/null +++ b/package/cpp/core/FilamentAssetWrapper.h @@ -0,0 +1,27 @@ +#pragma once + +#include "core/utils/EntityWrapper.h" +#include "jsi/HybridObject.h" +#include +#include + +namespace margelo { + +using namespace filament; + +class FilamentAssetWrapper : public HybridObject { +public: + explicit FilamentAssetWrapper(std::shared_ptr asset) : _asset(asset) {} + + void loadHybridMethods() override; + + void transformToUnitCube(TransformManager& transformManager); + +private: + std::shared_ptr getRoot(); + +private: + std::shared_ptr _asset; +}; + +} // namespace margelo diff --git a/package/cpp/core/LightEnum.h b/package/cpp/core/LightEnum.h new file mode 100644 index 00000000..c0d0e80e --- /dev/null +++ b/package/cpp/core/LightEnum.h @@ -0,0 +1,51 @@ +// +// Created by Hanno Gödecke on 29.02.24. +// + +#pragma once + +#include "jsi/EnumMapper.h" +#include + +namespace margelo { + +namespace EnumMapper { + using namespace filament; + + static void convertJSUnionToEnum(const std::string& inUnion, LightManager::Type* outEnum) { + if (inUnion == "directional") + *outEnum = LightManager::Type::DIRECTIONAL; + else if (inUnion == "spot") + *outEnum = LightManager::Type::SPOT; + else if (inUnion == "point") + *outEnum = LightManager::Type::POINT; + else if (inUnion == "focused_sport") + *outEnum = LightManager::Type::FOCUSED_SPOT; + else if (inUnion == "sun") + *outEnum = LightManager::Type::SUN; + else + throw invalidUnion(inUnion); + } + static void convertEnumToJSUnion(LightManager::Type inEnum, std::string* outUnion) { + switch (inEnum) { + case LightManager::Type::DIRECTIONAL: + *outUnion = "directional"; + break; + case LightManager::Type::SPOT: + *outUnion = "spot"; + break; + case LightManager::Type::POINT: + *outUnion = "point"; + break; + case LightManager::Type::FOCUSED_SPOT: + *outUnion = "focused_sport"; + break; + case LightManager::Type::SUN: + *outUnion = "sun"; + break; + default: + throw invalidEnum(inEnum); + } + } +} // namespace EnumMapper +} // namespace margelo diff --git a/package/cpp/core/ViewWrapper.cpp b/package/cpp/core/ViewWrapper.cpp index 4551443c..36793d0d 100644 --- a/package/cpp/core/ViewWrapper.cpp +++ b/package/cpp/core/ViewWrapper.cpp @@ -8,6 +8,7 @@ void ViewWrapper::loadHybridMethods() { registerHybridSetter("camera", &ViewWrapper::setCamera, this); registerHybridGetter("camera", &ViewWrapper::getCamera, this); registerHybridMethod("setViewport", &ViewWrapper::setViewport, this); + registerHybridGetter("aspectRatio", &ViewWrapper::getAspectRatio, this); } void ViewWrapper::setScene(std::shared_ptr scene) { @@ -41,11 +42,11 @@ void ViewWrapper::setViewport(int x, int y, int width, int height) { throw std::invalid_argument("Invalid viewport size"); } - if (!_view) { - throw std::invalid_argument("View is null"); - } - _view->setViewport({x, y, static_cast(width), static_cast(height)}); } +double ViewWrapper::getAspectRatio() { + return (double)_view->getViewport().width / _view->getViewport().height; +} + } // namespace margelo diff --git a/package/cpp/core/ViewWrapper.h b/package/cpp/core/ViewWrapper.h index 01d60cf0..816e4d66 100644 --- a/package/cpp/core/ViewWrapper.h +++ b/package/cpp/core/ViewWrapper.h @@ -27,6 +27,7 @@ class ViewWrapper : public HybridObject { std::shared_ptr getScene(); void setCamera(std::shared_ptr camera); std::shared_ptr getCamera(); + double getAspectRatio(); private: std::shared_ptr _view; diff --git a/package/cpp/core/utils/Converter.h b/package/cpp/core/utils/Converter.h new file mode 100644 index 00000000..de9541f8 --- /dev/null +++ b/package/cpp/core/utils/Converter.h @@ -0,0 +1,21 @@ +// +// Created by Hanno Gödecke on 29.02.24. +// + +#pragma once + +#include + +namespace margelo { + +class Converter { +public: + static math::float3 VecToFloat3(std::vector vec) { + if (vec.size() != 3) { + throw std::invalid_argument("Point must have 3 elements"); + } + + return math::float3(vec[0], vec[1], vec[2]); + } +}; +} // namespace margelo diff --git a/package/cpp/jsi/EnumMapper.h b/package/cpp/jsi/EnumMapper.h index 7f2a7513..67153bf3 100644 --- a/package/cpp/jsi/EnumMapper.h +++ b/package/cpp/jsi/EnumMapper.h @@ -17,17 +17,32 @@ namespace EnumMapper { static std::runtime_error invalidUnion(const std::string& passedUnion) { return std::runtime_error("Cannot convert JS Value to Enum: Invalid Union value passed! (\"" + std::string(passedUnion) + "\")"); } + template static std::runtime_error invalidEnum(T passedEnum) { return std::runtime_error("Cannot convert Enum to JS Value: Invalid Enum passed! (Value #" + std::to_string(static_cast(passedEnum)) + ")"); } - static void convertJSUnionToEnum(const std::string& inUnion, int*) { - throw invalidUnion(inUnion); + // Trait to check if a convertJSUnionToEnum function for enum type T exists + template struct has_js_union_to_enum : std::false_type {}; + template + struct has_js_union_to_enum(), std::declval()))>> + : std::true_type {}; + + // Trait to check if a convertEnumToJSUnion function for enum type T exists + template struct has_enum_to_js_union : std::false_type {}; + template + struct has_enum_to_js_union(), std::declval()))>> + : std::true_type {}; + + template static void convertJSUnionToEnum(const std::string&, TEnum*) { + static_assert(has_js_union_to_enum::value, + "Cannot convert a JS union to this enum type. Did you implement EnumMapper::convertJSUnionToEnum(...)?"); } - static void convertEnumToJSUnion(int inEnum, std::string*) { - throw invalidEnum(inEnum); + template static void convertEnumToJSUnion(TEnum, std::string*) { + static_assert(has_enum_to_js_union::value, + "Cannot convert this enum type to a JS union. Did you implement EnumMapper::convertEnumToJSUnion(...)?"); } } // namespace EnumMapper diff --git a/package/cpp/jsi/JSIConverter.h b/package/cpp/jsi/JSIConverter.h index fceba0b8..fa64dd80 100644 --- a/package/cpp/jsi/JSIConverter.h +++ b/package/cpp/jsi/JSIConverter.h @@ -117,7 +117,7 @@ template struct JSIConverter & Readonly +const penguModelPath = Platform.select({ + android: 'custom/pengu.glb', + ios: 'pengu.glb', +})! + +const indirectLightPath = Platform.select({ + android: 'custom/default_env_ibl.ktx', + ios: 'default_env_ibl.ktx', +})! + export class FilamentView extends React.PureComponent { private readonly ref: React.RefObject - private readonly choreographer = FilamentProxy.createChoreographer() - private choreographerListener: Listener | null = null + private readonly engine = FilamentProxy.createEngine() constructor(props: FilamentViewProps) { super(props) this.ref = React.createRef() + + this.setup3dScene() } // TODO: Does this also work for Fabric? @@ -30,48 +42,59 @@ export class FilamentView extends React.PureComponent { componentDidMount() { // TODO: lets get rid of this timeout setTimeout(() => { - this.setup3dScene() + this.setupSurface() }, 100) } - componentWillUnmount(): void { - this.choreographer.stop() - if (this.choreographerListener != null) { - this.choreographerListener.remove() - } + setup3dScene = () => { + // Load a model into the scene: + const modelBuffer = FilamentProxy.getAssetByteBuffer(penguModelPath) + const penguAsset = this.engine.loadAsset(modelBuffer) + // By default all assets get added to the origin at 0,0,0, + // we transform it to fit into a unit cube at the origin using this utility: + this.engine.transformToUnitCube(penguAsset) + + // We can also change the pengus position, rotation and scale: + const penguEntity = penguAsset.getRoot() + this.engine.setEntityPosition(penguEntity, [0, 2, 0], true) // Move the pengu up by 2 units + + // Create a default light: + const indirectLightBuffer = FilamentProxy.getAssetByteBuffer(indirectLightPath) + this.engine.setIndirectLight(indirectLightBuffer) + + // Create a directional light for supporting shadows + const light = this.engine.createLightEntity('directional', 6500, 10000, 0, -1, 0, true) + this.engine.getScene().addEntity(light) } - setup3dScene = () => { + renderCallback = () => { + const cameraPosition: Float3 = [0, 0, 5] + const cameraTarget: Float3 = [0, 0, 0] + const cameraUp: Float3 = [0, 1, 0] + + this.engine.getCamera().lookAt(cameraPosition, cameraTarget, cameraUp) + } + + setupSurface = () => { // Get Surface: const fView = FilamentProxy.findFilamentView(this.handle) const surfaceProvider = fView.getSurfaceProvider() - // Create engine: - const engine = FilamentProxy.createEngine() - - // Load a model into the scene: - const modelPath = Platform.select({ - android: 'custom/pengu.glb', - ios: 'pengu.glb', - }) - const modelBuffer = FilamentProxy.getAssetByteBuffer(modelPath!) - engine.loadAsset(modelBuffer) - - // Create a default light: - const indirectLightPath = Platform.select({ - android: 'custom/default_env_ibl.ktx', - ios: 'default_env_ibl.ktx', - }) - const indirectLightBuffer = FilamentProxy.getAssetByteBuffer(indirectLightPath!) - engine.createDefaultLight(indirectLightBuffer) - // Link the surface with the engine: - engine.setSurfaceProvider(surfaceProvider) + this.engine.setSurfaceProvider(surfaceProvider) + + // Configure camera lens (Important, do so after linking the surface) + const view = this.engine.getView() + const aspectRatio = view.aspectRatio + const focalLengthInMillimeters = 28 + const near = 0.1 + const far = 1000 + this.engine.getCamera().setLensProjection(focalLengthInMillimeters, aspectRatio, near, far) + console.log('aspectRatio', aspectRatio) + // Alternatively setProjection can be used // Callback for rendering every frame - engine.setRenderCallback(() => { - engine.getCamera().lookAt(engine.getCameraManipulator()) - }) + this.engine.setRenderCallback(this.renderCallback) } /** @internal */ diff --git a/package/src/types/Camera.ts b/package/src/types/Camera.ts index 1a6f4903..88666040 100644 --- a/package/src/types/Camera.ts +++ b/package/src/types/Camera.ts @@ -1,4 +1,5 @@ import { Manipulator } from './Manipulator' +import { Float3 } from './float3' /** * Camera represents the eye through which the scene is viewed. @@ -79,6 +80,36 @@ import { Manipulator } from './Manipulator' */ export interface Camera { - // Convenience method. The original method works slightly different, this is a simplification, so we don't have to deal with out params. - lookAt(cameraManipulator: Manipulator): void + // Convenience method + lookAtCameraManipulator(cameraManipulator: Manipulator): void + /** + * + * @param eye The position of the camera in space + * @param center The target position to look at + * @param up The up vector of the camera (Usually (0, 1, 0)) + */ + lookAt(eye: Float3, center: Float3, up: Float3): void + + /** Utility to set the projection matrix from the focal length. + * + * @param focalLengthInMillimeters lens's focal length in millimeters.focalLength > 0. + * @param aspect aspect ratio (You can use view.aspectRatio) + * @param near distance in world units from the camera to the near plane. near > 0. + * @param far distance in world units from the camera to the far plane. far > near. + */ + setLensProjection(focalLengthInMillimeters: number, aspect: number, near: number, far: number): void + + //TODO(Hanno): This also accepts a last parameter called direction. Implement once custom enums are fixed + /** + * Utility to set the projection matrix from the field-of-view. + * + * @param fovInDegrees full field-of-view in degrees. 0 < \p fov < 180. + * @param aspect aspect ratio \f$ \frac{width}{height} \f$. \p aspect > 0. + * @param near distance in world units from the camera to the near plane. \p near > 0. + * @param far distance in world units from the camera to the far plane. \p far > \p near. + * @param direction direction of the \p fovInDegrees parameter. + * + * @see Fov. + */ + setProjection(fov: number, aspect: number, near: number, far: number): void } diff --git a/package/src/types/Engine.ts b/package/src/types/Engine.ts index b97c62b3..599a3b3f 100644 --- a/package/src/types/Engine.ts +++ b/package/src/types/Engine.ts @@ -5,17 +5,49 @@ import { Renderer } from './Renderer' import { Scene } from './Scene' import { View } from './View' import { FilamentBuffer } from '../native/FilamentBuffer' +import { Entity } from './Entity' +import { FilamentAsset } from './FilamentAsset' +import { Float3 } from './float3' export interface Engine { setSurfaceProvider(surfaceProvider: SurfaceProvider): void setRenderCallback(callback: (engine: Engine) => void): void - loadAsset(buffer: FilamentBuffer): void - createDefaultLight(iblBuffer: FilamentBuffer): void + /** + * Given a @see FilamentBuffer (e.g. from a .glb file), load the asset into the engine. + * This will by default add all entities from the asset to the attached default scene. + */ + loadAsset(buffer: FilamentBuffer): FilamentAsset + + /** + * Set the indirect light for the scene. + * @param iblBuffer A buffer containing the IBL data (e.g. from a .ktx file) + */ + setIndirectLight(iblBuffer: FilamentBuffer): void getRenderer(): Renderer getScene(): Scene getCamera(): Camera getView(): View getCameraManipulator(): Manipulator + + createLightEntity( + type: 'directional' | 'spot' | 'point' | 'focused_point' | 'sun', + colorFahrenheit: number, + intensity: number, + directionX: number, + directionY: number, + directionZ: number, + castShadows: boolean, + ): Entity + + /** + * 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 + + setEntityPosition(entity: Entity, position: Float3, multiplyCurrent: boolean): void + setEntityRotation(entity: Entity, angleRadians: number, axis: Float3, multiplyCurrent: boolean): void + setEntityScale(entity: Entity, scale: Float3, multiplyCurrent: boolean): void } diff --git a/package/src/types/FilamentAsset.ts b/package/src/types/FilamentAsset.ts new file mode 100644 index 00000000..f3d11e03 --- /dev/null +++ b/package/src/types/FilamentAsset.ts @@ -0,0 +1,5 @@ +import type { Entity } from './Entity' + +export interface FilamentAsset { + getRoot(): Entity +} diff --git a/package/src/types/View.ts b/package/src/types/View.ts index ec7ce907..25eece32 100644 --- a/package/src/types/View.ts +++ b/package/src/types/View.ts @@ -23,5 +23,6 @@ import { Scene } from './Scene' export interface View { camera: Camera scene: Scene + aspectRatio: number setViewport(x: number, y: number, width: number, height: number): void } diff --git a/package/src/types/float3.ts b/package/src/types/float3.ts new file mode 100644 index 00000000..fb7f75d5 --- /dev/null +++ b/package/src/types/float3.ts @@ -0,0 +1,4 @@ +/** + * A 3D float vector (x, y, z). + */ +export type Float3 = [number, number, number]