diff --git a/ios/include/FilamentViewer.hpp b/ios/include/FilamentViewer.hpp index c5cff857..d5839605 100644 --- a/ios/include/FilamentViewer.hpp +++ b/ios/include/FilamentViewer.hpp @@ -177,7 +177,7 @@ namespace polyvox { void loadPngTexture(string path, ResourceBuffer data); void loadTextureFromPath(string path); - + Manipulator* _manipulator = nullptr; void _createManipulator(); uint32_t _lastFrameTimeInNanos; }; diff --git a/ios/polyvox_filament.podspec b/ios/polyvox_filament.podspec index e055a6b8..47700130 100644 --- a/ios/polyvox_filament.podspec +++ b/ios/polyvox_filament.podspec @@ -13,7 +13,7 @@ A new flutter plugin project. s.license = { :file => '../LICENSE' } s.author = { 'Your Company' => 'email@example.com' } s.source = { :path => '.' } - s.source_files = 'Classes/*', 'src/*', 'src/ios/*', 'include/filament/*', 'include/*', 'include/material/*.c' + s.source_files = 'Classes/*', 'src/*', "src/camutils/*", 'src/ios/*', 'include/filament/*', 'include/*', 'include/material/*.c' s.public_header_files = 'include/SwiftPolyvoxFilamentPlugin-Bridging-Header.h', 'include/PolyvoxFilamentApi.h', 'include/PolyvoxFilamentFFIApi.h', 'include/ResourceBuffer.hpp', 'include/Log.hpp' s.dependency 'Flutter' s.platform = :ios, '12.1' @@ -26,7 +26,7 @@ A new flutter plugin project. 'OTHER_CFLAGS' => '"-fvisibility=default" "$(inherited)"', 'USER_HEADER_SEARCH_PATHS' => '"${PODS_ROOT}/../.symlinks/plugins/polyvox_filament/ios/include" "${PODS_ROOT}/../.symlinks/plugins/polyvox_filament/ios/include/filament" "${PODS_ROOT}/../.symlinks/plugins/polyvox_filament/ios/src" "${PODS_ROOT}/../.symlinks/plugins/polyvox_filament/ios/src/image" "${PODS_ROOT}/../.symlinks/plugins/polyvox_filament/ios/src/shaders" "$(inherited)"', 'ALWAYS_SEARCH_USER_PATHS' => 'YES', - "OTHER_LDFLAGS" => '-lfilament -lbackend -lfilameshio -lviewer -lfilamat -lgeometry -lutils -lfilabridge -lgltfio_core -lfilament-iblprefilter -limage -limageio -ltinyexr -lcamutils -lgltfio_core -lfilaflat -ldracodec -libl -lktxreader -lpng -lpng16 -lz -lstb -luberzlib -lsmol-v -luberarchive -lzstd -lstdc++', + "OTHER_LDFLAGS" => '-lfilament -lbackend -lfilameshio -lviewer -lfilamat -lgeometry -lutils -lfilabridge -lgltfio_core -lfilament-iblprefilter -limage -limageio -ltinyexr -lgltfio_core -lfilaflat -ldracodec -libl -lktxreader -lpng -lpng16 -lz -lstb -luberzlib -lsmol-v -luberarchive -lzstd -lstdc++', 'LIBRARY_SEARCH_PATHS' => '"${PODS_ROOT}/../.symlinks/plugins/polyvox_filament/ios/lib" "$(inherited)"', } @@ -38,7 +38,7 @@ A new flutter plugin project. 'OTHER_CFLAGS' => '"-fvisibility=default" "$(inherited)"', 'USER_HEADER_SEARCH_PATHS' => '"${PODS_ROOT}/../.symlinks/plugins/polyvox_filament/ios/include" "${PODS_ROOT}/../.symlinks/plugins/polyvox_filament/ios/include/filament" "${PODS_ROOT}/../.symlinks/plugins/polyvox_filament/ios/src" "${PODS_ROOT}/../.symlinks/plugins/polyvox_filament/ios/src/image" "${PODS_ROOT}/../.symlinks/plugins/polyvox_filament/ios/src/shaders" "$(inherited)"', 'ALWAYS_SEARCH_USER_PATHS' => 'YES', - "OTHER_LDFLAGS" => '-lfilament -lbackend -lfilameshio -lviewer -lfilamat -lgeometry -lutils -lfilabridge -lgltfio_core -lfilament-iblprefilter -limage -limageio -ltinyexr -lcamutils -lgltfio_core -lfilaflat -ldracodec -libl -lktxreader -lpng -lpng16 -lz -lstb -luberzlib -lsmol-v -luberarchive -lzstd -lstdc++', + "OTHER_LDFLAGS" => '-lfilament -lbackend -lfilameshio -lviewer -lfilamat -lgeometry -lutils -lfilabridge -lgltfio_core -lfilament-iblprefilter -limage -limageio -ltinyexr -lgltfio_core -lfilaflat -ldracodec -libl -lktxreader -lpng -lpng16 -lz -lstb -luberzlib -lsmol-v -luberarchive -lzstd -lstdc++', 'LIBRARY_SEARCH_PATHS' => '"${PODS_ROOT}/../.symlinks/plugins/polyvox_filament/ios/lib" "$(inherited)"', } diff --git a/ios/src/FilamentViewer.cpp b/ios/src/FilamentViewer.cpp index 30cc7f00..b1c64a01 100644 --- a/ios/src/FilamentViewer.cpp +++ b/ios/src/FilamentViewer.cpp @@ -1,5 +1,5 @@ #if __APPLE__ - #include "TargetConditionals.h" +#include "TargetConditionals.h" #endif #ifdef _WIN32 @@ -22,6 +22,7 @@ * limitations under the License. */ #include + #include #include #include @@ -86,185 +87,195 @@ using namespace gltfio; using namespace utils; using namespace image; -namespace filament { -class IndirectLight; -class LightManager; +namespace filament +{ + class IndirectLight; + class LightManager; } // namespace filament -namespace polyvox { - -const double kNearPlane = 0.05; // 5 cm -const double kFarPlane = 1000.0; // 1 km +namespace polyvox +{ -// const float kAperture = 1.0f; -// const float kShutterSpeed = 1.0f; -// const float kSensitivity = 50.0f; -struct Vertex { + const double kNearPlane = 0.05; // 5 cm + const double kFarPlane = 1000.0; // 1 km + + // const float kAperture = 1.0f; + // const float kShutterSpeed = 1.0f; + // const float kSensitivity = 50.0f; + struct Vertex + { filament::math::float2 position; uint32_t color; -}; + }; -static constexpr float4 sFullScreenTriangleVertices[3] = { - { -1.0f, -1.0f, 1.0f, 1.0f }, - { 3.0f, -1.0f, 1.0f, 1.0f }, - { -1.0f, 3.0f, 1.0f, 1.0f } }; + static constexpr float4 sFullScreenTriangleVertices[3] = { + {-1.0f, -1.0f, 1.0f, 1.0f}, + {3.0f, -1.0f, 1.0f, 1.0f}, + {-1.0f, 3.0f, 1.0f, 1.0f}}; -static const uint16_t sFullScreenTriangleIndices[3] = {0, 1, 2}; + static const uint16_t sFullScreenTriangleIndices[3] = {0, 1, 2}; -FilamentViewer::FilamentViewer(const void* sharedContext, const ResourceLoaderWrapper* const resourceLoaderWrapper, void* const platform, const char* uberArchivePath) - : _resourceLoaderWrapper(resourceLoaderWrapper) { + FilamentViewer::FilamentViewer(const void *sharedContext, const ResourceLoaderWrapper *const resourceLoaderWrapper, void *const platform, const char *uberArchivePath) + : _resourceLoaderWrapper(resourceLoaderWrapper) + { - ASSERT_POSTCONDITION(_resourceLoaderWrapper != nullptr, "Resource loader must be non-null"); - - #if TARGET_OS_IPHONE + ASSERT_POSTCONDITION(_resourceLoaderWrapper != nullptr, "Resource loader must be non-null"); + +#if TARGET_OS_IPHONE ASSERT_POSTCONDITION(platform == nullptr, "Custom Platform not supported on iOS"); _engine = Engine::create(Engine::Backend::METAL); - #elif TARGET_OS_OSX - ASSERT_POSTCONDITION(platform == nullptr, "Custom Platform not supported on macOS"); - _engine = Engine::create(Engine::Backend::METAL); - #else - _engine = Engine::create(Engine::Backend::OPENGL, (backend::Platform*)platform, (void*)sharedContext, nullptr); - #endif +#elif TARGET_OS_OSX + ASSERT_POSTCONDITION(platform == nullptr, "Custom Platform not supported on macOS"); + _engine = Engine::create(Engine::Backend::METAL); +#else + _engine = Engine::create(Engine::Backend::OPENGL, (backend::Platform *)platform, (void *)sharedContext, nullptr); +#endif - _renderer = _engine->createRenderer(); + _renderer = _engine->createRenderer(); - float fr = 60.0f; - _renderer->setDisplayInfo({.refreshRate = fr}); + float fr = 60.0f; + _renderer->setDisplayInfo({.refreshRate = fr}); - Renderer::FrameRateOptions fro; - fro.interval = 1 / fr; - _renderer->setFrameRateOptions(fro); + Renderer::FrameRateOptions fro; + fro.interval = 1 / fr; + _renderer->setFrameRateOptions(fro); - _scene = _engine->createScene(); + _scene = _engine->createScene(); - Log("Scene created"); - - utils::Entity camera = EntityManager::get().create(); + Log("Scene created"); - _mainCamera = _engine->createCamera(camera); + utils::Entity camera = EntityManager::get().create(); - Log("Main camera created"); - _view = _engine->createView(); - Log("View created"); + _mainCamera = _engine->createCamera(camera); - setToneMapping(ToneMapping::ACES); + Log("Main camera created"); + _view = _engine->createView(); + Log("View created"); - Log("Set tone mapping"); + setToneMapping(ToneMapping::ACES); - setBloom(0.6f); - Log("Set bloom"); + Log("Set tone mapping"); - _view->setScene(_scene); - _view->setCamera(_mainCamera); + setBloom(0.6f); + Log("Set bloom"); - _cameraFocalLength = 28.0f; - _mainCamera->setLensProjection(_cameraFocalLength, 1.0f, kNearPlane, - kFarPlane); - // _mainCamera->setExposure(kAperture, kShutterSpeed, kSensitivity); - Log("View created"); - const float aperture = _mainCamera->getAperture(); - const float shutterSpeed = _mainCamera->getShutterSpeed(); - const float sens = _mainCamera->getSensitivity(); + _view->setScene(_scene); + _view->setCamera(_mainCamera); - Log("Camera aperture %f shutter %f sensitivity %f", aperture, shutterSpeed, sens); + _cameraFocalLength = 28.0f; + _mainCamera->setLensProjection(_cameraFocalLength, 1.0f, kNearPlane, + kFarPlane); + // _mainCamera->setExposure(kAperture, kShutterSpeed, kSensitivity); + Log("View created"); + const float aperture = _mainCamera->getAperture(); + const float shutterSpeed = _mainCamera->getShutterSpeed(); + const float sens = _mainCamera->getSensitivity(); - View::DynamicResolutionOptions options; - options.enabled = false; - // options.homogeneousScaling = homogeneousScaling; - // options.minScale = filament::math::float2{ minScale }; - // options.maxScale = filament::math::float2{ maxScale }; - // options.sharpness = sharpness; - // options.quality = View::QualityLevel::ULTRA; - _view->setDynamicResolutionOptions(options); + Log("Camera aperture %f shutter %f sensitivity %f", aperture, shutterSpeed, sens); - View::MultiSampleAntiAliasingOptions multiSampleAntiAliasingOptions; - multiSampleAntiAliasingOptions.enabled = true; + View::DynamicResolutionOptions options; + options.enabled = false; + // options.homogeneousScaling = homogeneousScaling; + // options.minScale = filament::math::float2{ minScale }; + // options.maxScale = filament::math::float2{ maxScale }; + // options.sharpness = sharpness; + // options.quality = View::QualityLevel::ULTRA; + _view->setDynamicResolutionOptions(options); - _view->setMultiSampleAntiAliasingOptions(multiSampleAntiAliasingOptions); + View::MultiSampleAntiAliasingOptions multiSampleAntiAliasingOptions; + multiSampleAntiAliasingOptions.enabled = true; - _view->setAntiAliasing(AntiAliasing::NONE); + _view->setMultiSampleAntiAliasingOptions(multiSampleAntiAliasingOptions); - EntityManager &em = EntityManager::get(); + _view->setAntiAliasing(AntiAliasing::NONE); - _ncm = new NameComponentManager(em); + EntityManager &em = EntityManager::get(); - _assetManager = new AssetManager( - _resourceLoaderWrapper, - _ncm, - _engine, - _scene, - uberArchivePath - ); - - _imageTexture = Texture::Builder() - .width(1) - .height(1) - .levels(0x01) - .format(Texture::InternalFormat::RGB16F) - .sampler(Texture::Sampler::SAMPLER_2D) - .build(*_engine); - try { - _imageMaterial = - Material::Builder() - .package(IMAGE_IMAGE_DATA, IMAGE_IMAGE_SIZE) - .build(*_engine); - _imageMaterial->setDefaultParameter("showImage",0); - _imageMaterial->setDefaultParameter("backgroundColor", RgbaType::sRGB, float4(0.5f, 0.5f, 0.5f, 1.0f)); - _imageMaterial->setDefaultParameter("image", _imageTexture, _imageSampler); - } catch(...) { - Log("Failed to load background image material provider"); - std::rethrow_exception(std::current_exception()); + _ncm = new NameComponentManager(em); + + _assetManager = new AssetManager( + _resourceLoaderWrapper, + _ncm, + _engine, + _scene, + uberArchivePath); + + _imageTexture = Texture::Builder() + .width(1) + .height(1) + .levels(0x01) + .format(Texture::InternalFormat::RGB16F) + .sampler(Texture::Sampler::SAMPLER_2D) + .build(*_engine); + try + { + _imageMaterial = + Material::Builder() + .package(IMAGE_IMAGE_DATA, IMAGE_IMAGE_SIZE) + .build(*_engine); + _imageMaterial->setDefaultParameter("showImage", 0); + _imageMaterial->setDefaultParameter("backgroundColor", RgbaType::sRGB, float4(0.5f, 0.5f, 0.5f, 1.0f)); + _imageMaterial->setDefaultParameter("image", _imageTexture, _imageSampler); + } + catch (...) + { + Log("Failed to load background image material provider"); + std::rethrow_exception(std::current_exception()); + } + _imageScale = mat4f{1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}; + + _imageMaterial->setDefaultParameter("transform", _imageScale); + + _imageVb = VertexBuffer::Builder() + .vertexCount(3) + .bufferCount(1) + .attribute(VertexAttribute::POSITION, 0, + VertexBuffer::AttributeType::FLOAT4, 0) + .build(*_engine); + + _imageVb->setBufferAt( + *_engine, 0, + {sFullScreenTriangleVertices, sizeof(sFullScreenTriangleVertices)}); + + _imageIb = IndexBuffer::Builder() + .indexCount(3) + .bufferType(IndexBuffer::IndexType::USHORT) + .build(*_engine); + + _imageIb->setBuffer(*_engine, {sFullScreenTriangleIndices, + sizeof(sFullScreenTriangleIndices)}); + + utils::Entity imageEntity = em.create(); + RenderableManager::Builder(1) + .boundingBox({{}, {1.0f, 1.0f, 1.0f}}) + .material(0, _imageMaterial->getDefaultInstance()) + .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, _imageVb, + _imageIb, 0, 3) + .culling(false) + .build(*_engine, imageEntity); + _imageEntity = &imageEntity; + _scene->addEntity(imageEntity); } - _imageScale = mat4f { 1.0f , 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; - _imageMaterial->setDefaultParameter("transform", _imageScale); + void FilamentViewer::setPostProcessing(bool enabled) + { + _view->setPostProcessingEnabled(enabled); + } - _imageVb = VertexBuffer::Builder() - .vertexCount(3) - .bufferCount(1) - .attribute(VertexAttribute::POSITION, 0, - VertexBuffer::AttributeType::FLOAT4, 0) - .build(*_engine); + void FilamentViewer::setBloom(float strength) + { + decltype(_view->getBloomOptions()) opts; + opts.enabled = true; + opts.strength = strength; + _view->setBloomOptions(opts); + } - _imageVb->setBufferAt( - *_engine, 0, - {sFullScreenTriangleVertices, sizeof(sFullScreenTriangleVertices)}); + void FilamentViewer::setToneMapping(ToneMapping toneMapping) + { - _imageIb = IndexBuffer::Builder() - .indexCount(3) - .bufferType(IndexBuffer::IndexType::USHORT) - .build(*_engine); - - _imageIb->setBuffer(*_engine, {sFullScreenTriangleIndices, - sizeof(sFullScreenTriangleIndices)}); - - utils::Entity imageEntity = em.create(); - RenderableManager::Builder(1) - .boundingBox({{}, {1.0f, 1.0f, 1.0f}}) - .material(0, _imageMaterial->getDefaultInstance()) - .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, _imageVb, - _imageIb, 0, 3) - .culling(false) - .build(*_engine, imageEntity); - _imageEntity = &imageEntity; - _scene->addEntity(imageEntity); -} - -void FilamentViewer::setPostProcessing(bool enabled) { - _view->setPostProcessingEnabled(enabled); -} - -void FilamentViewer::setBloom(float strength) { - decltype(_view->getBloomOptions()) opts; - opts.enabled = true; - opts.strength = strength; - _view->setBloomOptions(opts); -} - -void FilamentViewer::setToneMapping(ToneMapping toneMapping) { - - ToneMapper* tm; - switch(toneMapping) { + ToneMapper *tm; + switch (toneMapping) + { case ToneMapping::ACES: Log("Setting tone mapping to ACES"); tm = new ACESToneMapper(); @@ -280,474 +291,529 @@ void FilamentViewer::setToneMapping(ToneMapping toneMapping) { default: Log("ERROR: Unsupported tone mapping"); return; + } + + auto newColorGrading = ColorGrading::Builder().toneMapper(tm).build(*_engine); + _view->setColorGrading(newColorGrading); + _engine->destroy(colorGrading); + delete tm; } - - auto newColorGrading = ColorGrading::Builder().toneMapper(tm).build(*_engine); - _view->setColorGrading(newColorGrading); - _engine->destroy(colorGrading); - delete tm; -} - -void FilamentViewer::setFrameInterval(float frameInterval) { - Renderer::FrameRateOptions fro; - fro.interval = frameInterval; - _renderer->setFrameRateOptions(fro); - Log("Set framerate interval to %f", frameInterval); -} - -int32_t FilamentViewer::addLight(LightManager::Type t, float colour, float intensity, float posX, float posY, float posZ, float dirX, float dirY, float dirZ, bool shadows) { - auto light = EntityManager::get().create(); - LightManager::Builder(t) - .color(Color::cct(colour)) - .intensity(intensity) - .position(math::float3(posX, posY, posZ)) - .direction(math::float3(dirX, dirY, dirZ)) - .castShadows(shadows) - .build(*_engine, light); - _scene->addEntity(light); - _lights.push_back(light); - auto entityId = Entity::smuggle(light); - Log("Added light under entity ID %d of type %d with colour %f intensity %f at (%f, %f, %f) with direction (%f, %f, %f) with shadows %d", entityId, t, colour, intensity, posX, posY, posZ, dirX, dirY, dirZ, shadows); - return entityId; -} - -void FilamentViewer::removeLight(EntityId entityId) { - Log("Removing light with entity ID %d", entityId); - auto entity = utils::Entity::import(entityId); - if(entity.isNull()) { - Log("Error: light entity not found under ID %d", entityId); - } else { - remove(_lights.begin(), _lights.end(), entity); - _scene->remove(entity); - EntityManager::get().destroy(1, &entity); + void FilamentViewer::setFrameInterval(float frameInterval) + { + Renderer::FrameRateOptions fro; + fro.interval = frameInterval; + _renderer->setFrameRateOptions(fro); + Log("Set framerate interval to %f", frameInterval); } -} -void FilamentViewer::clearLights() { - Log("Removing all lights"); - _scene->removeEntities(_lights.data(), _lights.size()); - EntityManager::get().destroy(_lights.size(), _lights.data()); - _lights.clear(); -} + int32_t FilamentViewer::addLight(LightManager::Type t, float colour, float intensity, float posX, float posY, float posZ, float dirX, float dirY, float dirZ, bool shadows) + { + auto light = EntityManager::get().create(); + LightManager::Builder(t) + .color(Color::cct(colour)) + .intensity(intensity) + .position(math::float3(posX, posY, posZ)) + .direction(math::float3(dirX, dirY, dirZ)) + .castShadows(shadows) + .build(*_engine, light); + _scene->addEntity(light); + _lights.push_back(light); + auto entityId = Entity::smuggle(light); + Log("Added light under entity ID %d of type %d with colour %f intensity %f at (%f, %f, %f) with direction (%f, %f, %f) with shadows %d", entityId, t, colour, intensity, posX, posY, posZ, dirX, dirY, dirZ, shadows); + return entityId; + } -static bool endsWith(string path, string ending) { - return path.compare(path.length() - ending.length(), ending.length(), ending) == 0; -} + void FilamentViewer::removeLight(EntityId entityId) + { + Log("Removing light with entity ID %d", entityId); + auto entity = utils::Entity::import(entityId); + if (entity.isNull()) + { + Log("Error: light entity not found under ID %d", entityId); + } + else + { + remove(_lights.begin(), _lights.end(), entity); + _scene->remove(entity); + EntityManager::get().destroy(1, &entity); + } + } -void FilamentViewer::loadKtx2Texture(string path, ResourceBuffer rb) { + void FilamentViewer::clearLights() + { + Log("Removing all lights"); + _scene->removeEntities(_lights.data(), _lights.size()); + EntityManager::get().destroy(_lights.size(), _lights.data()); + _lights.clear(); + } - // TODO - check all this + static bool endsWith(string path, string ending) + { + return path.compare(path.length() - ending.length(), ending.length(), ending) == 0; + } - // ktxreader::Ktx2Reader reader(*_engine); + void FilamentViewer::loadKtx2Texture(string path, ResourceBuffer rb) + { - // reader.requestFormat(Texture::InternalFormat::DXT3_SRGBA); - // reader.requestFormat(Texture::InternalFormat::DXT3_RGBA); + // TODO - check all this - // // Uncompressed formats are lower priority, so they get added last. - // reader.requestFormat(Texture::InternalFormat::SRGB8_A8); - // reader.requestFormat(Texture::InternalFormat::RGBA8); + // ktxreader::Ktx2Reader reader(*_engine); - // // std::ifstream inputStream("/data/data/app.polyvox.filament_example/foo.ktx", ios::binary); + // reader.requestFormat(Texture::InternalFormat::DXT3_SRGBA); + // reader.requestFormat(Texture::InternalFormat::DXT3_RGBA); - // // auto contents = vector((istreambuf_iterator(inputStream)), {}); + // // Uncompressed formats are lower priority, so they get added last. + // reader.requestFormat(Texture::InternalFormat::SRGB8_A8); + // reader.requestFormat(Texture::InternalFormat::RGBA8); - // _imageTexture = reader.load(contents.data(), contents.size(), - // ktxreader::Ktx2Reader::TransferFunction::LINEAR); -} + // // std::ifstream inputStream("/data/data/app.polyvox.filament_example/foo.ktx", ios::binary); -void FilamentViewer::loadKtxTexture(string path, ResourceBuffer rb) { + // // auto contents = vector((istreambuf_iterator(inputStream)), {}); + + // _imageTexture = reader.load(contents.data(), contents.size(), + // ktxreader::Ktx2Reader::TransferFunction::LINEAR); + } + + void FilamentViewer::loadKtxTexture(string path, ResourceBuffer rb) + { ktxreader::Ktx1Bundle *bundle = - new ktxreader::Ktx1Bundle(static_cast(rb.data), - static_cast(rb.size)); + new ktxreader::Ktx1Bundle(static_cast(rb.data), + static_cast(rb.size)); - // because the ResourceBuffer will go out of scope before the texture callback is invoked, we need to make a copy to the heap - ResourceBuffer* rbCopy = new ResourceBuffer(rb); + ResourceBuffer *rbCopy = new ResourceBuffer(rb); - std::vector* callbackData = new std::vector { (void*)_resourceLoaderWrapper, rbCopy }; - + std::vector *callbackData = new std::vector{(void *)_resourceLoaderWrapper, rbCopy}; _imageTexture = - ktxreader::Ktx1Reader::createTexture(_engine, *bundle, false, [](void* userdata) { + ktxreader::Ktx1Reader::createTexture( + _engine, *bundle, false, [](void *userdata) + { std::vector* vec = (std::vector*)userdata; ResourceLoaderWrapper* loader = (ResourceLoaderWrapper*)vec->at(0); ResourceBuffer* rb = (ResourceBuffer*) vec->at(1); loader->free(*rb); delete rb; - delete vec; - }, callbackData); + delete vec; }, + callbackData); auto info = bundle->getInfo(); _imageWidth = info.pixelWidth; _imageHeight = info.pixelHeight; -} + } -void FilamentViewer::loadPngTexture(string path, ResourceBuffer rb) { - - polyvox::StreamBufferAdapter sb((char *)rb.data, (char *)rb.data + rb.size); + void FilamentViewer::loadPngTexture(string path, ResourceBuffer rb) + { - std::istream inputStream(&sb); + polyvox::StreamBufferAdapter sb((char *)rb.data, (char *)rb.data + rb.size); - LinearImage* image = new LinearImage(ImageDecoder::decode( + std::istream inputStream(&sb); + + LinearImage *image = new LinearImage(ImageDecoder::decode( inputStream, path.c_str(), ImageDecoder::ColorSpace::SRGB)); - if (!image->isValid()) { - Log("Invalid image : %s", path.c_str()); - return; - } - - uint32_t channels = image->getChannels(); - _imageWidth = image->getWidth(); - _imageHeight = image->getHeight(); - - _imageTexture = Texture::Builder() - .width(_imageWidth) - .height(_imageHeight) - .levels(0x01) - .format(channels == 3 ? Texture::InternalFormat::RGB16F - : Texture::InternalFormat::RGBA16F) - .sampler(Texture::Sampler::SAMPLER_2D) - .build(*_engine); - - Texture::PixelBufferDescriptor::Callback freeCallback = [](void *buf, size_t, - void *data) { - delete reinterpret_cast(data); - }; - - auto pbd = Texture::PixelBufferDescriptor( - image->getPixelRef(), size_t(_imageWidth * _imageHeight * channels * sizeof(float)), - channels == 3 ? Texture::Format::RGB : Texture::Format::RGBA, - Texture::Type::FLOAT, nullptr, freeCallback, image); - - _imageTexture->setImage(*_engine, 0, std::move(pbd)); - // we don't need to free the ResourceBuffer in the texture callback because LinearImage takes a copy - // (check if this is correct ? ) - _resourceLoaderWrapper->free(rb); -} - -void FilamentViewer::loadTextureFromPath(string path) { - string ktxExt(".ktx"); - string ktx2Ext(".ktx2"); - string pngExt(".png"); - - if (path.length() < 5) { - Log("Invalid resource path : %s", path.c_str()); - return; - } - - ResourceBuffer rb = _resourceLoaderWrapper->load(path.c_str()); - - if(endsWith(path, ktxExt)) { - loadKtxTexture(path, rb); - } else if(endsWith(path, ktx2Ext)) { - loadKtx2Texture(path, rb); - } else if(endsWith(path, pngExt)) { - loadPngTexture(path, rb); - } -} - -void FilamentViewer::setBackgroundColor(const float r, const float g, const float b, const float a) { - _imageMaterial->setDefaultParameter("showImage", 0); - _imageMaterial->setDefaultParameter("backgroundColor", RgbaType::sRGB, float4(r, g, b, a)); - _imageMaterial->setDefaultParameter("transform", _imageScale); -} - -void FilamentViewer::clearBackgroundImage() { - _imageMaterial->setDefaultParameter("showImage", 0); - if (_imageTexture) { - _engine->destroy(_imageTexture); - _imageTexture = nullptr; - Log("Destroyed background image texture"); - } -} - -void FilamentViewer::setBackgroundImage(const char *resourcePath, bool fillHeight) { - - string resourcePathString(resourcePath); - - Log("Setting background image to %s", resourcePath); - - clearBackgroundImage(); - - loadTextureFromPath(resourcePathString); - - // This currently just anchors the image at the bottom left of the viewport at its original size - // TODO - implement stretch/etc - const Viewport& vp = _view->getViewport(); - Log("Image width %d height %d vp width %d height %d", _imageWidth, _imageHeight, vp.width, vp.height); - - - float xScale = float(vp.width) / float(_imageWidth); - - float yScale; - if(fillHeight) { - yScale = 1.0f; - } else { - yScale = float(vp.height) / float(_imageHeight); - } - - _imageScale = mat4f { xScale , 0.0f, 0.0f, 0.0f, 0.0f, yScale , 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; - - _imageMaterial->setDefaultParameter("transform", _imageScale); - _imageMaterial->setDefaultParameter("image", _imageTexture, _imageSampler); - _imageMaterial->setDefaultParameter("showImage", 1); - -} - - -/// -/// Translates the background image by (x,y) pixels. -/// If clamp is true, x/y are both clamped so that the left/top and right/bottom sides of the background image -/// are positioned at a max/min of -1/1 respectively -/// (i.e. you cannot set a position where the left/top or right/bottom sides would be "inside" the screen coordinate space). -/// -void FilamentViewer::setBackgroundImagePosition(float x, float y, bool clamp=false) { - - // to translate the background image, we apply a transform to the UV coordinates of the quad texture, not the quad itself (see image.mat). - // this allows us to set a background colour for the quad when the texture has been translated outside the quad's bounds. - // so we need to munge the coordinates appropriately (and take into consideration the scale transform applied when the image was loaded). - - // first, convert x/y to a percentage of the original image size - x /= _imageWidth; - y /= _imageHeight; - - // now scale these by the viewport dimensions so they can be incorporated directly into the UV transform matrix. - // x *= _imageScale[0][0]; - // y *= _imageScale[1][1]; - - // TODO - I haven't updated the clamp calculations to work with scaled image width/height percentages so the below code is probably wrong, don't use it until it's fixed. - if(clamp) { - Log("Clamping background image translation"); - // first, clamp x/y - auto xScale = float(_imageWidth) / _view->getViewport().width; - auto yScale = float(_imageHeight) / _view->getViewport().height; - - float xMin = 0; - float xMax = 0; - float yMin = 0; - float yMax = 0; - - // we need to clamp x so that it can only be translated between (left side touching viewport left) and (right side touching viewport right) - // if width is less than viewport, these values are 0/1-xScale respectively - if(xScale < 1) { - xMin = 0; - xMax = 1-xScale; - // otherwise, these value are (xScale-1 and 1-xScale) - } else { - xMin = 1-xScale; - xMax = 0; + if (!image->isValid()) + { + Log("Invalid image : %s", path.c_str()); + return; } - // do the same for y - if(yScale < 1) { - yMin = 0; - yMax = 1-yScale; - } else { - yMin = 1-yScale; - yMax = 0; + uint32_t channels = image->getChannels(); + _imageWidth = image->getWidth(); + _imageHeight = image->getHeight(); + + _imageTexture = Texture::Builder() + .width(_imageWidth) + .height(_imageHeight) + .levels(0x01) + .format(channels == 3 ? Texture::InternalFormat::RGB16F + : Texture::InternalFormat::RGBA16F) + .sampler(Texture::Sampler::SAMPLER_2D) + .build(*_engine); + + Texture::PixelBufferDescriptor::Callback freeCallback = [](void *buf, size_t, + void *data) + { + delete reinterpret_cast(data); + }; + + auto pbd = Texture::PixelBufferDescriptor( + image->getPixelRef(), size_t(_imageWidth * _imageHeight * channels * sizeof(float)), + channels == 3 ? Texture::Format::RGB : Texture::Format::RGBA, + Texture::Type::FLOAT, nullptr, freeCallback, image); + + _imageTexture->setImage(*_engine, 0, std::move(pbd)); + // we don't need to free the ResourceBuffer in the texture callback because LinearImage takes a copy + // (check if this is correct ? ) + _resourceLoaderWrapper->free(rb); + } + + void FilamentViewer::loadTextureFromPath(string path) + { + string ktxExt(".ktx"); + string ktx2Ext(".ktx2"); + string pngExt(".png"); + + if (path.length() < 5) + { + Log("Invalid resource path : %s", path.c_str()); + return; } - x = std::max(xMin, std::min(x,xMax)); - y = std::max(yMin, std::min(y,yMax)); + ResourceBuffer rb = _resourceLoaderWrapper->load(path.c_str()); + + if (endsWith(path, ktxExt)) + { + loadKtxTexture(path, rb); + } + else if (endsWith(path, ktx2Ext)) + { + loadKtx2Texture(path, rb); + } + else if (endsWith(path, pngExt)) + { + loadPngTexture(path, rb); + } } - // these values are then negated to account for the fact that the transform is applied to the UV coordinates, not the vertices (see image.mat). - // i.e. translating the image right by 0.5 units means translating the UV coordinates left by 0.5 units. - x = -x; - y = -y; - Log("x %f y %f", x, y); - - Log("imageScale %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f ", _imageScale[0][0],_imageScale[0][1],_imageScale[0][2], _imageScale[0][3], \ - _imageScale[1][0],_imageScale[1][1],_imageScale[1][2], _imageScale[1][3],\ - _imageScale[2][0],_imageScale[2][1],_imageScale[2][2], _imageScale[2][3], \ - _imageScale[3][0],_imageScale[3][1],_imageScale[3][2], _imageScale[3][3]); - - auto transform = math::mat4f::translation(math::float3(x, y, 0.0f)) * _imageScale; - - - Log("transform %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f ", transform[0][0],transform[0][1],transform[0][2], transform[0][3], \ - transform[1][0],transform[1][1],transform[1][2], transform[1][3],\ - transform[2][0],transform[2][1],transform[2][2], transform[2][3], \ - transform[3][0],transform[3][1],transform[3][2], transform[3][3]); - _imageMaterial->setDefaultParameter("transform", transform); -} - -FilamentViewer::~FilamentViewer() { - clearAssets(); - delete _assetManager; - - for(auto it : _lights) { - _engine->destroy(it); + void FilamentViewer::setBackgroundColor(const float r, const float g, const float b, const float a) + { + _imageMaterial->setDefaultParameter("showImage", 0); + _imageMaterial->setDefaultParameter("backgroundColor", RgbaType::sRGB, float4(r, g, b, a)); + _imageMaterial->setDefaultParameter("transform", _imageScale); } - - _engine->destroyCameraComponent(_mainCamera->getEntity()); - _mainCamera = nullptr; - _engine->destroy(_view); - _engine->destroy(_scene); - _engine->destroy(_renderer); - _engine->destroy(_swapChain); - - Engine::destroy(&_engine); // clears engine* -} -Renderer *FilamentViewer::getRenderer() { return _renderer; } + void FilamentViewer::clearBackgroundImage() + { + _imageMaterial->setDefaultParameter("showImage", 0); + if (_imageTexture) + { + _engine->destroy(_imageTexture); + _imageTexture = nullptr; + Log("Destroyed background image texture"); + } + } -void FilamentViewer::createSwapChain(const void *window, uint32_t width, uint32_t height) { - #if TARGET_OS_IPHONE - _swapChain = _engine->createSwapChain((void*)window, filament::backend::SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER); - #else - if(window) { - _swapChain = _engine->createSwapChain((void*)window, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE); + void FilamentViewer::setBackgroundImage(const char *resourcePath, bool fillHeight) + { + + string resourcePathString(resourcePath); + + Log("Setting background image to %s", resourcePath); + + clearBackgroundImage(); + + loadTextureFromPath(resourcePathString); + + // This currently just anchors the image at the bottom left of the viewport at its original size + // TODO - implement stretch/etc + const Viewport &vp = _view->getViewport(); + Log("Image width %d height %d vp width %d height %d", _imageWidth, _imageHeight, vp.width, vp.height); + + float xScale = float(vp.width) / float(_imageWidth); + + float yScale; + if (fillHeight) + { + yScale = 1.0f; + } + else + { + yScale = float(vp.height) / float(_imageHeight); + } + + _imageScale = mat4f{xScale, 0.0f, 0.0f, 0.0f, 0.0f, yScale, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}; + + _imageMaterial->setDefaultParameter("transform", _imageScale); + _imageMaterial->setDefaultParameter("image", _imageTexture, _imageSampler); + _imageMaterial->setDefaultParameter("showImage", 1); + } + + /// + /// Translates the background image by (x,y) pixels. + /// If clamp is true, x/y are both clamped so that the left/top and right/bottom sides of the background image + /// are positioned at a max/min of -1/1 respectively + /// (i.e. you cannot set a position where the left/top or right/bottom sides would be "inside" the screen coordinate space). + /// + void FilamentViewer::setBackgroundImagePosition(float x, float y, bool clamp = false) + { + + // to translate the background image, we apply a transform to the UV coordinates of the quad texture, not the quad itself (see image.mat). + // this allows us to set a background colour for the quad when the texture has been translated outside the quad's bounds. + // so we need to munge the coordinates appropriately (and take into consideration the scale transform applied when the image was loaded). + + // first, convert x/y to a percentage of the original image size + x /= _imageWidth; + y /= _imageHeight; + + // now scale these by the viewport dimensions so they can be incorporated directly into the UV transform matrix. + // x *= _imageScale[0][0]; + // y *= _imageScale[1][1]; + + // TODO - I haven't updated the clamp calculations to work with scaled image width/height percentages so the below code is probably wrong, don't use it until it's fixed. + if (clamp) + { + Log("Clamping background image translation"); + // first, clamp x/y + auto xScale = float(_imageWidth) / _view->getViewport().width; + auto yScale = float(_imageHeight) / _view->getViewport().height; + + float xMin = 0; + float xMax = 0; + float yMin = 0; + float yMax = 0; + + // we need to clamp x so that it can only be translated between (left side touching viewport left) and (right side touching viewport right) + // if width is less than viewport, these values are 0/1-xScale respectively + if (xScale < 1) + { + xMin = 0; + xMax = 1 - xScale; + // otherwise, these value are (xScale-1 and 1-xScale) + } + else + { + xMin = 1 - xScale; + xMax = 0; + } + + // do the same for y + if (yScale < 1) + { + yMin = 0; + yMax = 1 - yScale; + } + else + { + yMin = 1 - yScale; + yMax = 0; + } + + x = std::max(xMin, std::min(x, xMax)); + y = std::max(yMin, std::min(y, yMax)); + } + + // these values are then negated to account for the fact that the transform is applied to the UV coordinates, not the vertices (see image.mat). + // i.e. translating the image right by 0.5 units means translating the UV coordinates left by 0.5 units. + x = -x; + y = -y; + Log("x %f y %f", x, y); + + Log("imageScale %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f ", _imageScale[0][0], _imageScale[0][1], _imageScale[0][2], _imageScale[0][3], + _imageScale[1][0], _imageScale[1][1], _imageScale[1][2], _imageScale[1][3], + _imageScale[2][0], _imageScale[2][1], _imageScale[2][2], _imageScale[2][3], + _imageScale[3][0], _imageScale[3][1], _imageScale[3][2], _imageScale[3][3]); + + auto transform = math::mat4f::translation(math::float3(x, y, 0.0f)) * _imageScale; + + Log("transform %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f ", transform[0][0], transform[0][1], transform[0][2], transform[0][3], + transform[1][0], transform[1][1], transform[1][2], transform[1][3], + transform[2][0], transform[2][1], transform[2][2], transform[2][3], + transform[3][0], transform[3][1], transform[3][2], transform[3][3]); + _imageMaterial->setDefaultParameter("transform", transform); + } + + FilamentViewer::~FilamentViewer() + { + clearAssets(); + delete _assetManager; + + for (auto it : _lights) + { + _engine->destroy(it); + } + + _engine->destroyCameraComponent(_mainCamera->getEntity()); + _mainCamera = nullptr; + _engine->destroy(_view); + _engine->destroy(_scene); + _engine->destroy(_renderer); + _engine->destroy(_swapChain); + + Engine::destroy(&_engine); // clears engine* + } + + Renderer *FilamentViewer::getRenderer() { return _renderer; } + + void FilamentViewer::createSwapChain(const void *window, uint32_t width, uint32_t height) + { +#if TARGET_OS_IPHONE + _swapChain = _engine->createSwapChain((void *)window, filament::backend::SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER); +#else + if (window) + { + _swapChain = _engine->createSwapChain((void *)window, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE); Log("Created window swapchain."); - } else { + } + else + { Log("Created headless swapchain."); _swapChain = _engine->createSwapChain(width, height, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE); } - #endif -} - -void FilamentViewer::createRenderTarget(intptr_t texture, uint32_t width, uint32_t height) { - // Create filament textures and render targets (note the color buffer has the import call) - _rtColor = filament::Texture::Builder() - .width(width) - .height(height) - .levels(1) - .usage(filament::Texture::Usage::COLOR_ATTACHMENT | filament::Texture::Usage::SAMPLEABLE) - .format(filament::Texture::InternalFormat::RGBA8) - .import(texture) - .build(*_engine); - _rtDepth = filament::Texture::Builder() - .width(width) - .height(height) - .levels(1) - .usage(filament::Texture::Usage::DEPTH_ATTACHMENT) - .format(filament::Texture::InternalFormat::DEPTH32F) - .build(*_engine); - _rt = filament::RenderTarget::Builder() - .texture(RenderTarget::AttachmentPoint::COLOR, _rtColor) - .texture(RenderTarget::AttachmentPoint::DEPTH, _rtDepth) - .build(*_engine); - - // Make a specific viewport just for our render target - _view->setRenderTarget(_rt); - - Log("Set render target for texture id %u to %u x %u", texture, width, height); - -} - -void FilamentViewer::destroySwapChain() { - if(_rt) { - _view->setRenderTarget(nullptr); - _engine->destroy(_rtDepth); - _engine->destroy(_rtColor); - _engine->destroy(_rt); - _rt = nullptr; - _rtDepth = nullptr; - _rtColor = nullptr; +#endif } - if (_swapChain) { - _engine->destroy(_swapChain); - _swapChain = nullptr; - Log("Swapchain destroyed."); - } -} -void FilamentViewer::clearAssets() { - Log("Clearing all assets"); - if(_mainCamera) { + void FilamentViewer::createRenderTarget(intptr_t texture, uint32_t width, uint32_t height) + { + // Create filament textures and render targets (note the color buffer has the import call) + _rtColor = filament::Texture::Builder() + .width(width) + .height(height) + .levels(1) + .usage(filament::Texture::Usage::COLOR_ATTACHMENT | filament::Texture::Usage::SAMPLEABLE) + .format(filament::Texture::InternalFormat::RGBA8) + .import(texture) + .build(*_engine); + _rtDepth = filament::Texture::Builder() + .width(width) + .height(height) + .levels(1) + .usage(filament::Texture::Usage::DEPTH_ATTACHMENT) + .format(filament::Texture::InternalFormat::DEPTH32F) + .build(*_engine); + _rt = filament::RenderTarget::Builder() + .texture(RenderTarget::AttachmentPoint::COLOR, _rtColor) + .texture(RenderTarget::AttachmentPoint::DEPTH, _rtDepth) + .build(*_engine); + + // Make a specific viewport just for our render target + _view->setRenderTarget(_rt); + + Log("Set render target for texture id %u to %u x %u", texture, width, height); + } + + void FilamentViewer::destroySwapChain() + { + if (_rt) + { + _view->setRenderTarget(nullptr); + _engine->destroy(_rtDepth); + _engine->destroy(_rtColor); + _engine->destroy(_rt); + _rt = nullptr; + _rtDepth = nullptr; + _rtColor = nullptr; + } + if (_swapChain) + { + _engine->destroy(_swapChain); + _swapChain = nullptr; + Log("Swapchain destroyed."); + } + } + + void FilamentViewer::clearAssets() + { + Log("Clearing all assets"); + if (_mainCamera) + { + _view->setCamera(_mainCamera); + } + + _assetManager->destroyAll(); + + Log("Cleared all assets"); + } + + void FilamentViewer::removeAsset(EntityId asset) + { + Log("Removing asset from scene"); + + mtx.lock(); + // todo - what if we are using a camera from this asset? _view->setCamera(_mainCamera); + _assetManager->remove(asset); + mtx.unlock(); } - _assetManager->destroyAll(); - - Log("Cleared all assets"); -} + /// + /// Set the exposure for the current active camera. + /// + void FilamentViewer::setCameraExposure(float aperture, float shutterSpeed, float sensitivity) + { + Camera &cam = _view->getCamera(); + Log("Setting aperture (%03f) shutterSpeed (%03f) and sensitivity (%03f)", aperture, shutterSpeed, sensitivity); + cam.setExposure(aperture, shutterSpeed, sensitivity); + } -void FilamentViewer::removeAsset(EntityId asset) { - Log("Removing asset from scene"); + /// + /// Set the focal length of the active camera. + /// + void FilamentViewer::setCameraFocalLength(float focalLength) + { + Camera &cam = _view->getCamera(); + _cameraFocalLength = focalLength; + cam.setLensProjection(_cameraFocalLength, 1.0f, kNearPlane, + kFarPlane); + } - mtx.lock(); - // todo - what if we are using a camera from this asset? - _view->setCamera(_mainCamera); - _assetManager->remove(asset); - mtx.unlock(); -} + /// + /// Set the focus distance of the active camera. + /// + void FilamentViewer::setCameraFocusDistance(float focusDistance) + { + Camera &cam = _view->getCamera(); + _cameraFocusDistance = focusDistance; + cam.setFocusDistance(_cameraFocusDistance); + } -/// -/// Set the exposure for the current active camera. -/// -void FilamentViewer::setCameraExposure(float aperture, float shutterSpeed, float sensitivity) { - Camera& cam =_view->getCamera(); - Log("Setting aperture (%03f) shutterSpeed (%03f) and sensitivity (%03f)", aperture, shutterSpeed, sensitivity); - cam.setExposure(aperture, shutterSpeed, sensitivity); -} - -/// -/// Set the focal length of the active camera. -/// -void FilamentViewer::setCameraFocalLength(float focalLength) { - Camera& cam =_view->getCamera(); - _cameraFocalLength = focalLength; - cam.setLensProjection(_cameraFocalLength, 1.0f, kNearPlane, - kFarPlane); -} - -/// -/// Set the focus distance of the active camera. -/// -void FilamentViewer::setCameraFocusDistance(float focusDistance) { - Camera& cam =_view->getCamera(); - _cameraFocusDistance = focusDistance; - cam.setFocusDistance(_cameraFocusDistance); -} - -/// -/// Sets the active camera to the GLTF camera node specified by [name] (or if null, the first camera found under that node). -/// N.B. Blender will generally export a three-node hierarchy - -/// Camera1->Camera_Orientation->Camera2. The correct name will be the Camera_Orientation. -/// -bool FilamentViewer::setCamera(EntityId entityId, const char *cameraName) { + /// + /// Sets the active camera to the GLTF camera node specified by [name] (or if null, the first camera found under that node). + /// N.B. Blender will generally export a three-node hierarchy - + /// Camera1->Camera_Orientation->Camera2. The correct name will be the Camera_Orientation. + /// + bool FilamentViewer::setCamera(EntityId entityId, const char *cameraName) + { auto asset = _assetManager->getAssetByEntityId(entityId); - if(!asset) { - Log("Failed to find asset under entity id %d.", entityId); - return false; + if (!asset) + { + Log("Failed to find asset under entity id %d.", entityId); + return false; } size_t count = asset->getCameraEntityCount(); - if (count == 0) { - Log("No cameras found attached to specified entity."); - return false; + if (count == 0) + { + Log("No cameras found attached to specified entity."); + return false; } - const utils::Entity* cameras = asset->getCameraEntities(); + const utils::Entity *cameras = asset->getCameraEntities(); utils::Entity target; - if(!cameraName) { - auto inst = _ncm->getInstance(cameras[0]); - const char *name = _ncm->getName(inst); - target = cameras[0]; - Log("No camera specified, using first camera node found (%s)", name); - } else { - for (int j = 0; j < count; j++) { - auto inst = _ncm->getInstance(cameras[j]); - const char *name = _ncm->getName(inst); - if (strcmp(name, cameraName) == 0) { - target = cameras[j]; - break; - } - } + if (!cameraName) + { + auto inst = _ncm->getInstance(cameras[0]); + const char *name = _ncm->getName(inst); + target = cameras[0]; + Log("No camera specified, using first camera node found (%s)", name); } - if(target.isNull()) { + else + { + for (int j = 0; j < count; j++) + { + auto inst = _ncm->getInstance(cameras[j]); + const char *name = _ncm->getName(inst); + if (strcmp(name, cameraName) == 0) + { + target = cameras[j]; + break; + } + } + } + if (target.isNull()) + { Log("Unable to locate camera under name %s ", cameraName); return false; } Camera *camera = _engine->getCameraComponent(target); - if(!camera) { - Log("Failed to retrieve camera component for target"); - return false; + if (!camera) + { + Log("Failed to retrieve camera component for target"); + return false; } _view->setCamera(camera); @@ -759,312 +825,400 @@ bool FilamentViewer::setCamera(EntityId entityId, const char *cameraName) { Log("Successfully set view camera to target"); return true; -} - -void FilamentViewer::loadSkybox(const char *const skyboxPath) { - - removeSkybox(); - - if (!skyboxPath) { - Log("No skybox path provided, removed skybox."); } - Log("Loading skybox from path %s", skyboxPath); - + void FilamentViewer::loadSkybox(const char *const skyboxPath) + { + + removeSkybox(); + + if (!skyboxPath) + { + Log("No skybox path provided, removed skybox."); + } + + Log("Loading skybox from path %s", skyboxPath); + ResourceBuffer skyboxBuffer = _resourceLoaderWrapper->load(skyboxPath); // because this will go out of scope before the texture callback is invoked, we need to make a copy to the heap - ResourceBuffer* skyboxBufferCopy = new ResourceBuffer(skyboxBuffer); - - if(skyboxBuffer.size <= 0) { + ResourceBuffer *skyboxBufferCopy = new ResourceBuffer(skyboxBuffer); + + if (skyboxBuffer.size <= 0) + { Log("Could not load skybox resource."); return; } Log("Loaded skybox data of length %d", skyboxBuffer.size); - std::vector* callbackData = new std::vector { (void*)_resourceLoaderWrapper, skyboxBufferCopy }; + std::vector *callbackData = new std::vector{(void *)_resourceLoaderWrapper, skyboxBufferCopy}; image::Ktx1Bundle *skyboxBundle = new image::Ktx1Bundle(static_cast(skyboxBuffer.data), static_cast(skyboxBuffer.size)); _skyboxTexture = - ktxreader::Ktx1Reader::createTexture(_engine, *skyboxBundle, false, [](void* userdata) { + ktxreader::Ktx1Reader::createTexture( + _engine, *skyboxBundle, false, [](void *userdata) + { std::vector* vec = (std::vector*)userdata; ResourceLoaderWrapper* loader = (ResourceLoaderWrapper*)vec->at(0); ResourceBuffer* rb = (ResourceBuffer*) vec->at(1); loader->free(*rb); delete rb; delete vec; - Log("Skybox load complete."); - }, callbackData); + Log("Skybox load complete."); }, + callbackData); _skybox = filament::Skybox::Builder().environment(_skyboxTexture).build(*_engine); _scene->setSkybox(_skybox); - -} - -void FilamentViewer::removeSkybox() { - Log("Removing skybox"); - _scene->setSkybox(nullptr); - if(_skybox) { - _engine->destroy(_skybox); - _skybox = nullptr; } - if(_skyboxTexture) { - _engine->destroy(_skyboxTexture); - _skyboxTexture = nullptr; - } -} -void FilamentViewer::removeIbl() { - if(_indirectLight) { - _engine->destroy(_indirectLight); - _engine->destroy(_iblTexture); - _indirectLight = nullptr; - _iblTexture = nullptr; - } - _scene->setIndirectLight(nullptr); -} - -void FilamentViewer::loadIbl(const char *const iblPath, float intensity) { - removeIbl(); - if (iblPath) { - Log("Loading IBL from %s", iblPath); - - // Load IBL. - ResourceBuffer iblBuffer = _resourceLoaderWrapper->load(iblPath); - // because this will go out of scope before the texture callback is invoked, we need to make a copy to the heap - ResourceBuffer* iblBufferCopy = new ResourceBuffer(iblBuffer); - - if(iblBuffer.size == 0) { - Log("Error loading IBL, resource could not be loaded."); - return; + void FilamentViewer::removeSkybox() + { + Log("Removing skybox"); + _scene->setSkybox(nullptr); + if (_skybox) + { + _engine->destroy(_skybox); + _skybox = nullptr; } + if (_skyboxTexture) + { + _engine->destroy(_skyboxTexture); + _skyboxTexture = nullptr; + } + } - image::Ktx1Bundle *iblBundle = - new image::Ktx1Bundle(static_cast(iblBuffer.data), - static_cast(iblBuffer.size)); - math::float3 harmonics[9]; - iblBundle->getSphericalHarmonics(harmonics); + void FilamentViewer::removeIbl() + { + if (_indirectLight) + { + _engine->destroy(_indirectLight); + _engine->destroy(_iblTexture); + _indirectLight = nullptr; + _iblTexture = nullptr; + } + _scene->setIndirectLight(nullptr); + } - std::vector* callbackData = new std::vector { (void*)_resourceLoaderWrapper, iblBufferCopy }; + void FilamentViewer::loadIbl(const char *const iblPath, float intensity) + { + removeIbl(); + if (iblPath) + { + Log("Loading IBL from %s", iblPath); + + // Load IBL. + ResourceBuffer iblBuffer = _resourceLoaderWrapper->load(iblPath); + // because this will go out of scope before the texture callback is invoked, we need to make a copy to the heap + ResourceBuffer *iblBufferCopy = new ResourceBuffer(iblBuffer); + + if (iblBuffer.size == 0) + { + Log("Error loading IBL, resource could not be loaded."); + return; + } + + image::Ktx1Bundle *iblBundle = + new image::Ktx1Bundle(static_cast(iblBuffer.data), + static_cast(iblBuffer.size)); + math::float3 harmonics[9]; + iblBundle->getSphericalHarmonics(harmonics); + + std::vector *callbackData = new std::vector{(void *)_resourceLoaderWrapper, iblBufferCopy}; _iblTexture = - ktxreader::Ktx1Reader::createTexture(_engine, *iblBundle, false, [](void* userdata) { + ktxreader::Ktx1Reader::createTexture( + _engine, *iblBundle, false, [](void *userdata) + { std::vector* vec = (std::vector*)userdata; ResourceLoaderWrapper* loader = (ResourceLoaderWrapper*)vec->at(0); ResourceBuffer* rb = (ResourceBuffer*) vec->at(1); loader->free(*rb); delete rb; - delete vec; - }, callbackData); - _indirectLight = IndirectLight::Builder() - .reflections(_iblTexture) - .irradiance(3, harmonics) - .intensity(intensity) - .build(*_engine); - _scene->setIndirectLight(_indirectLight); + delete vec; }, + callbackData); + _indirectLight = IndirectLight::Builder() + .reflections(_iblTexture) + .irradiance(3, harmonics) + .intensity(intensity) + .build(*_engine); + _scene->setIndirectLight(_indirectLight); - Log("IBL loaded."); - } -} - -double _elapsed = 0; -int _frameCount = 0; - -void FilamentViewer::render( - uint64_t frameTimeInNanos, - void* pixelBuffer, - void (*callback)(void *buf, size_t size, void *data), - void* data) { - - if (!_view || !_mainCamera || !_swapChain) { - Log("Not ready for rendering"); - return; - } - - if(_frameCount == 60) { - // Log("1 sec average for asset animation update %f", _elapsed / 60); - _elapsed = 0; - _frameCount = 0; - } - - Timer tmr; - - _assetManager->updateAnimations(); - - _elapsed += tmr.elapsed(); - _frameCount++; - - if(pixelBuffer) { - auto pbd = Texture::PixelBufferDescriptor( - pixelBuffer, size_t(1024 * 768 * 4), - Texture::Format::RGBA, - Texture::Type::BYTE, nullptr, callback, data); - - _renderer->beginFrame(_swapChain, 0); - _renderer->render(_view); - _renderer->readPixels(0,0,1024,768, std::move(pbd)); - _renderer->endFrame(); - } else { - // Render the scene, unless the renderer wants to skip the frame. - if (_renderer->beginFrame(_swapChain, frameTimeInNanos)) { - _renderer->render(_view); - _renderer->endFrame(); - } else { - // skipped frame + Log("IBL loaded."); } } - -} -void FilamentViewer::updateViewportAndCameraProjection( - int width, int height, float contentScaleFactor) { - if (!_view || !_mainCamera) { - Log("Skipping camera update, no view or camrea"); - return; + double _elapsed = 0; + int _frameCount = 0; + + void FilamentViewer::render( + uint64_t frameTimeInNanos, + void *pixelBuffer, + void (*callback)(void *buf, size_t size, void *data), + void *data) + { + + if (!_view || !_mainCamera || !_swapChain) + { + Log("Not ready for rendering"); + return; + } + + if (_frameCount == 60) + { + // Log("1 sec average for asset animation update %f", _elapsed / 60); + _elapsed = 0; + _frameCount = 0; + } + + Timer tmr; + + _assetManager->updateAnimations(); + + _elapsed += tmr.elapsed(); + _frameCount++; + + // if a manipulator is active, update the active camera orientation + if(_manipulator) { + math::double3 eye, target, upward; + Camera& cam =_view->getCamera(); + _manipulator->getLookAt(&eye, &target, &upward); + cam.lookAt(eye, target, upward); + } + + // TODO - this was an experiment but probably useful to keep for debugging + // if pixelBuffer is provided, we will copy the framebuffer into the pixelBuffer. + if (pixelBuffer) + { + auto pbd = Texture::PixelBufferDescriptor( + pixelBuffer, size_t(1024 * 768 * 4), + Texture::Format::RGBA, + Texture::Type::BYTE, nullptr, callback, data); + + _renderer->beginFrame(_swapChain, 0); + _renderer->render(_view); + _renderer->readPixels(0, 0, 1024, 768, std::move(pbd)); + _renderer->endFrame(); + } + else + { + // Render the scene, unless the renderer wants to skip the frame. + if (_renderer->beginFrame(_swapChain, frameTimeInNanos)) + { + _renderer->render(_view); + _renderer->endFrame(); + } + else + { + // skipped frame + } + } } - const uint32_t _width = width * contentScaleFactor; - const uint32_t _height = height * contentScaleFactor; - _view->setViewport({0, 0, _width, _height}); + void FilamentViewer::updateViewportAndCameraProjection( + int width, int height, float contentScaleFactor) + { + if (!_view || !_mainCamera) + { + Log("Skipping camera update, no view or camrea"); + return; + } - const double aspect = (double)width / height; + const uint32_t _width = width * contentScaleFactor; + const uint32_t _height = height * contentScaleFactor; + _view->setViewport({0, 0, _width, _height}); - Camera& cam =_view->getCamera(); - cam.setLensProjection(_cameraFocalLength, 1.0f, kNearPlane, - kFarPlane); + const double aspect = (double)width / height; - cam.setScaling({1.0 / aspect, 1.0}); + Camera &cam = _view->getCamera(); + cam.setLensProjection(_cameraFocalLength, 1.0f, kNearPlane, + kFarPlane); - Log("Set viewport to width: %d height: %d aspect %f scaleFactor : %f", width, height, aspect, - contentScaleFactor); -} + cam.setScaling({1.0 / aspect, 1.0}); -void FilamentViewer::setViewFrustumCulling(bool enabled) { - _view->setFrustumCullingEnabled(enabled); -} + Log("Set viewport to width: %d height: %d aspect %f scaleFactor : %f", width, height, aspect, + contentScaleFactor); + } -void FilamentViewer::setCameraPosition(float x, float y, float z) { - Camera& cam =_view->getCamera(); + void FilamentViewer::setViewFrustumCulling(bool enabled) + { + _view->setFrustumCullingEnabled(enabled); + } - _cameraPosition = math::mat4f::translation(math::float3(x,y,z)); - cam.setModelMatrix(_cameraPosition * _cameraRotation); -} + void FilamentViewer::setCameraPosition(float x, float y, float z) + { + Camera &cam = _view->getCamera(); -void FilamentViewer::moveCameraToAsset(EntityId entityId) { - auto asset = _assetManager->getAssetByEntityId(entityId); - if(!asset) { + _cameraPosition = math::mat4f::translation(math::float3(x, y, z)); + cam.setModelMatrix(_cameraPosition * _cameraRotation); + } + + void FilamentViewer::moveCameraToAsset(EntityId entityId) + { + auto asset = _assetManager->getAssetByEntityId(entityId); + if (!asset) + { Log("Failed to find asset attached to specified entity id."); return; - } - - const filament::Aabb bb = asset->getBoundingBox(); - auto corners = bb.getCorners(); - Camera& cam =_view->getCamera(); - auto eye = corners.vertices[0] * 1.5; - auto lookAt = corners.vertices[7]; - cam.lookAt(eye, lookAt); - Log("Moved camera to %f %f %f, lookAt %f %f %f, near %f far %f", eye[0], eye[1], eye[2], lookAt[0], lookAt[1], lookAt[2], cam.getNear(), cam.getCullingFar()); -} - -void FilamentViewer::setCameraRotation(float rads, float x, float y, float z) { - Camera& cam =_view->getCamera(); - _cameraRotation = math::mat4f::rotation(rads, math::float3(x,y,z)); - cam.setModelMatrix(_cameraPosition * _cameraRotation); -} - -void FilamentViewer::setCameraModelMatrix(const float* const matrix) { - Camera& cam =_view->getCamera(); - - mat4 modelMatrix( - matrix[0], - matrix[1], - matrix[2], - matrix[3], - matrix[4], - matrix[5], - matrix[6], - matrix[7], - matrix[8], - matrix[9], - matrix[10], - matrix[11], - matrix[12], - matrix[13], - matrix[14], - matrix[15] -); - cam.setModelMatrix(modelMatrix); -} - -void FilamentViewer::grabBegin(float x, float y, bool pan) { - if (!_view || !_mainCamera || !_swapChain) { - Log("View not ready, ignoring grab"); - return; - } - _panning = pan; - _startX = x; - _startY = y; -} - -void FilamentViewer::grabUpdate(float x, float y) { - if (!_view || !_swapChain) { - Log("View not ready, ignoring grab"); - return; } - Camera& cam =_view->getCamera(); - auto eye = cam.getPosition(); - auto target = eye + cam.getForwardVector(); - auto upward = cam.getUpVector(); - Viewport const& vp = _view->getViewport(); - if(_panning) { - auto trans = cam.getModelMatrix() * mat4::translation(math::float3 { 50 * (x - _startX) / vp.width, 50 * (y - _startY) / vp.height, 0.0f }); - cam.setModelMatrix(trans); - } else { - auto trans = cam.getModelMatrix() * mat4::rotation(0.05, - math::float3 { (y - _startY) / vp.height, (x - _startX) / vp.width, 0.0f }); - cam.setModelMatrix(trans); - } - _startX = x; - _startY = y; -} - -void FilamentViewer::grabEnd() { - if (!_view || !_mainCamera || !_swapChain) { - Log("View not ready, ignoring grab"); - return; + const filament::Aabb bb = asset->getBoundingBox(); + auto corners = bb.getCorners(); + Camera &cam = _view->getCamera(); + auto eye = corners.vertices[0] * 1.5; + auto lookAt = corners.vertices[7]; + cam.lookAt(eye, lookAt); + Log("Moved camera to %f %f %f, lookAt %f %f %f, near %f far %f", eye[0], eye[1], eye[2], lookAt[0], lookAt[1], lookAt[2], cam.getNear(), cam.getCullingFar()); } -} -void FilamentViewer::scrollBegin() { - // noop -} + void FilamentViewer::setCameraRotation(float rads, float x, float y, float z) + { + Camera &cam = _view->getCamera(); + _cameraRotation = math::mat4f::rotation(rads, math::float3(x, y, z)); + cam.setModelMatrix(_cameraPosition * _cameraRotation); + } -void FilamentViewer::scrollUpdate(float x, float y, float delta) { - Camera& cam =_view->getCamera(); - Viewport const& vp = _view->getViewport(); - auto trans = cam.getModelMatrix() * mat4::translation(math::float3 {0.0f, 0.0f, delta }); - cam.setModelMatrix(trans); -} + void FilamentViewer::setCameraModelMatrix(const float *const matrix) + { + Camera &cam = _view->getCamera(); -void FilamentViewer::scrollEnd() { - // noop -} + mat4 modelMatrix( + matrix[0], + matrix[1], + matrix[2], + matrix[3], + matrix[4], + matrix[5], + matrix[6], + matrix[7], + matrix[8], + matrix[9], + matrix[10], + matrix[11], + matrix[12], + matrix[13], + matrix[14], + matrix[15]); + cam.setModelMatrix(modelMatrix); + } -void FilamentViewer::pick(uint32_t x, uint32_t y, EntityId* entityId) { - Log("Picking at %d,%d", x, y); - _view->pick(x, y, [=](filament::View::PickingQueryResult const & result) { - + void FilamentViewer::_createManipulator() + { + Camera &cam = _view->getCamera(); + math::double3 home = cam.getPosition(); + math::double3 up = cam.getUpVector(); + math::double3 target = home + cam.getForwardVector(); + Viewport const &vp = _view->getViewport(); + Log("Creating manipulator for viewport size %dx%d with camera norm %f", vp.width, vp.height, norm(home)); + float zoomSpeed = norm(home) / 10; + zoomSpeed = math::clamp(zoomSpeed, 0.1f, 100000.0f); + + _manipulator = Manipulator::Builder() + .viewport(vp.width, vp.height) + .orbitHomePosition(home[0], home[1], home[2]) + .upVector(up.x, up.y, up.z) + .zoomSpeed(zoomSpeed) + // .orbitSpeed(0.0001, 0.0001) + .targetPosition(target[0], target[1], target[2]) + .build(Mode::ORBIT); + } + + void FilamentViewer::grabBegin(float x, float y, bool pan) + { + if (!_view || !_mainCamera || !_swapChain) + { + Log("View not ready, ignoring grab"); + return; + } + if (!_manipulator) + { + _createManipulator(); + } + if(pan) { + Log("Beginning pan at %f %f", x, y); + } else { + Log("Beginning rotate at %f %f", x, y); + } + + _manipulator->grabBegin(x, y, pan); + } + + void FilamentViewer::grabUpdate(float x, float y) + { + if (!_view || !_swapChain) + { + Log("View not ready, ignoring grab"); + return; + } + + + Log("Updating grab at %f %f", x, y); + + + if (_manipulator) + { + _manipulator->grabUpdate(x, y); + } + else + { + Log("Error - trying to use a manipulator when one is not available. Ensure you call grabBegin before grabUpdate/grabEnd"); + } + } + + void FilamentViewer::grabEnd() + { + if (!_view || !_mainCamera || !_swapChain) + { + Log("View not ready, ignoring grab"); + return; + } + if (_manipulator) + { + _manipulator->grabEnd(); + } + else + { + Log("Error - trying to use a manipulator when one is not available. Ensure you call grabBegin before grabUpdate/grabEnd"); + } + delete _manipulator; + _manipulator = nullptr; + } + + void FilamentViewer::scrollBegin() + { + if (!_manipulator) + { + _createManipulator(); + } + } + + void FilamentViewer::scrollUpdate(float x, float y, float delta) + { + if (_manipulator) + { + _manipulator->scroll(int(x), int(y), delta); + } + else + { + Log("Error - trying to use a manipulator when one is not available. Ensure you call grabBegin before grabUpdate/grabEnd"); + } + } + + void FilamentViewer::scrollEnd() + { + delete _manipulator; + _manipulator = nullptr; + } + + void FilamentViewer::pick(uint32_t x, uint32_t y, EntityId *entityId) + { + _view->pick(x, y, [=](filament::View::PickingQueryResult const &result) { *entityId = Entity::smuggle(result.renderable); - Log("Got result %d", *entityId); - }); -} + }); + } } // namespace polyvox - - diff --git a/ios/src/PolyvoxFilamentApi.cpp b/ios/src/PolyvoxFilamentApi.cpp index 45d89a56..b03dd271 100644 --- a/ios/src/PolyvoxFilamentApi.cpp +++ b/ios/src/PolyvoxFilamentApi.cpp @@ -43,32 +43,32 @@ extern "C" { ((FilamentViewer*)viewer)->setBackgroundImage(path, fillHeight); } - FLUTTER_PLUGIN_EXPORT void set_background_image_position(const void* const viewer, float x, float y, bool clamp) { + FLUTTER_PLUGIN_EXPORT void set_background_image_position(const void* const viewer, float x, float y, bool clamp) { ((FilamentViewer*)viewer)->setBackgroundImagePosition(x, y, clamp); } - FLUTTER_PLUGIN_EXPORT void set_tone_mapping(const void* const viewer, int toneMapping) { + FLUTTER_PLUGIN_EXPORT void set_tone_mapping(const void* const viewer, int toneMapping) { ((FilamentViewer*)viewer)->setToneMapping((ToneMapping)toneMapping); } - FLUTTER_PLUGIN_EXPORT void set_bloom(const void* const viewer, float strength) { + FLUTTER_PLUGIN_EXPORT void set_bloom(const void* const viewer, float strength) { Log("Setting bloom to %f", strength); ((FilamentViewer*)viewer)->setBloom(strength); } - FLUTTER_PLUGIN_EXPORT void load_skybox(const void* const viewer, const char* skyboxPath) { + FLUTTER_PLUGIN_EXPORT void load_skybox(const void* const viewer, const char* skyboxPath) { ((FilamentViewer*)viewer)->loadSkybox(skyboxPath); } - FLUTTER_PLUGIN_EXPORT void load_ibl(const void* const viewer, const char* iblPath, float intensity) { + FLUTTER_PLUGIN_EXPORT void load_ibl(const void* const viewer, const char* iblPath, float intensity) { ((FilamentViewer*)viewer)->loadIbl(iblPath, intensity); } - FLUTTER_PLUGIN_EXPORT void remove_skybox(const void* const viewer) { + FLUTTER_PLUGIN_EXPORT void remove_skybox(const void* const viewer) { ((FilamentViewer*)viewer)->removeSkybox(); } - FLUTTER_PLUGIN_EXPORT void remove_ibl(const void* const viewer) { + FLUTTER_PLUGIN_EXPORT void remove_ibl(const void* const viewer) { ((FilamentViewer*)viewer)->removeIbl(); } @@ -76,11 +76,11 @@ extern "C" { return ((FilamentViewer*)viewer)->addLight((LightManager::Type)type, colour, intensity, posX, posY, posZ, dirX, dirY, dirZ, shadows); } - FLUTTER_PLUGIN_EXPORT void remove_light(const void* const viewer, int32_t entityId) { + FLUTTER_PLUGIN_EXPORT void remove_light(const void* const viewer, int32_t entityId) { ((FilamentViewer*)viewer)->removeLight(entityId); } - FLUTTER_PLUGIN_EXPORT void clear_lights(const void* const viewer) { + FLUTTER_PLUGIN_EXPORT void clear_lights(const void* const viewer) { ((FilamentViewer*)viewer)->clearLights(); } @@ -104,27 +104,27 @@ extern "C" { ((FilamentViewer*)viewer)->moveCameraToAsset(asset); } - FLUTTER_PLUGIN_EXPORT void set_camera_focus_distance(const void* const viewer, float distance) { + FLUTTER_PLUGIN_EXPORT void set_camera_focus_distance(const void* const viewer, float distance) { ((FilamentViewer*)viewer)->setCameraFocusDistance(distance); } - FLUTTER_PLUGIN_EXPORT void set_camera_exposure(const void* const viewer, float aperture, float shutterSpeed, float sensitivity) { + FLUTTER_PLUGIN_EXPORT void set_camera_exposure(const void* const viewer, float aperture, float shutterSpeed, float sensitivity) { ((FilamentViewer*)viewer)->setCameraExposure(aperture, shutterSpeed, sensitivity); } - FLUTTER_PLUGIN_EXPORT void set_camera_position(const void* const viewer, float x, float y, float z) { + FLUTTER_PLUGIN_EXPORT void set_camera_position(const void* const viewer, float x, float y, float z) { ((FilamentViewer*)viewer)->setCameraPosition(x, y, z); } - FLUTTER_PLUGIN_EXPORT void set_camera_rotation(const void* const viewer, float rads, float x, float y, float z) { + FLUTTER_PLUGIN_EXPORT void set_camera_rotation(const void* const viewer, float rads, float x, float y, float z) { ((FilamentViewer*)viewer)->setCameraRotation(rads, x, y, z); } - FLUTTER_PLUGIN_EXPORT void set_camera_model_matrix(const void* const viewer, const float* const matrix) { + FLUTTER_PLUGIN_EXPORT void set_camera_model_matrix(const void* const viewer, const float* const matrix) { ((FilamentViewer*)viewer)->setCameraModelMatrix(matrix); } - FLUTTER_PLUGIN_EXPORT void set_camera_focal_length(const void* const viewer, float focalLength) { + FLUTTER_PLUGIN_EXPORT void set_camera_focal_length(const void* const viewer, float focalLength) { ((FilamentViewer*)viewer)->setCameraFocalLength(focalLength); } @@ -137,46 +137,46 @@ extern "C" { ((FilamentViewer*)viewer)->render(frameTimeInNanos, pixelBuffer, callback, data); } - FLUTTER_PLUGIN_EXPORT void set_frame_interval( + FLUTTER_PLUGIN_EXPORT void set_frame_interval( const void* const viewer, float frameInterval ) { ((FilamentViewer*)viewer)->setFrameInterval(frameInterval); } - FLUTTER_PLUGIN_EXPORT void destroy_swap_chain(const void* const viewer) { + FLUTTER_PLUGIN_EXPORT void destroy_swap_chain(const void* const viewer) { ((FilamentViewer*)viewer)->destroySwapChain(); } - FLUTTER_PLUGIN_EXPORT void create_swap_chain(const void* const viewer, const void* const window, uint32_t width, uint32_t height) { + FLUTTER_PLUGIN_EXPORT void create_swap_chain(const void* const viewer, const void* const window, uint32_t width, uint32_t height) { ((FilamentViewer*)viewer)->createSwapChain(window, width, height); } - FLUTTER_PLUGIN_EXPORT void update_viewport_and_camera_projection(const void* const viewer, uint32_t width, uint32_t height, float scaleFactor) { + FLUTTER_PLUGIN_EXPORT void update_viewport_and_camera_projection(const void* const viewer, uint32_t width, uint32_t height, float scaleFactor) { return ((FilamentViewer*)viewer)->updateViewportAndCameraProjection(width, height, scaleFactor); } - FLUTTER_PLUGIN_EXPORT void scroll_update(const void* const viewer, float x, float y, float delta) { + FLUTTER_PLUGIN_EXPORT void scroll_update(const void* const viewer, float x, float y, float delta) { ((FilamentViewer*)viewer)->scrollUpdate(x, y, delta); } - FLUTTER_PLUGIN_EXPORT void scroll_begin(const void* const viewer) { + FLUTTER_PLUGIN_EXPORT void scroll_begin(const void* const viewer) { ((FilamentViewer*)viewer)->scrollBegin(); } - FLUTTER_PLUGIN_EXPORT void scroll_end(const void* const viewer) { + FLUTTER_PLUGIN_EXPORT void scroll_end(const void* const viewer) { ((FilamentViewer*)viewer)->scrollEnd(); } - FLUTTER_PLUGIN_EXPORT void grab_begin(const void* const viewer, float x, float y, bool pan) { + FLUTTER_PLUGIN_EXPORT void grab_begin(const void* const viewer, float x, float y, bool pan) { ((FilamentViewer*)viewer)->grabBegin(x, y, pan); } - FLUTTER_PLUGIN_EXPORT void grab_update(const void* const viewer, float x, float y) { + FLUTTER_PLUGIN_EXPORT void grab_update(const void* const viewer, float x, float y) { ((FilamentViewer*)viewer)->grabUpdate(x, y); } - FLUTTER_PLUGIN_EXPORT void grab_end(const void* const viewer) { + FLUTTER_PLUGIN_EXPORT void grab_end(const void* const viewer) { ((FilamentViewer*)viewer)->grabEnd(); } @@ -184,7 +184,7 @@ extern "C" { return (void*)((FilamentViewer*)viewer)->getAssetManager(); } - FLUTTER_PLUGIN_EXPORT void apply_weights( + FLUTTER_PLUGIN_EXPORT void apply_weights( void* assetManager, EntityId asset, const char* const entityName, @@ -193,7 +193,7 @@ extern "C" { // ((AssetManager*)assetManager)->setMorphTargetWeights(asset, entityName, weights, count); } - FLUTTER_PLUGIN_EXPORT void set_morph_target_weights( + FLUTTER_PLUGIN_EXPORT void set_morph_target_weights( void* assetManager, EntityId asset, const char* const entityName, @@ -288,7 +288,7 @@ extern "C" { // } - FLUTTER_PLUGIN_EXPORT void play_animation( + FLUTTER_PLUGIN_EXPORT void play_animation( void* assetManager, EntityId asset, int index, @@ -299,7 +299,7 @@ extern "C" { ((AssetManager*)assetManager)->playAnimation(asset, index, loop, reverse, replaceActive, crossfade); } - FLUTTER_PLUGIN_EXPORT void set_animation_frame( + FLUTTER_PLUGIN_EXPORT void set_animation_frame( void* assetManager, EntityId asset, int animationIndex, @@ -319,7 +319,7 @@ extern "C" { return (int)names->size(); } - FLUTTER_PLUGIN_EXPORT void get_animation_name( + FLUTTER_PLUGIN_EXPORT void get_animation_name( void* assetManager, EntityId asset, char* const outPtr, @@ -335,17 +335,17 @@ extern "C" { return (int)names->size(); } - FLUTTER_PLUGIN_EXPORT void get_morph_target_name(void* assetManager, EntityId asset, const char* meshName, char* const outPtr, int index ) { + FLUTTER_PLUGIN_EXPORT void get_morph_target_name(void* assetManager, EntityId asset, const char* meshName, char* const outPtr, int index ) { unique_ptr> names = ((AssetManager*)assetManager)->getMorphTargetNames(asset, meshName); string name = names->at(index); strcpy(outPtr, name.c_str()); } - FLUTTER_PLUGIN_EXPORT void remove_asset(const void* const viewer, EntityId asset) { + FLUTTER_PLUGIN_EXPORT void remove_asset(const void* const viewer, EntityId asset) { ((FilamentViewer*)viewer)->removeAsset(asset); } - FLUTTER_PLUGIN_EXPORT void clear_assets(const void* const viewer) { + FLUTTER_PLUGIN_EXPORT void clear_assets(const void* const viewer) { ((FilamentViewer*)viewer)->clearAssets(); } @@ -353,23 +353,23 @@ extern "C" { return ((AssetManager*)assetManager)->setMaterialColor(asset, meshName, materialIndex, r, g, b, a); } - FLUTTER_PLUGIN_EXPORT void transform_to_unit_cube(void* assetManager, EntityId asset) { + FLUTTER_PLUGIN_EXPORT void transform_to_unit_cube(void* assetManager, EntityId asset) { ((AssetManager*)assetManager)->transformToUnitCube(asset); } - FLUTTER_PLUGIN_EXPORT void set_position(void* assetManager, EntityId asset, float x, float y, float z) { + FLUTTER_PLUGIN_EXPORT void set_position(void* assetManager, EntityId asset, float x, float y, float z) { ((AssetManager*)assetManager)->setPosition(asset, x, y, z); } - FLUTTER_PLUGIN_EXPORT void set_rotation(void* assetManager, EntityId asset, float rads, float x, float y, float z) { + FLUTTER_PLUGIN_EXPORT void set_rotation(void* assetManager, EntityId asset, float rads, float x, float y, float z) { ((AssetManager*)assetManager)->setRotation(asset, rads, x, y, z); } - FLUTTER_PLUGIN_EXPORT void set_scale(void* assetManager, EntityId asset, float scale) { + FLUTTER_PLUGIN_EXPORT void set_scale(void* assetManager, EntityId asset, float scale) { ((AssetManager*)assetManager)->setScale(asset, scale); } - FLUTTER_PLUGIN_EXPORT void stop_animation(void* assetManager, EntityId asset, int index) { + FLUTTER_PLUGIN_EXPORT void stop_animation(void* assetManager, EntityId asset, int index) { ((AssetManager*)assetManager)->stopAnimation(asset, index); } diff --git a/ios/src/camutils/Bookmark.cpp b/ios/src/camutils/Bookmark.cpp new file mode 100644 index 00000000..9c16177b --- /dev/null +++ b/ios/src/camutils/Bookmark.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include + +using namespace filament::math; + +namespace filament { +namespace camutils { + +template +Bookmark Bookmark::interpolate(Bookmark a, Bookmark b, double t) { + Bookmark result; + using float3 = filament::math::vec3; + + if (a.mode == Mode::MAP) { + assert(b.mode == Mode::MAP); + const double rho = sqrt(2.0); + const double rho2 = 2, rho4 = 4; + const double ux0 = a.map.center.x, uy0 = a.map.center.y, w0 = a.map.extent; + const double ux1 = b.map.center.x, uy1 = b.map.center.y, w1 = b.map.extent; + const double dx = ux1 - ux0, dy = uy1 - uy0, d2 = dx * dx + dy * dy, d1 = sqrt(d2); + const double b0 = (w1 * w1 - w0 * w0 + rho4 * d2) / (2.0 * w0 * rho2 * d1); + const double b1 = (w1 * w1 - w0 * w0 - rho4 * d2) / (2.0 * w1 * rho2 * d1); + const double r0 = log(sqrt(b0 * b0 + 1.0) - b0); + const double r1 = log(sqrt(b1 * b1 + 1) - b1); + const double dr = r1 - r0; + const int valid = !std::isnan(dr) && dr != 0; + const double S = (valid ? dr : log(w1 / w0)) / rho; + const double s = t * S; + + // This performs Van Wijk interpolation to animate between two waypoints on a map. + if (valid) { + const double coshr0 = cosh(r0); + const double u = w0 / (rho2 * d1) * (coshr0 * tanh(rho * s + r0) - sinh(r0)); + Bookmark result; + result.map.center.x = ux0 + u * dx; + result.map.center.y = uy0 + u * dy; + result.map.extent = w0 * coshr0 / cosh(rho * s + r0); + return result; + } + + // For degenerate cases, fall back to a simplified interpolation method. + result.map.center.x = ux0 + t * dx; + result.map.center.y = uy0 + t * dy; + result.map.extent = w0 * exp(rho * s); + return result; +} + + assert(b.mode == Mode::ORBIT); + result.orbit.phi = lerp(a.orbit.phi, b.orbit.phi, FLOAT(t)); + result.orbit.theta = lerp(a.orbit.theta, b.orbit.theta, FLOAT(t)); + result.orbit.distance = lerp(a.orbit.distance, b.orbit.distance, FLOAT(t)); + result.orbit.pivot = lerp(a.orbit.pivot, b.orbit.pivot, float3(t)); + return result; +} + +// Uses the Van Wijk method to suggest a duration for animating between two waypoints on a map. +// This does not have units, so just use it as a multiplier. +template +double Bookmark::duration(Bookmark a, Bookmark b) { + assert(a.mode == Mode::ORBIT && b.mode == Mode::ORBIT); + const double rho = sqrt(2.0); + const double rho2 = 2, rho4 = 4; + const double ux0 = a.map.center.x, uy0 = a.map.center.y, w0 = a.map.extent; + const double ux1 = b.map.center.x, uy1 = b.map.center.y, w1 = b.map.extent; + const double dx = ux1 - ux0, dy = uy1 - uy0, d2 = dx * dx + dy * dy, d1 = sqrt(d2); + const double b0 = (w1 * w1 - w0 * w0 + rho4 * d2) / (2.0 * w0 * rho2 * d1); + const double b1 = (w1 * w1 - w0 * w0 - rho4 * d2) / (2.0 * w1 * rho2 * d1); + const double r0 = log(sqrt(b0 * b0 + 1.0) - b0); + const double r1 = log(sqrt(b1 * b1 + 1) - b1); + const double dr = r1 - r0; + const int valid = !std::isnan(dr) && dr != 0; + const double S = (valid ? dr : log(w1 / w0)) / rho; + return fabs(S); +} + +template class Bookmark; + +} // namespace camutils +} // namespace filament diff --git a/ios/src/camutils/FreeFlightManipulator.h b/ios/src/camutils/FreeFlightManipulator.h new file mode 100644 index 00000000..1df4dd56 --- /dev/null +++ b/ios/src/camutils/FreeFlightManipulator.h @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CAMUTILS_FREEFLIGHT_MANIPULATOR_H +#define CAMUTILS_FREEFLIGHT_MANIPULATOR_H + +#include + +#include +#include +#include +#include + +#include + +namespace filament { +namespace camutils { + +using namespace filament::math; + +template +class FreeFlightManipulator : public Manipulator { +public: + using vec2 = filament::math::vec2; + using vec3 = filament::math::vec3; + using vec4 = filament::math::vec4; + using Bookmark = filament::camutils::Bookmark; + using Base = Manipulator; + using Config = typename Base::Config; + + FreeFlightManipulator(Mode mode, const Config& props) : Base(mode, props) { + setProperties(props); + Base::mEye = Base::mProps.flightStartPosition; + const auto pitch = Base::mProps.flightStartPitch; + const auto yaw = Base::mProps.flightStartYaw; + mTargetEuler = {pitch, yaw}; + updateTarget(pitch, yaw); + } + + void setProperties(const Config& props) override { + Config resolved = props; + + if (resolved.flightPanSpeed == vec2(0, 0)) { + resolved.flightPanSpeed = vec2(0.01, 0.01); + } + if (resolved.flightMaxSpeed == 0.0) { + resolved.flightMaxSpeed = 10.0; + } + if (resolved.flightSpeedSteps == 0) { + resolved.flightSpeedSteps = 80; + } + + Base::setProperties(resolved); + } + + void updateTarget(FLOAT pitch, FLOAT yaw) { + Base::mTarget = Base::mEye + (mat3::eulerZYX(0, yaw, pitch) * vec3(0.0, 0.0, -1.0)); + } + + void grabBegin(int x, int y, bool strafe) override { + mGrabWin = {x, y}; + mGrabbing = true; + mGrabEuler = mTargetEuler; + } + + void grabUpdate(int x, int y) override { + if (!mGrabbing) { + return; + } + + const vec2 del = mGrabWin - vec2{x, y}; + + const auto& grabPitch = mGrabEuler.x; + const auto& grabYaw = mGrabEuler.y; + auto& pitch = mTargetEuler.x; + auto& yaw = mTargetEuler.y; + + constexpr double EPSILON = 0.001; + + auto panSpeed = Base::mProps.flightPanSpeed; + constexpr FLOAT minPitch = (-F_PI_2 + EPSILON); + constexpr FLOAT maxPitch = ( F_PI_2 - EPSILON); + pitch = clamp(grabPitch + del.y * -panSpeed.y, minPitch, maxPitch); + yaw = fmod(grabYaw + del.x * panSpeed.x, 2.0 * F_PI); + + updateTarget(pitch, yaw); + } + + void grabEnd() override { + mGrabbing = false; + } + + void keyDown(typename Base::Key key) override { + mKeyDown[(int) key] = true; + } + + void keyUp(typename Base::Key key) override { + mKeyDown[(int) key] = false; + } + + void scroll(int x, int y, FLOAT scrolldelta) override { + const FLOAT halfSpeedSteps = Base::mProps.flightSpeedSteps / 2; + mScrollWheel = clamp(mScrollWheel + scrolldelta, -halfSpeedSteps, halfSpeedSteps); + // Normalize the scroll position from -1 to 1 and calculate the move speed, in world + // units per second. + mScrollPositionNormalized = (mScrollWheel + halfSpeedSteps) / halfSpeedSteps - 1.0; + mMoveSpeed = pow(Base::mProps.flightMaxSpeed, mScrollPositionNormalized); + } + + void update(FLOAT deltaTime) override { + vec3 forceLocal { 0.0, 0.0, 0.0 }; + + if (mKeyDown[(int) Base::Key::FORWARD]) { + forceLocal += vec3{ 0.0, 0.0, -1.0 }; + } + if (mKeyDown[(int) Base::Key::LEFT]) { + forceLocal += vec3{ -1.0, 0.0, 0.0 }; + } + if (mKeyDown[(int) Base::Key::BACKWARD]) { + forceLocal += vec3{ 0.0, 0.0, 1.0 }; + } + if (mKeyDown[(int) Base::Key::RIGHT]) { + forceLocal += vec3{ 1.0, 0.0, 0.0 }; + } + + const mat4 orientation = mat4::lookAt(Base::mEye, Base::mTarget, Base::mProps.upVector); + vec3 forceWorld = (orientation * vec4{ forceLocal, 0.0f }).xyz; + + if (mKeyDown[(int) Base::Key::UP]) { + forceWorld += vec3{ 0.0, 1.0, 0.0 }; + } + if (mKeyDown[(int) Base::Key::DOWN]) { + forceWorld += vec3{ 0.0, -1.0, 0.0 }; + } + + forceWorld *= mMoveSpeed; + + const auto dampingFactor = Base::mProps.flightMoveDamping; + if (dampingFactor == 0.0) { + // Without damping, we simply treat the force as our velocity. + mEyeVelocity = forceWorld; + } else { + // The dampingFactor acts as "friction", which acts upon the camera in the direction + // opposite its velocity. + // Force is also multiplied by the dampingFactor, to "make up" for the friction. + // This ensures that the max velocity still approaches mMoveSpeed; + vec3 velocityDelta = (forceWorld - mEyeVelocity) * dampingFactor; + mEyeVelocity += velocityDelta * deltaTime; + } + + const vec3 positionDelta = mEyeVelocity * deltaTime; + + Base::mEye += positionDelta; + Base::mTarget += positionDelta; + } + + Bookmark getCurrentBookmark() const override { + Bookmark bookmark; + bookmark.flight.position = Base::mEye; + bookmark.flight.pitch = mTargetEuler.x; + bookmark.flight.yaw = mTargetEuler.y; + return bookmark; + } + + Bookmark getHomeBookmark() const override { + Bookmark bookmark; + bookmark.flight.position = Base::mProps.flightStartPosition; + bookmark.flight.pitch = Base::mProps.flightStartPitch; + bookmark.flight.yaw = Base::mProps.flightStartYaw; + return bookmark; + } + + void jumpToBookmark(const Bookmark& bookmark) override { + Base::mEye = bookmark.flight.position; + updateTarget(bookmark.flight.pitch, bookmark.flight.yaw); + } + +private: + vec2 mGrabWin; + vec2 mTargetEuler; // (pitch, yaw) + vec2 mGrabEuler; // (pitch, yaw) + bool mKeyDown[(int) Base::Key::COUNT] = {false}; + bool mGrabbing = false; + FLOAT mScrollWheel = 0.0f; + FLOAT mScrollPositionNormalized = 0.0f; + FLOAT mMoveSpeed = 1.0f; + vec3 mEyeVelocity; +}; + +} // namespace camutils +} // namespace filament + +#endif /* CAMUTILS_FREEFLIGHT_MANIPULATOR_H */ diff --git a/ios/src/camutils/Manipulator.cpp b/ios/src/camutils/Manipulator.cpp new file mode 100644 index 00000000..0c748b8e --- /dev/null +++ b/ios/src/camutils/Manipulator.cpp @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include "FreeFlightManipulator.h" +#include "MapManipulator.h" +#include "OrbitManipulator.h" + +using namespace filament::math; + +namespace filament { +namespace camutils { + +template typename +Manipulator::Builder& Manipulator::Builder::viewport(int width, int height) { + details.viewport[0] = width; + details.viewport[1] = height; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::targetPosition(FLOAT x, FLOAT y, FLOAT z) { + details.targetPosition = {x, y, z}; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::upVector(FLOAT x, FLOAT y, FLOAT z) { + details.upVector = {x, y, z}; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::zoomSpeed(FLOAT val) { + details.zoomSpeed = val; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::orbitHomePosition(FLOAT x, FLOAT y, FLOAT z) { + details.orbitHomePosition = {x, y, z}; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::orbitSpeed(FLOAT x, FLOAT y) { + details.orbitSpeed = {x, y}; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::fovDirection(Fov fov) { + details.fovDirection = fov; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::fovDegrees(FLOAT degrees) { + details.fovDegrees = degrees; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::farPlane(FLOAT distance) { + details.farPlane = distance; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::mapExtent(FLOAT worldWidth, FLOAT worldHeight) { + details.mapExtent = {worldWidth, worldHeight}; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::mapMinDistance(FLOAT mindist) { + details.mapMinDistance = mindist; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::flightStartPosition(FLOAT x, FLOAT y, FLOAT z) { + details.flightStartPosition = {x, y, z}; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::flightStartOrientation(FLOAT pitch, FLOAT yaw) { + details.flightStartPitch = pitch; + details.flightStartYaw = yaw; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::flightMaxMoveSpeed(FLOAT maxSpeed) { + details.flightMaxSpeed = maxSpeed; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::flightSpeedSteps(int steps) { + details.flightSpeedSteps = steps; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::flightPanSpeed(FLOAT x, FLOAT y) { + details.flightPanSpeed = {x, y}; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::flightMoveDamping(FLOAT damping) { + details.flightMoveDamping = damping; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::groundPlane(FLOAT a, FLOAT b, FLOAT c, FLOAT d) { + details.groundPlane = {a, b, c, d}; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::raycastCallback(RayCallback cb, void* userdata) { + details.raycastCallback = cb; + details.raycastUserdata = userdata; + return *this; +} + +template +Manipulator* Manipulator::Builder::build(Mode mode) { + switch (mode) { + case Mode::FREE_FLIGHT: + return new FreeFlightManipulator(mode, details); + case Mode::MAP: + return new MapManipulator(mode, details); + case Mode::ORBIT: + return new OrbitManipulator(mode, details); + } +} + +template +Manipulator::Manipulator(Mode mode, const Config& props) : mMode(mode) { + setProperties(props); +} + +template +void Manipulator::setProperties(const Config& props) { + mProps = props; + + if (mProps.zoomSpeed == FLOAT(0)) { + mProps.zoomSpeed = 0.01; + } + + if (mProps.upVector == vec3(0)) { + mProps.upVector = vec3(0, 1, 0); + } + + if (mProps.fovDegrees == FLOAT(0)) { + mProps.fovDegrees = 33; + } + + if (mProps.farPlane == FLOAT(0)) { + mProps.farPlane = 5000; + } + + if (mProps.mapExtent == vec2(0)) { + mProps.mapExtent = vec2(512); + } +} + +template +void Manipulator::setViewport(int width, int height) { + Config props = mProps; + props.viewport[0] = width; + props.viewport[1] = height; + setProperties(props); +} + +template +void Manipulator::getLookAt(vec3* eyePosition, vec3* targetPosition, vec3* upward) const { + *targetPosition = mTarget; + *eyePosition = mEye; + const vec3 gaze = normalize(mTarget - mEye); + const vec3 right = cross(gaze, mProps.upVector); + *upward = cross(right, gaze); +} + +template +static bool raycastPlane(const filament::math::vec3& origin, + const filament::math::vec3& dir, FLOAT* t, void* userdata) { + using vec3 = filament::math::vec3; + using vec4 = filament::math::vec4; + auto props = (const typename Manipulator::Config*) userdata; + const vec4 plane = props->groundPlane; + const vec3 n = vec3(plane[0], plane[1], plane[2]); + const vec3 p0 = n * plane[3]; + const FLOAT denom = -dot(n, dir); + if (denom > 1e-6) { + const vec3 p0l0 = p0 - origin; + *t = dot(p0l0, n) / -denom; + return *t >= 0; + } + return false; +} + +template +void Manipulator::getRay(int x, int y, vec3* porigin, vec3* pdir) const { + const vec3 gaze = normalize(mTarget - mEye); + const vec3 right = normalize(cross(gaze, mProps.upVector)); + const vec3 upward = cross(right, gaze); + const FLOAT width = mProps.viewport[0]; + const FLOAT height = mProps.viewport[1]; + const FLOAT fov = mProps.fovDegrees * F_PI / 180.0; + + // Remap the grid coordinate into [-1, +1] and shift it to the pixel center. + const FLOAT u = 2.0 * (0.5 + x) / width - 1.0; + const FLOAT v = 2.0 * (0.5 + y) / height - 1.0; + + // Compute the tangent of the field-of-view angle as well as the aspect ratio. + const FLOAT tangent = tan(fov / 2.0); + const FLOAT aspect = width / height; + + // Adjust the gaze so it goes through the pixel of interest rather than the grid center. + vec3 dir = gaze; + if (mProps.fovDirection == Fov::VERTICAL) { + dir += right * tangent * u * aspect; + dir += upward * tangent * v; + } else { + dir += right * tangent * u; + dir += upward * tangent * v / aspect; + } + dir = normalize(dir); + + *porigin = mEye; + *pdir = dir; +} + +template +bool Manipulator::raycast(int x, int y, vec3* result) const { + vec3 origin, dir; + getRay(x, y, &origin, &dir); + + // Choose either the user's callback function or the plane intersector. + auto callback = mProps.raycastCallback; + auto fallback = raycastPlane; + void* userdata = mProps.raycastUserdata; + if (!callback) { + callback = fallback; + userdata = (void*) &mProps; + } + + // If the ray misses, then try the fallback function. + FLOAT t; + if (!callback(mEye, dir, &t, userdata)) { + if (callback == fallback || !fallback(mEye, dir, &t, (void*) &mProps)) { + return false; + } + } + + *result = mEye + dir * t; + return true; +} + +template +filament::math::vec3 Manipulator::raycastFarPlane(int x, int y) const { + const filament::math::vec3 gaze = normalize(mTarget - mEye); + const vec3 right = cross(gaze, mProps.upVector); + const vec3 upward = cross(right, gaze); + const FLOAT width = mProps.viewport[0]; + const FLOAT height = mProps.viewport[1]; + const FLOAT fov = mProps.fovDegrees * math::F_PI / 180.0; + + // Remap the grid coordinate into [-1, +1] and shift it to the pixel center. + const FLOAT u = 2.0 * (0.5 + x) / width - 1.0; + const FLOAT v = 2.0 * (0.5 + y) / height - 1.0; + + // Compute the tangent of the field-of-view angle as well as the aspect ratio. + const FLOAT tangent = tan(fov / 2.0); + const FLOAT aspect = width / height; + + // Adjust the gaze so it goes through the pixel of interest rather than the grid center. + vec3 dir = gaze; + if (mProps.fovDirection == Fov::VERTICAL) { + dir += right * tangent * u * aspect; + dir += upward * tangent * v; + } else { + dir += right * tangent * u; + dir += upward * tangent * v / aspect; + } + return mEye + dir * mProps.farPlane; +} + +template +void Manipulator::keyDown(Manipulator::Key key) { } + +template +void Manipulator::keyUp(Manipulator::Key key) { } + +template +void Manipulator::update(FLOAT deltaTime) { } + +template class Manipulator; +template class Manipulator; + +} // namespace camutils +} // namespace filament diff --git a/ios/src/camutils/MapManipulator.h b/ios/src/camutils/MapManipulator.h new file mode 100644 index 00000000..6df5b4c5 --- /dev/null +++ b/ios/src/camutils/MapManipulator.h @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CAMUTILS_MAP_MANIPULATOR_H +#define CAMUTILS_MAP_MANIPULATOR_H + +#include + +#include + +namespace filament { +namespace camutils { + +template +class MapManipulator : public Manipulator { +public: + using vec2 = math::vec2; + using vec3 = math::vec3; + using vec4 = math::vec4; + using Bookmark = filament::camutils::Bookmark; + using Base = Manipulator; + using Config = typename Manipulator::Config; + + MapManipulator(Mode mode, const Config& props) : Manipulator(mode, props) { + const FLOAT width = Base::mProps.mapExtent.x; + const FLOAT height = Base::mProps.mapExtent.y; + const bool horiz = Base::mProps.fovDirection == Fov::HORIZONTAL; + const vec3 targetToEye = Base::mProps.groundPlane.xyz; + const FLOAT halfExtent = (horiz ? width : height) / 2.0; + const FLOAT fov = Base::mProps.fovDegrees * math::F_PI / 180.0; + const FLOAT distance = halfExtent / tan(fov / 2.0); + Base::mTarget = Base::mProps.targetPosition; + Base::mEye = Base::mTarget + distance * targetToEye; + } + + void grabBegin(int x, int y, bool strafe) override { + if (strafe || !Base::raycast(x, y, &mGrabScene)) { + return; + } + mGrabFar = Base::raycastFarPlane(x, y); + mGrabEye = Base::mEye; + mGrabTarget = Base::mTarget; + mGrabbing = true; + } + + void grabUpdate(int x, int y) override { + if (mGrabbing) { + const FLOAT ulen = distance(mGrabScene, mGrabEye); + const FLOAT vlen = distance(mGrabFar, mGrabScene); + const vec3 translation = (mGrabFar - Base::raycastFarPlane(x, y)) * ulen / vlen; + const vec3 eyePosition = mGrabEye + translation; + const vec3 targetPosition = mGrabTarget + translation; + moveWithConstraints(eyePosition, targetPosition); + } + } + + void grabEnd() override { + mGrabbing = false; + } + + void scroll(int x, int y, FLOAT scrolldelta) override { + vec3 grabScene; + if (!Base::raycast(x, y, &grabScene)) { + return; + } + + // Find the direction of travel for the dolly. We do not normalize since it + // is desirable to move faster when further away from the targetPosition. + vec3 u = grabScene - Base::mEye; + + // Prevent getting stuck when zooming in. + if (scrolldelta < 0) { + const FLOAT distanceToSurface = length(u); + if (distanceToSurface < Base::mProps.zoomSpeed) { + return; + } + } + + u *= -scrolldelta * Base::mProps.zoomSpeed; + + const vec3 eyePosition = Base::mEye + u; + const vec3 targetPosition = Base::mTarget + u; + moveWithConstraints(eyePosition, targetPosition); + } + + Bookmark getCurrentBookmark() const override { + const vec3 dir = normalize(Base::mTarget - Base::mEye); + + FLOAT distance; + raycastPlane(Base::mEye, dir, &distance); + + const FLOAT fov = Base::mProps.fovDegrees * math::F_PI / 180.0; + const FLOAT halfExtent = distance * tan(fov / 2.0); + + vec3 targetPosition = Base::mEye + dir * distance; + + const vec3 targetToEye = Base::mProps.groundPlane.xyz; + const vec3 uvec = cross(Base::mProps.upVector, targetToEye); + const vec3 vvec = cross(targetToEye, uvec); + const vec3 centerToTarget = targetPosition - Base::mProps.targetPosition; + + Bookmark bookmark; + bookmark.mode = Mode::MAP; + bookmark.map.extent = halfExtent * 2.0; + bookmark.map.center.x = dot(uvec, centerToTarget); + bookmark.map.center.y = dot(vvec, centerToTarget); + + bookmark.orbit.theta = 0; + bookmark.orbit.phi = 0; + bookmark.orbit.pivot = Base::mProps.targetPosition + + uvec * bookmark.map.center.x + + vvec * bookmark.map.center.y; + bookmark.orbit.distance = halfExtent / tan(fov / 2.0); + + return bookmark; + } + + Bookmark getHomeBookmark() const override { + const FLOAT fov = Base::mProps.fovDegrees * math::F_PI / 180.0; + const FLOAT width = Base::mProps.mapExtent.x; + const FLOAT height = Base::mProps.mapExtent.y; + const bool horiz = Base::mProps.fovDirection == Fov::HORIZONTAL; + + Bookmark bookmark; + bookmark.mode = Mode::MAP; + bookmark.map.extent = horiz ? width : height; + bookmark.map.center.x = 0; + bookmark.map.center.y = 0; + + bookmark.orbit.theta = 0; + bookmark.orbit.phi = 0; + bookmark.orbit.pivot = Base::mTarget; + bookmark.orbit.distance = 0.5 * bookmark.map.extent / tan(fov / 2.0); + + // TODO: Add optional boundary constraints here. + + return bookmark; + } + + void jumpToBookmark(const Bookmark& bookmark) override { + const vec3 targetToEye = Base::mProps.groundPlane.xyz; + const FLOAT halfExtent = bookmark.map.extent / 2.0; + const FLOAT fov = Base::mProps.fovDegrees * math::F_PI / 180.0; + const FLOAT distance = halfExtent / tan(fov / 2.0); + vec3 uvec = cross(Base::mProps.upVector, targetToEye); + vec3 vvec = cross(targetToEye, uvec); + uvec = normalize(uvec) * bookmark.map.center.x; + vvec = normalize(vvec) * bookmark.map.center.y; + Base::mTarget = Base::mProps.targetPosition + uvec + vvec; + Base::mEye = Base::mTarget + distance * targetToEye; + } + +private: + bool raycastPlane(const vec3& origin, const vec3& dir, FLOAT* t) const { + const vec4 plane = Base::mProps.groundPlane; + const vec3 n = vec3(plane[0], plane[1], plane[2]); + const vec3 p0 = n * plane[3]; + const FLOAT denom = -dot(n, dir); + if (denom > 1e-6) { + const vec3 p0l0 = p0 - origin; + *t = dot(p0l0, n) / -denom; + return *t >= 0; + } + return false; + } + + void moveWithConstraints(vec3 eye, vec3 targetPosition) { + Base::mEye = eye; + Base::mTarget = targetPosition; + // TODO: Add optional boundary constraints here. + } + +private: + bool mGrabbing = false; + vec3 mGrabScene; + vec3 mGrabFar; + vec3 mGrabEye; + vec3 mGrabTarget; +}; + +} // namespace camutils +} // namespace filament + +#endif /* CAMUTILS_MAP_MANIPULATOR_H */ diff --git a/ios/src/camutils/OrbitManipulator.h b/ios/src/camutils/OrbitManipulator.h new file mode 100644 index 00000000..54e325cc --- /dev/null +++ b/ios/src/camutils/OrbitManipulator.h @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CAMUTILS_ORBIT_MANIPULATOR_H +#define CAMUTILS_ORBIT_MANIPULATOR_H + +#include + +#include + +#define MAX_PHI (F_PI / 2.0 - 0.001) + +namespace filament { +namespace camutils { + +using namespace filament::math; + +template +class OrbitManipulator : public Manipulator { +public: + using vec2 = filament::math::vec2; + using vec3 = filament::math::vec3; + using vec4 = filament::math::vec4; + using Bookmark = filament::camutils::Bookmark; + using Base = Manipulator; + using Config = typename Base::Config; + + enum GrabState { INACTIVE, ORBITING, PANNING }; + + OrbitManipulator(Mode mode, const Config& props) : Base(mode, props) { + setProperties(props); + Base::mEye = Base::mProps.orbitHomePosition; + mPivot = Base::mTarget = Base::mProps.targetPosition; + } + + void setProperties(const Config& props) override { + Config resolved = props; + + if (resolved.orbitHomePosition == vec3(0)) { + resolved.orbitHomePosition = vec3(0, 0, 1); + } + + if (resolved.orbitSpeed == vec2(0)) { + resolved.orbitSpeed = vec2(0.01); + } + + // By default, place the ground plane so that it aligns with the targetPosition position. + // This is used only when PANNING. + if (resolved.groundPlane == vec4(0)) { + const FLOAT d = length(resolved.targetPosition); + const vec3 n = normalize(resolved.orbitHomePosition - resolved.targetPosition); + resolved.groundPlane = vec4(n, -d); + } + + Base::setProperties(resolved); + } + + void grabBegin(int x, int y, bool strafe) override { + mGrabState = strafe ? PANNING : ORBITING; + mGrabPivot = mPivot; + mGrabEye = Base::mEye; + mGrabTarget = Base::mTarget; + mGrabBookmark = getCurrentBookmark(); + mGrabWinX = x; + mGrabWinY = y; + mGrabFar = Base::raycastFarPlane(x, y); + Base::raycast(x, y, &mGrabScene); + } + + void grabUpdate(int x, int y) override { + const int delx = mGrabWinX - x; + const int dely = mGrabWinY - y; + + if (mGrabState == ORBITING) { + Bookmark bookmark = getCurrentBookmark(); + + const FLOAT theta = delx * Base::mProps.orbitSpeed.x; + const FLOAT phi = dely * Base::mProps.orbitSpeed.y; + const FLOAT maxPhi = MAX_PHI; + + bookmark.orbit.phi = clamp(mGrabBookmark.orbit.phi + phi, -maxPhi, +maxPhi); + bookmark.orbit.theta = mGrabBookmark.orbit.theta + theta; + + jumpToBookmark(bookmark); + } + + if (mGrabState == PANNING) { + const FLOAT ulen = distance(mGrabScene, mGrabEye); + const FLOAT vlen = distance(mGrabFar, mGrabScene); + const vec3 translation = (mGrabFar - Base::raycastFarPlane(x, y)) * ulen / vlen; + mPivot = mGrabPivot + translation; + Base::mEye = mGrabEye + translation; + Base::mTarget = mGrabTarget + translation; + } + } + + void grabEnd() override { + mGrabState = INACTIVE; + } + + void scroll(int x, int y, FLOAT scrolldelta) override { + const vec3 gaze = normalize(Base::mTarget - Base::mEye); + const vec3 movement = gaze * Base::mProps.zoomSpeed * -scrolldelta; + const vec3 v0 = mPivot - Base::mEye; + Base::mEye += movement; + Base::mTarget += movement; + const vec3 v1 = mPivot - Base::mEye; + + // Check if the camera has moved past the point of interest. + if (dot(v0, v1) < 0) { + mFlipped = !mFlipped; + } + } + + Bookmark getCurrentBookmark() const override { + Bookmark bookmark; + bookmark.mode = Mode::ORBIT; + const vec3 pivotToEye = Base::mEye - mPivot; + const FLOAT d = length(pivotToEye); + const FLOAT x = pivotToEye.x / d; + const FLOAT y = pivotToEye.y / d; + const FLOAT z = pivotToEye.z / d; + + bookmark.orbit.phi = asin(y); + bookmark.orbit.theta = atan2(x, z); + bookmark.orbit.distance = mFlipped ? -d : d; + bookmark.orbit.pivot = mPivot; + + const FLOAT fov = Base::mProps.fovDegrees * math::F_PI / 180.0; + const FLOAT halfExtent = d * tan(fov / 2.0); + const vec3 targetToEye = Base::mProps.groundPlane.xyz; + const vec3 uvec = cross(Base::mProps.upVector, targetToEye); + const vec3 vvec = cross(targetToEye, uvec); + const vec3 centerToTarget = mPivot - Base::mProps.targetPosition; + + bookmark.map.extent = halfExtent * 2; + bookmark.map.center.x = dot(uvec, centerToTarget); + bookmark.map.center.y = dot(vvec, centerToTarget); + + return bookmark; + } + + Bookmark getHomeBookmark() const override { + Bookmark bookmark; + bookmark.mode = Mode::ORBIT; + bookmark.orbit.phi = FLOAT(0); + bookmark.orbit.theta = FLOAT(0); + bookmark.orbit.pivot = Base::mProps.targetPosition; + bookmark.orbit.distance = distance(Base::mProps.targetPosition, Base::mProps.orbitHomePosition); + + const FLOAT fov = Base::mProps.fovDegrees * math::F_PI / 180.0; + const FLOAT halfExtent = bookmark.orbit.distance * tan(fov / 2.0); + + bookmark.map.extent = halfExtent * 2; + bookmark.map.center.x = 0; + bookmark.map.center.y = 0; + + return bookmark; + } + + void jumpToBookmark(const Bookmark& bookmark) override { + mPivot = bookmark.orbit.pivot; + const FLOAT x = sin(bookmark.orbit.theta) * cos(bookmark.orbit.phi); + const FLOAT y = sin(bookmark.orbit.phi); + const FLOAT z = cos(bookmark.orbit.theta) * cos(bookmark.orbit.phi); + Base::mEye = mPivot + vec3(x, y, z) * abs(bookmark.orbit.distance); + mFlipped = bookmark.orbit.distance < 0; + Base::mTarget = Base::mEye + vec3(x, y, z) * (mFlipped ? 1.0 : -1.0); + } + +private: + GrabState mGrabState = INACTIVE; + bool mFlipped = false; + vec3 mGrabPivot; + vec3 mGrabScene; + vec3 mGrabFar; + vec3 mGrabEye; + vec3 mGrabTarget; + Bookmark mGrabBookmark; + int mGrabWinX; + int mGrabWinY; + vec3 mPivot; +}; + +} // namespace camutils +} // namespace filament + +#endif /* CAMUTILS_ORBIT_MANIPULATOR_H */ diff --git a/lib/filament_controller.dart b/lib/filament_controller.dart index 435b97a8..a54a4842 100644 --- a/lib/filament_controller.dart +++ b/lib/filament_controller.dart @@ -197,7 +197,7 @@ abstract class FilamentController { /// /// Called by `FilamentGestureDetector`. You probably don't want to call this yourself. /// - Future zoomUpdate(double z); + Future zoomUpdate(double x, double y, double z); /// /// Called by `FilamentGestureDetector`. You probably don't want to call this yourself. diff --git a/lib/filament_controller_ffi.dart b/lib/filament_controller_ffi.dart index bca3a6ac..22a5b7e6 100644 --- a/lib/filament_controller_ffi.dart +++ b/lib/filament_controller_ffi.dart @@ -191,16 +191,45 @@ class FilamentControllerFFI extends FilamentController { _isReadyForScene.complete(true); } + /// + /// I'm not exactly sure how to resize the backing textures on all platforms. + /// So for now, I'm sticking with the safe option when the widget is resized: destroying the swapchain, recreating the textures, and creating a new swapchain. + /// @override Future resize(int width, int height, {double scaleFactor = 1.0}) async { _resizing = true; setRendering(false); + _lib.destroy_swap_chain(_viewer!); + await destroyTexture(); size = ui.Size(width * _pixelRatio, height * _pixelRatio); - _textureId = await _channel - .invokeMethod("resize", [size.width, size.height, scaleFactor]); - _textureIdController.add(_textureId); + + var textures = + await _channel.invokeMethod("createTexture", [size.width, size.height]); + var flutterTextureId = textures[0]; + _textureId = flutterTextureId; + + // void* on iOS (pointer to pixel buffer), void* on Android (pointer to native window), null on Windows/macOS + var surfaceAddress = textures[1] as int? ?? 0; + + // null on iOS/Android, void* on MacOS (pointer to metal texture), GLuid on Windows/Linux + var nativeTexture = textures[2] as int? ?? 0; + + _lib.create_swap_chain_ffi( + _viewer!, + Pointer.fromAddress(surfaceAddress), + size.width.toInt(), + size.height.toInt()); + if (nativeTexture != 0) { + assert(surfaceAddress == 0); + print("Creating render target from native texture $nativeTexture"); + _lib.create_render_target_ffi( + _viewer!, nativeTexture, size.width.toInt(), size.height.toInt()); + } + _lib.update_viewport_and_camera_projection_ffi( - _viewer!, size.width.toInt(), size.height.toInt(), scaleFactor); + _viewer!, size.width.toInt(), size.height.toInt(), 1.0); + + _textureIdController.add(_textureId); _resizing = false; setRendering(true); } @@ -553,11 +582,11 @@ class FilamentControllerFFI extends FilamentController { } @override - Future zoomUpdate(double z) async { + Future zoomUpdate(double x, double y, double z) async { if (_viewer == null || _resizing) { throw Exception("No viewer available, ignoring"); } - _lib.scroll_update(_viewer!, 0.0, 0.0, z); + _lib.scroll_update(_viewer!, x, y, z); } @override diff --git a/lib/filament_controller_method_channel.dart b/lib/filament_controller_method_channel.dart index 28ba9e30..92cc7293 100644 --- a/lib/filament_controller_method_channel.dart +++ b/lib/filament_controller_method_channel.dart @@ -434,11 +434,11 @@ class FilamentControllerMethodChannel extends FilamentController { await _channel.invokeMethod("scrollBegin"); } - Future zoomUpdate(double z) async { + Future zoomUpdate(double x, double y, double z) async { if (_viewer == null || _resizing) { throw Exception("No viewer available, ignoring"); } - await _channel.invokeMethod("scrollUpdate", [0.0, 0.0, z]); + await _channel.invokeMethod("scrollUpdate", [x, y, z]); } Future zoomEnd() async { diff --git a/lib/widgets/filament_gesture_detector.dart b/lib/widgets/filament_gesture_detector.dart index 2ca7b6df..f0197fb9 100644 --- a/lib/widgets/filament_gesture_detector.dart +++ b/lib/widgets/filament_gesture_detector.dart @@ -38,15 +38,12 @@ class FilamentGestureDetector extends StatelessWidget { /// final bool listenerEnabled; - final double zoomDelta; - const FilamentGestureDetector( {Key? key, required this.controller, this.child, this.showControlOverlay = false, - this.listenerEnabled = true, - this.zoomDelta = 1}) + this.listenerEnabled = true}) : super(key: key); @override diff --git a/lib/widgets/filament_gesture_detector_desktop.dart b/lib/widgets/filament_gesture_detector_desktop.dart index 94cddc79..a2088313 100644 --- a/lib/widgets/filament_gesture_detector_desktop.dart +++ b/lib/widgets/filament_gesture_detector_desktop.dart @@ -32,15 +32,12 @@ class FilamentGestureDetectorDesktop extends StatefulWidget { /// final bool listenerEnabled; - final double zoomDelta; - const FilamentGestureDetectorDesktop( {Key? key, required this.controller, this.child, this.showControlOverlay = false, - this.listenerEnabled = true, - this.zoomDelta = 1}) + this.listenerEnabled = true}) : super(key: key); @override @@ -71,15 +68,18 @@ class _FilamentGestureDetectorDesktopState /// /// Scroll-wheel on desktop, interpreted as zoom /// - void _zoom(PointerScrollEvent pointerSignal) { + void _zoom(PointerScrollEvent pointerSignal) async { _scrollTimer?.cancel(); - widget.controller.zoomBegin(); - widget.controller.zoomUpdate(pointerSignal.scrollDelta.dy > 0 - ? widget.zoomDelta - : -widget.zoomDelta); - _scrollTimer = Timer(const Duration(milliseconds: 100), () { - widget.controller.zoomEnd(); - _scrollTimer = null; + await widget.controller.zoomBegin(); + await widget.controller.zoomUpdate( + pointerSignal.localPosition.dx, + pointerSignal.localPosition.dy, + pointerSignal.scrollDelta.dy > 0 ? 1 : -1); + + // we don't want to end the zoom in the same frame, because this will destroy the camera manipulator (and cancel the zoom update). + // here, we just defer calling [zoomEnd] for 100ms to ensure the update is propagated through. + _scrollTimer = Timer(Duration(milliseconds: 100), () async { + await widget.controller.zoomEnd(); }); } @@ -108,7 +108,8 @@ class _FilamentGestureDetectorDesktopState // if this is the first move event, we need to call rotateStart/panStart to set the first coordinates if (!_pointerMoving) { if (d.buttons == kTertiaryButton) { - widget.controller.rotateStart(d.position.dx, d.position.dy); + widget.controller + .rotateStart(d.localPosition.dx, d.localPosition.dy); } else { widget.controller .panStart(d.localPosition.dx, d.localPosition.dy); @@ -117,7 +118,8 @@ class _FilamentGestureDetectorDesktopState // set the _pointerMoving flag so we don't call rotateStart/panStart on future move events _pointerMoving = true; if (d.buttons == kTertiaryButton) { - widget.controller.rotateUpdate(d.position.dx, d.position.dy); + widget.controller + .rotateUpdate(d.localPosition.dx, d.localPosition.dy); } else { widget.controller.panUpdate(d.localPosition.dx, d.localPosition.dy); } diff --git a/lib/widgets/filament_gesture_detector_mobile.dart b/lib/widgets/filament_gesture_detector_mobile.dart index 1d3f8505..4d1b1072 100644 --- a/lib/widgets/filament_gesture_detector_mobile.dart +++ b/lib/widgets/filament_gesture_detector_mobile.dart @@ -158,8 +158,8 @@ class _FilamentGestureDetectorMobileState }, onScaleUpdate: (ScaleUpdateDetails d) async { if (d.pointerCount == 2) { - widget.controller - .zoomUpdate(d.horizontalScale > 1 ? 0.1 : -0.1); + widget.controller.zoomUpdate(d.localFocalPoint.dx, + d.localFocalPoint.dy, d.horizontalScale > 1 ? 0.1 : -0.1); } else if (!_scaling) { if (_rotateOnPointerMove) { widget.controller diff --git a/macos/Classes/SwiftPolyvoxFilamentPlugin.swift b/macos/Classes/SwiftPolyvoxFilamentPlugin.swift index 4d364fc0..0ac688d7 100644 --- a/macos/Classes/SwiftPolyvoxFilamentPlugin.swift +++ b/macos/Classes/SwiftPolyvoxFilamentPlugin.swift @@ -64,7 +64,9 @@ public class SwiftPolyvoxFilamentPlugin: NSObject, FlutterPlugin, FlutterTexture var markTextureFrameAvailable : @convention(c) (UnsafeMutableRawPointer?) -> () = { instancePtr in let instance:SwiftPolyvoxFilamentPlugin = Unmanaged.fromOpaque(instancePtr!).takeUnretainedValue() - instance.registry.textureFrameAvailable(instance.flutterTextureId!) + if(instance.flutterTextureId != nil) { + instance.registry.textureFrameAvailable(instance.flutterTextureId!) + } } public func copyPixelBuffer() -> Unmanaged? { @@ -142,6 +144,10 @@ public class SwiftPolyvoxFilamentPlugin: NSObject, FlutterPlugin, FlutterTexture Int(width), Int(height), 0, &cvMetalTexture); + if(cvret != 0) { + result(FlutterError()) + return + } metalTexture = CVMetalTextureGetTexture(cvMetalTexture!); let pixelBufferPtr = CVPixelBufferGetBaseAddress(pixelBuffer!); let pixelBufferAddress = Int(bitPattern:pixelBufferPtr); @@ -160,13 +166,7 @@ public class SwiftPolyvoxFilamentPlugin: NSObject, FlutterPlugin, FlutterTexture let args = call.arguments as! [Any] let width = UInt32(args[0] as! Int64) let height = UInt32(args[1] as! Int64) - if(self.flutterTextureId != nil) { - self.registry.unregisterTexture(self.flutterTextureId!) - } - createPixelBuffer(width: Int(width), height:Int(height)) - let metalTextureId = Int(bitPattern:Unmanaged.passUnretained(metalTexture!).toOpaque()) - print("Resized to \(args[0])x\(args[1])") - result(self.flutterTextureId); + result(FlutterMethodNotImplemented) default: result(FlutterMethodNotImplemented) } diff --git a/macos/include/FilamentViewer.hpp b/macos/include/FilamentViewer.hpp index c5cff857..d5839605 100644 --- a/macos/include/FilamentViewer.hpp +++ b/macos/include/FilamentViewer.hpp @@ -177,7 +177,7 @@ namespace polyvox { void loadPngTexture(string path, ResourceBuffer data); void loadTextureFromPath(string path); - + Manipulator* _manipulator = nullptr; void _createManipulator(); uint32_t _lastFrameTimeInNanos; }; diff --git a/macos/polyvox_filament.podspec b/macos/polyvox_filament.podspec index b92c129f..b1859547 100644 --- a/macos/polyvox_filament.podspec +++ b/macos/polyvox_filament.podspec @@ -14,7 +14,7 @@ A new Flutter plugin project. s.author = { 'Your Company' => 'email@example.com' } s.source = { :path => '.' } - s.source_files = 'Classes/*', 'src/*', 'include/filament/*', 'include/*', 'include/material/*.c' + s.source_files = 'Classes/*', 'src/*', "src/camutils/*", 'include/filament/*', 'include/*', 'include/material/*.c' s.public_header_files = 'include/SwiftPolyvoxFilamentPlugin-Bridging-Header.h', 'include/PolyvoxFilamentApi.h', 'include/PolyvoxFilamentFFIApi.h', 'include/ResourceBuffer.hpp' #, 'include/filament/*' s.dependency 'FlutterMacOS' @@ -38,7 +38,7 @@ A new Flutter plugin project. 'OTHER_CFLAGS' => '"-fvisibility=default" "$(inherited)"', 'USER_HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}/include" "${PODS_TARGET_SRCROOT}/include/filament" "$(inherited)"', 'ALWAYS_SEARCH_USER_PATHS' => 'YES', - "OTHER_LDFLAGS" => '-lfilament -lbackend -lfilameshio -lviewer -lfilamat -lgeometry -lutils -lfilabridge -lgltfio_core -lfilament-iblprefilter -limage -limageio -ltinyexr -lcamutils -lgltfio_core -lfilaflat -ldracodec -libl -lktxreader -lpng -lz -lstb -luberzlib -lsmol-v -luberarchive -lzstd -lvkshaders -lbluegl -lbluevk -lbasis_transcoder -lmeshoptimizer', + "OTHER_LDFLAGS" => '-lfilament -lbackend -lfilameshio -lviewer -lfilamat -lgeometry -lutils -lfilabridge -lgltfio_core -lfilament-iblprefilter -limage -limageio -ltinyexr -lgltfio_core -lfilaflat -ldracodec -libl -lktxreader -lpng -lz -lstb -luberzlib -lsmol-v -luberarchive -lzstd -lvkshaders -lbluegl -lbluevk -lbasis_transcoder -lmeshoptimizer', 'LIBRARY_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}/lib" "$(inherited)"', } s.swift_version = '5.0' diff --git a/macos/src/FilamentViewer.cpp b/macos/src/FilamentViewer.cpp index 30cc7f00..b1c64a01 100644 --- a/macos/src/FilamentViewer.cpp +++ b/macos/src/FilamentViewer.cpp @@ -1,5 +1,5 @@ #if __APPLE__ - #include "TargetConditionals.h" +#include "TargetConditionals.h" #endif #ifdef _WIN32 @@ -22,6 +22,7 @@ * limitations under the License. */ #include + #include #include #include @@ -86,185 +87,195 @@ using namespace gltfio; using namespace utils; using namespace image; -namespace filament { -class IndirectLight; -class LightManager; +namespace filament +{ + class IndirectLight; + class LightManager; } // namespace filament -namespace polyvox { - -const double kNearPlane = 0.05; // 5 cm -const double kFarPlane = 1000.0; // 1 km +namespace polyvox +{ -// const float kAperture = 1.0f; -// const float kShutterSpeed = 1.0f; -// const float kSensitivity = 50.0f; -struct Vertex { + const double kNearPlane = 0.05; // 5 cm + const double kFarPlane = 1000.0; // 1 km + + // const float kAperture = 1.0f; + // const float kShutterSpeed = 1.0f; + // const float kSensitivity = 50.0f; + struct Vertex + { filament::math::float2 position; uint32_t color; -}; + }; -static constexpr float4 sFullScreenTriangleVertices[3] = { - { -1.0f, -1.0f, 1.0f, 1.0f }, - { 3.0f, -1.0f, 1.0f, 1.0f }, - { -1.0f, 3.0f, 1.0f, 1.0f } }; + static constexpr float4 sFullScreenTriangleVertices[3] = { + {-1.0f, -1.0f, 1.0f, 1.0f}, + {3.0f, -1.0f, 1.0f, 1.0f}, + {-1.0f, 3.0f, 1.0f, 1.0f}}; -static const uint16_t sFullScreenTriangleIndices[3] = {0, 1, 2}; + static const uint16_t sFullScreenTriangleIndices[3] = {0, 1, 2}; -FilamentViewer::FilamentViewer(const void* sharedContext, const ResourceLoaderWrapper* const resourceLoaderWrapper, void* const platform, const char* uberArchivePath) - : _resourceLoaderWrapper(resourceLoaderWrapper) { + FilamentViewer::FilamentViewer(const void *sharedContext, const ResourceLoaderWrapper *const resourceLoaderWrapper, void *const platform, const char *uberArchivePath) + : _resourceLoaderWrapper(resourceLoaderWrapper) + { - ASSERT_POSTCONDITION(_resourceLoaderWrapper != nullptr, "Resource loader must be non-null"); - - #if TARGET_OS_IPHONE + ASSERT_POSTCONDITION(_resourceLoaderWrapper != nullptr, "Resource loader must be non-null"); + +#if TARGET_OS_IPHONE ASSERT_POSTCONDITION(platform == nullptr, "Custom Platform not supported on iOS"); _engine = Engine::create(Engine::Backend::METAL); - #elif TARGET_OS_OSX - ASSERT_POSTCONDITION(platform == nullptr, "Custom Platform not supported on macOS"); - _engine = Engine::create(Engine::Backend::METAL); - #else - _engine = Engine::create(Engine::Backend::OPENGL, (backend::Platform*)platform, (void*)sharedContext, nullptr); - #endif +#elif TARGET_OS_OSX + ASSERT_POSTCONDITION(platform == nullptr, "Custom Platform not supported on macOS"); + _engine = Engine::create(Engine::Backend::METAL); +#else + _engine = Engine::create(Engine::Backend::OPENGL, (backend::Platform *)platform, (void *)sharedContext, nullptr); +#endif - _renderer = _engine->createRenderer(); + _renderer = _engine->createRenderer(); - float fr = 60.0f; - _renderer->setDisplayInfo({.refreshRate = fr}); + float fr = 60.0f; + _renderer->setDisplayInfo({.refreshRate = fr}); - Renderer::FrameRateOptions fro; - fro.interval = 1 / fr; - _renderer->setFrameRateOptions(fro); + Renderer::FrameRateOptions fro; + fro.interval = 1 / fr; + _renderer->setFrameRateOptions(fro); - _scene = _engine->createScene(); + _scene = _engine->createScene(); - Log("Scene created"); - - utils::Entity camera = EntityManager::get().create(); + Log("Scene created"); - _mainCamera = _engine->createCamera(camera); + utils::Entity camera = EntityManager::get().create(); - Log("Main camera created"); - _view = _engine->createView(); - Log("View created"); + _mainCamera = _engine->createCamera(camera); - setToneMapping(ToneMapping::ACES); + Log("Main camera created"); + _view = _engine->createView(); + Log("View created"); - Log("Set tone mapping"); + setToneMapping(ToneMapping::ACES); - setBloom(0.6f); - Log("Set bloom"); + Log("Set tone mapping"); - _view->setScene(_scene); - _view->setCamera(_mainCamera); + setBloom(0.6f); + Log("Set bloom"); - _cameraFocalLength = 28.0f; - _mainCamera->setLensProjection(_cameraFocalLength, 1.0f, kNearPlane, - kFarPlane); - // _mainCamera->setExposure(kAperture, kShutterSpeed, kSensitivity); - Log("View created"); - const float aperture = _mainCamera->getAperture(); - const float shutterSpeed = _mainCamera->getShutterSpeed(); - const float sens = _mainCamera->getSensitivity(); + _view->setScene(_scene); + _view->setCamera(_mainCamera); - Log("Camera aperture %f shutter %f sensitivity %f", aperture, shutterSpeed, sens); + _cameraFocalLength = 28.0f; + _mainCamera->setLensProjection(_cameraFocalLength, 1.0f, kNearPlane, + kFarPlane); + // _mainCamera->setExposure(kAperture, kShutterSpeed, kSensitivity); + Log("View created"); + const float aperture = _mainCamera->getAperture(); + const float shutterSpeed = _mainCamera->getShutterSpeed(); + const float sens = _mainCamera->getSensitivity(); - View::DynamicResolutionOptions options; - options.enabled = false; - // options.homogeneousScaling = homogeneousScaling; - // options.minScale = filament::math::float2{ minScale }; - // options.maxScale = filament::math::float2{ maxScale }; - // options.sharpness = sharpness; - // options.quality = View::QualityLevel::ULTRA; - _view->setDynamicResolutionOptions(options); + Log("Camera aperture %f shutter %f sensitivity %f", aperture, shutterSpeed, sens); - View::MultiSampleAntiAliasingOptions multiSampleAntiAliasingOptions; - multiSampleAntiAliasingOptions.enabled = true; + View::DynamicResolutionOptions options; + options.enabled = false; + // options.homogeneousScaling = homogeneousScaling; + // options.minScale = filament::math::float2{ minScale }; + // options.maxScale = filament::math::float2{ maxScale }; + // options.sharpness = sharpness; + // options.quality = View::QualityLevel::ULTRA; + _view->setDynamicResolutionOptions(options); - _view->setMultiSampleAntiAliasingOptions(multiSampleAntiAliasingOptions); + View::MultiSampleAntiAliasingOptions multiSampleAntiAliasingOptions; + multiSampleAntiAliasingOptions.enabled = true; - _view->setAntiAliasing(AntiAliasing::NONE); + _view->setMultiSampleAntiAliasingOptions(multiSampleAntiAliasingOptions); - EntityManager &em = EntityManager::get(); + _view->setAntiAliasing(AntiAliasing::NONE); - _ncm = new NameComponentManager(em); + EntityManager &em = EntityManager::get(); - _assetManager = new AssetManager( - _resourceLoaderWrapper, - _ncm, - _engine, - _scene, - uberArchivePath - ); - - _imageTexture = Texture::Builder() - .width(1) - .height(1) - .levels(0x01) - .format(Texture::InternalFormat::RGB16F) - .sampler(Texture::Sampler::SAMPLER_2D) - .build(*_engine); - try { - _imageMaterial = - Material::Builder() - .package(IMAGE_IMAGE_DATA, IMAGE_IMAGE_SIZE) - .build(*_engine); - _imageMaterial->setDefaultParameter("showImage",0); - _imageMaterial->setDefaultParameter("backgroundColor", RgbaType::sRGB, float4(0.5f, 0.5f, 0.5f, 1.0f)); - _imageMaterial->setDefaultParameter("image", _imageTexture, _imageSampler); - } catch(...) { - Log("Failed to load background image material provider"); - std::rethrow_exception(std::current_exception()); + _ncm = new NameComponentManager(em); + + _assetManager = new AssetManager( + _resourceLoaderWrapper, + _ncm, + _engine, + _scene, + uberArchivePath); + + _imageTexture = Texture::Builder() + .width(1) + .height(1) + .levels(0x01) + .format(Texture::InternalFormat::RGB16F) + .sampler(Texture::Sampler::SAMPLER_2D) + .build(*_engine); + try + { + _imageMaterial = + Material::Builder() + .package(IMAGE_IMAGE_DATA, IMAGE_IMAGE_SIZE) + .build(*_engine); + _imageMaterial->setDefaultParameter("showImage", 0); + _imageMaterial->setDefaultParameter("backgroundColor", RgbaType::sRGB, float4(0.5f, 0.5f, 0.5f, 1.0f)); + _imageMaterial->setDefaultParameter("image", _imageTexture, _imageSampler); + } + catch (...) + { + Log("Failed to load background image material provider"); + std::rethrow_exception(std::current_exception()); + } + _imageScale = mat4f{1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}; + + _imageMaterial->setDefaultParameter("transform", _imageScale); + + _imageVb = VertexBuffer::Builder() + .vertexCount(3) + .bufferCount(1) + .attribute(VertexAttribute::POSITION, 0, + VertexBuffer::AttributeType::FLOAT4, 0) + .build(*_engine); + + _imageVb->setBufferAt( + *_engine, 0, + {sFullScreenTriangleVertices, sizeof(sFullScreenTriangleVertices)}); + + _imageIb = IndexBuffer::Builder() + .indexCount(3) + .bufferType(IndexBuffer::IndexType::USHORT) + .build(*_engine); + + _imageIb->setBuffer(*_engine, {sFullScreenTriangleIndices, + sizeof(sFullScreenTriangleIndices)}); + + utils::Entity imageEntity = em.create(); + RenderableManager::Builder(1) + .boundingBox({{}, {1.0f, 1.0f, 1.0f}}) + .material(0, _imageMaterial->getDefaultInstance()) + .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, _imageVb, + _imageIb, 0, 3) + .culling(false) + .build(*_engine, imageEntity); + _imageEntity = &imageEntity; + _scene->addEntity(imageEntity); } - _imageScale = mat4f { 1.0f , 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; - _imageMaterial->setDefaultParameter("transform", _imageScale); + void FilamentViewer::setPostProcessing(bool enabled) + { + _view->setPostProcessingEnabled(enabled); + } - _imageVb = VertexBuffer::Builder() - .vertexCount(3) - .bufferCount(1) - .attribute(VertexAttribute::POSITION, 0, - VertexBuffer::AttributeType::FLOAT4, 0) - .build(*_engine); + void FilamentViewer::setBloom(float strength) + { + decltype(_view->getBloomOptions()) opts; + opts.enabled = true; + opts.strength = strength; + _view->setBloomOptions(opts); + } - _imageVb->setBufferAt( - *_engine, 0, - {sFullScreenTriangleVertices, sizeof(sFullScreenTriangleVertices)}); + void FilamentViewer::setToneMapping(ToneMapping toneMapping) + { - _imageIb = IndexBuffer::Builder() - .indexCount(3) - .bufferType(IndexBuffer::IndexType::USHORT) - .build(*_engine); - - _imageIb->setBuffer(*_engine, {sFullScreenTriangleIndices, - sizeof(sFullScreenTriangleIndices)}); - - utils::Entity imageEntity = em.create(); - RenderableManager::Builder(1) - .boundingBox({{}, {1.0f, 1.0f, 1.0f}}) - .material(0, _imageMaterial->getDefaultInstance()) - .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, _imageVb, - _imageIb, 0, 3) - .culling(false) - .build(*_engine, imageEntity); - _imageEntity = &imageEntity; - _scene->addEntity(imageEntity); -} - -void FilamentViewer::setPostProcessing(bool enabled) { - _view->setPostProcessingEnabled(enabled); -} - -void FilamentViewer::setBloom(float strength) { - decltype(_view->getBloomOptions()) opts; - opts.enabled = true; - opts.strength = strength; - _view->setBloomOptions(opts); -} - -void FilamentViewer::setToneMapping(ToneMapping toneMapping) { - - ToneMapper* tm; - switch(toneMapping) { + ToneMapper *tm; + switch (toneMapping) + { case ToneMapping::ACES: Log("Setting tone mapping to ACES"); tm = new ACESToneMapper(); @@ -280,474 +291,529 @@ void FilamentViewer::setToneMapping(ToneMapping toneMapping) { default: Log("ERROR: Unsupported tone mapping"); return; + } + + auto newColorGrading = ColorGrading::Builder().toneMapper(tm).build(*_engine); + _view->setColorGrading(newColorGrading); + _engine->destroy(colorGrading); + delete tm; } - - auto newColorGrading = ColorGrading::Builder().toneMapper(tm).build(*_engine); - _view->setColorGrading(newColorGrading); - _engine->destroy(colorGrading); - delete tm; -} - -void FilamentViewer::setFrameInterval(float frameInterval) { - Renderer::FrameRateOptions fro; - fro.interval = frameInterval; - _renderer->setFrameRateOptions(fro); - Log("Set framerate interval to %f", frameInterval); -} - -int32_t FilamentViewer::addLight(LightManager::Type t, float colour, float intensity, float posX, float posY, float posZ, float dirX, float dirY, float dirZ, bool shadows) { - auto light = EntityManager::get().create(); - LightManager::Builder(t) - .color(Color::cct(colour)) - .intensity(intensity) - .position(math::float3(posX, posY, posZ)) - .direction(math::float3(dirX, dirY, dirZ)) - .castShadows(shadows) - .build(*_engine, light); - _scene->addEntity(light); - _lights.push_back(light); - auto entityId = Entity::smuggle(light); - Log("Added light under entity ID %d of type %d with colour %f intensity %f at (%f, %f, %f) with direction (%f, %f, %f) with shadows %d", entityId, t, colour, intensity, posX, posY, posZ, dirX, dirY, dirZ, shadows); - return entityId; -} - -void FilamentViewer::removeLight(EntityId entityId) { - Log("Removing light with entity ID %d", entityId); - auto entity = utils::Entity::import(entityId); - if(entity.isNull()) { - Log("Error: light entity not found under ID %d", entityId); - } else { - remove(_lights.begin(), _lights.end(), entity); - _scene->remove(entity); - EntityManager::get().destroy(1, &entity); + void FilamentViewer::setFrameInterval(float frameInterval) + { + Renderer::FrameRateOptions fro; + fro.interval = frameInterval; + _renderer->setFrameRateOptions(fro); + Log("Set framerate interval to %f", frameInterval); } -} -void FilamentViewer::clearLights() { - Log("Removing all lights"); - _scene->removeEntities(_lights.data(), _lights.size()); - EntityManager::get().destroy(_lights.size(), _lights.data()); - _lights.clear(); -} + int32_t FilamentViewer::addLight(LightManager::Type t, float colour, float intensity, float posX, float posY, float posZ, float dirX, float dirY, float dirZ, bool shadows) + { + auto light = EntityManager::get().create(); + LightManager::Builder(t) + .color(Color::cct(colour)) + .intensity(intensity) + .position(math::float3(posX, posY, posZ)) + .direction(math::float3(dirX, dirY, dirZ)) + .castShadows(shadows) + .build(*_engine, light); + _scene->addEntity(light); + _lights.push_back(light); + auto entityId = Entity::smuggle(light); + Log("Added light under entity ID %d of type %d with colour %f intensity %f at (%f, %f, %f) with direction (%f, %f, %f) with shadows %d", entityId, t, colour, intensity, posX, posY, posZ, dirX, dirY, dirZ, shadows); + return entityId; + } -static bool endsWith(string path, string ending) { - return path.compare(path.length() - ending.length(), ending.length(), ending) == 0; -} + void FilamentViewer::removeLight(EntityId entityId) + { + Log("Removing light with entity ID %d", entityId); + auto entity = utils::Entity::import(entityId); + if (entity.isNull()) + { + Log("Error: light entity not found under ID %d", entityId); + } + else + { + remove(_lights.begin(), _lights.end(), entity); + _scene->remove(entity); + EntityManager::get().destroy(1, &entity); + } + } -void FilamentViewer::loadKtx2Texture(string path, ResourceBuffer rb) { + void FilamentViewer::clearLights() + { + Log("Removing all lights"); + _scene->removeEntities(_lights.data(), _lights.size()); + EntityManager::get().destroy(_lights.size(), _lights.data()); + _lights.clear(); + } - // TODO - check all this + static bool endsWith(string path, string ending) + { + return path.compare(path.length() - ending.length(), ending.length(), ending) == 0; + } - // ktxreader::Ktx2Reader reader(*_engine); + void FilamentViewer::loadKtx2Texture(string path, ResourceBuffer rb) + { - // reader.requestFormat(Texture::InternalFormat::DXT3_SRGBA); - // reader.requestFormat(Texture::InternalFormat::DXT3_RGBA); + // TODO - check all this - // // Uncompressed formats are lower priority, so they get added last. - // reader.requestFormat(Texture::InternalFormat::SRGB8_A8); - // reader.requestFormat(Texture::InternalFormat::RGBA8); + // ktxreader::Ktx2Reader reader(*_engine); - // // std::ifstream inputStream("/data/data/app.polyvox.filament_example/foo.ktx", ios::binary); + // reader.requestFormat(Texture::InternalFormat::DXT3_SRGBA); + // reader.requestFormat(Texture::InternalFormat::DXT3_RGBA); - // // auto contents = vector((istreambuf_iterator(inputStream)), {}); + // // Uncompressed formats are lower priority, so they get added last. + // reader.requestFormat(Texture::InternalFormat::SRGB8_A8); + // reader.requestFormat(Texture::InternalFormat::RGBA8); - // _imageTexture = reader.load(contents.data(), contents.size(), - // ktxreader::Ktx2Reader::TransferFunction::LINEAR); -} + // // std::ifstream inputStream("/data/data/app.polyvox.filament_example/foo.ktx", ios::binary); -void FilamentViewer::loadKtxTexture(string path, ResourceBuffer rb) { + // // auto contents = vector((istreambuf_iterator(inputStream)), {}); + + // _imageTexture = reader.load(contents.data(), contents.size(), + // ktxreader::Ktx2Reader::TransferFunction::LINEAR); + } + + void FilamentViewer::loadKtxTexture(string path, ResourceBuffer rb) + { ktxreader::Ktx1Bundle *bundle = - new ktxreader::Ktx1Bundle(static_cast(rb.data), - static_cast(rb.size)); + new ktxreader::Ktx1Bundle(static_cast(rb.data), + static_cast(rb.size)); - // because the ResourceBuffer will go out of scope before the texture callback is invoked, we need to make a copy to the heap - ResourceBuffer* rbCopy = new ResourceBuffer(rb); + ResourceBuffer *rbCopy = new ResourceBuffer(rb); - std::vector* callbackData = new std::vector { (void*)_resourceLoaderWrapper, rbCopy }; - + std::vector *callbackData = new std::vector{(void *)_resourceLoaderWrapper, rbCopy}; _imageTexture = - ktxreader::Ktx1Reader::createTexture(_engine, *bundle, false, [](void* userdata) { + ktxreader::Ktx1Reader::createTexture( + _engine, *bundle, false, [](void *userdata) + { std::vector* vec = (std::vector*)userdata; ResourceLoaderWrapper* loader = (ResourceLoaderWrapper*)vec->at(0); ResourceBuffer* rb = (ResourceBuffer*) vec->at(1); loader->free(*rb); delete rb; - delete vec; - }, callbackData); + delete vec; }, + callbackData); auto info = bundle->getInfo(); _imageWidth = info.pixelWidth; _imageHeight = info.pixelHeight; -} + } -void FilamentViewer::loadPngTexture(string path, ResourceBuffer rb) { - - polyvox::StreamBufferAdapter sb((char *)rb.data, (char *)rb.data + rb.size); + void FilamentViewer::loadPngTexture(string path, ResourceBuffer rb) + { - std::istream inputStream(&sb); + polyvox::StreamBufferAdapter sb((char *)rb.data, (char *)rb.data + rb.size); - LinearImage* image = new LinearImage(ImageDecoder::decode( + std::istream inputStream(&sb); + + LinearImage *image = new LinearImage(ImageDecoder::decode( inputStream, path.c_str(), ImageDecoder::ColorSpace::SRGB)); - if (!image->isValid()) { - Log("Invalid image : %s", path.c_str()); - return; - } - - uint32_t channels = image->getChannels(); - _imageWidth = image->getWidth(); - _imageHeight = image->getHeight(); - - _imageTexture = Texture::Builder() - .width(_imageWidth) - .height(_imageHeight) - .levels(0x01) - .format(channels == 3 ? Texture::InternalFormat::RGB16F - : Texture::InternalFormat::RGBA16F) - .sampler(Texture::Sampler::SAMPLER_2D) - .build(*_engine); - - Texture::PixelBufferDescriptor::Callback freeCallback = [](void *buf, size_t, - void *data) { - delete reinterpret_cast(data); - }; - - auto pbd = Texture::PixelBufferDescriptor( - image->getPixelRef(), size_t(_imageWidth * _imageHeight * channels * sizeof(float)), - channels == 3 ? Texture::Format::RGB : Texture::Format::RGBA, - Texture::Type::FLOAT, nullptr, freeCallback, image); - - _imageTexture->setImage(*_engine, 0, std::move(pbd)); - // we don't need to free the ResourceBuffer in the texture callback because LinearImage takes a copy - // (check if this is correct ? ) - _resourceLoaderWrapper->free(rb); -} - -void FilamentViewer::loadTextureFromPath(string path) { - string ktxExt(".ktx"); - string ktx2Ext(".ktx2"); - string pngExt(".png"); - - if (path.length() < 5) { - Log("Invalid resource path : %s", path.c_str()); - return; - } - - ResourceBuffer rb = _resourceLoaderWrapper->load(path.c_str()); - - if(endsWith(path, ktxExt)) { - loadKtxTexture(path, rb); - } else if(endsWith(path, ktx2Ext)) { - loadKtx2Texture(path, rb); - } else if(endsWith(path, pngExt)) { - loadPngTexture(path, rb); - } -} - -void FilamentViewer::setBackgroundColor(const float r, const float g, const float b, const float a) { - _imageMaterial->setDefaultParameter("showImage", 0); - _imageMaterial->setDefaultParameter("backgroundColor", RgbaType::sRGB, float4(r, g, b, a)); - _imageMaterial->setDefaultParameter("transform", _imageScale); -} - -void FilamentViewer::clearBackgroundImage() { - _imageMaterial->setDefaultParameter("showImage", 0); - if (_imageTexture) { - _engine->destroy(_imageTexture); - _imageTexture = nullptr; - Log("Destroyed background image texture"); - } -} - -void FilamentViewer::setBackgroundImage(const char *resourcePath, bool fillHeight) { - - string resourcePathString(resourcePath); - - Log("Setting background image to %s", resourcePath); - - clearBackgroundImage(); - - loadTextureFromPath(resourcePathString); - - // This currently just anchors the image at the bottom left of the viewport at its original size - // TODO - implement stretch/etc - const Viewport& vp = _view->getViewport(); - Log("Image width %d height %d vp width %d height %d", _imageWidth, _imageHeight, vp.width, vp.height); - - - float xScale = float(vp.width) / float(_imageWidth); - - float yScale; - if(fillHeight) { - yScale = 1.0f; - } else { - yScale = float(vp.height) / float(_imageHeight); - } - - _imageScale = mat4f { xScale , 0.0f, 0.0f, 0.0f, 0.0f, yScale , 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; - - _imageMaterial->setDefaultParameter("transform", _imageScale); - _imageMaterial->setDefaultParameter("image", _imageTexture, _imageSampler); - _imageMaterial->setDefaultParameter("showImage", 1); - -} - - -/// -/// Translates the background image by (x,y) pixels. -/// If clamp is true, x/y are both clamped so that the left/top and right/bottom sides of the background image -/// are positioned at a max/min of -1/1 respectively -/// (i.e. you cannot set a position where the left/top or right/bottom sides would be "inside" the screen coordinate space). -/// -void FilamentViewer::setBackgroundImagePosition(float x, float y, bool clamp=false) { - - // to translate the background image, we apply a transform to the UV coordinates of the quad texture, not the quad itself (see image.mat). - // this allows us to set a background colour for the quad when the texture has been translated outside the quad's bounds. - // so we need to munge the coordinates appropriately (and take into consideration the scale transform applied when the image was loaded). - - // first, convert x/y to a percentage of the original image size - x /= _imageWidth; - y /= _imageHeight; - - // now scale these by the viewport dimensions so they can be incorporated directly into the UV transform matrix. - // x *= _imageScale[0][0]; - // y *= _imageScale[1][1]; - - // TODO - I haven't updated the clamp calculations to work with scaled image width/height percentages so the below code is probably wrong, don't use it until it's fixed. - if(clamp) { - Log("Clamping background image translation"); - // first, clamp x/y - auto xScale = float(_imageWidth) / _view->getViewport().width; - auto yScale = float(_imageHeight) / _view->getViewport().height; - - float xMin = 0; - float xMax = 0; - float yMin = 0; - float yMax = 0; - - // we need to clamp x so that it can only be translated between (left side touching viewport left) and (right side touching viewport right) - // if width is less than viewport, these values are 0/1-xScale respectively - if(xScale < 1) { - xMin = 0; - xMax = 1-xScale; - // otherwise, these value are (xScale-1 and 1-xScale) - } else { - xMin = 1-xScale; - xMax = 0; + if (!image->isValid()) + { + Log("Invalid image : %s", path.c_str()); + return; } - // do the same for y - if(yScale < 1) { - yMin = 0; - yMax = 1-yScale; - } else { - yMin = 1-yScale; - yMax = 0; + uint32_t channels = image->getChannels(); + _imageWidth = image->getWidth(); + _imageHeight = image->getHeight(); + + _imageTexture = Texture::Builder() + .width(_imageWidth) + .height(_imageHeight) + .levels(0x01) + .format(channels == 3 ? Texture::InternalFormat::RGB16F + : Texture::InternalFormat::RGBA16F) + .sampler(Texture::Sampler::SAMPLER_2D) + .build(*_engine); + + Texture::PixelBufferDescriptor::Callback freeCallback = [](void *buf, size_t, + void *data) + { + delete reinterpret_cast(data); + }; + + auto pbd = Texture::PixelBufferDescriptor( + image->getPixelRef(), size_t(_imageWidth * _imageHeight * channels * sizeof(float)), + channels == 3 ? Texture::Format::RGB : Texture::Format::RGBA, + Texture::Type::FLOAT, nullptr, freeCallback, image); + + _imageTexture->setImage(*_engine, 0, std::move(pbd)); + // we don't need to free the ResourceBuffer in the texture callback because LinearImage takes a copy + // (check if this is correct ? ) + _resourceLoaderWrapper->free(rb); + } + + void FilamentViewer::loadTextureFromPath(string path) + { + string ktxExt(".ktx"); + string ktx2Ext(".ktx2"); + string pngExt(".png"); + + if (path.length() < 5) + { + Log("Invalid resource path : %s", path.c_str()); + return; } - x = std::max(xMin, std::min(x,xMax)); - y = std::max(yMin, std::min(y,yMax)); + ResourceBuffer rb = _resourceLoaderWrapper->load(path.c_str()); + + if (endsWith(path, ktxExt)) + { + loadKtxTexture(path, rb); + } + else if (endsWith(path, ktx2Ext)) + { + loadKtx2Texture(path, rb); + } + else if (endsWith(path, pngExt)) + { + loadPngTexture(path, rb); + } } - // these values are then negated to account for the fact that the transform is applied to the UV coordinates, not the vertices (see image.mat). - // i.e. translating the image right by 0.5 units means translating the UV coordinates left by 0.5 units. - x = -x; - y = -y; - Log("x %f y %f", x, y); - - Log("imageScale %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f ", _imageScale[0][0],_imageScale[0][1],_imageScale[0][2], _imageScale[0][3], \ - _imageScale[1][0],_imageScale[1][1],_imageScale[1][2], _imageScale[1][3],\ - _imageScale[2][0],_imageScale[2][1],_imageScale[2][2], _imageScale[2][3], \ - _imageScale[3][0],_imageScale[3][1],_imageScale[3][2], _imageScale[3][3]); - - auto transform = math::mat4f::translation(math::float3(x, y, 0.0f)) * _imageScale; - - - Log("transform %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f ", transform[0][0],transform[0][1],transform[0][2], transform[0][3], \ - transform[1][0],transform[1][1],transform[1][2], transform[1][3],\ - transform[2][0],transform[2][1],transform[2][2], transform[2][3], \ - transform[3][0],transform[3][1],transform[3][2], transform[3][3]); - _imageMaterial->setDefaultParameter("transform", transform); -} - -FilamentViewer::~FilamentViewer() { - clearAssets(); - delete _assetManager; - - for(auto it : _lights) { - _engine->destroy(it); + void FilamentViewer::setBackgroundColor(const float r, const float g, const float b, const float a) + { + _imageMaterial->setDefaultParameter("showImage", 0); + _imageMaterial->setDefaultParameter("backgroundColor", RgbaType::sRGB, float4(r, g, b, a)); + _imageMaterial->setDefaultParameter("transform", _imageScale); } - - _engine->destroyCameraComponent(_mainCamera->getEntity()); - _mainCamera = nullptr; - _engine->destroy(_view); - _engine->destroy(_scene); - _engine->destroy(_renderer); - _engine->destroy(_swapChain); - - Engine::destroy(&_engine); // clears engine* -} -Renderer *FilamentViewer::getRenderer() { return _renderer; } + void FilamentViewer::clearBackgroundImage() + { + _imageMaterial->setDefaultParameter("showImage", 0); + if (_imageTexture) + { + _engine->destroy(_imageTexture); + _imageTexture = nullptr; + Log("Destroyed background image texture"); + } + } -void FilamentViewer::createSwapChain(const void *window, uint32_t width, uint32_t height) { - #if TARGET_OS_IPHONE - _swapChain = _engine->createSwapChain((void*)window, filament::backend::SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER); - #else - if(window) { - _swapChain = _engine->createSwapChain((void*)window, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE); + void FilamentViewer::setBackgroundImage(const char *resourcePath, bool fillHeight) + { + + string resourcePathString(resourcePath); + + Log("Setting background image to %s", resourcePath); + + clearBackgroundImage(); + + loadTextureFromPath(resourcePathString); + + // This currently just anchors the image at the bottom left of the viewport at its original size + // TODO - implement stretch/etc + const Viewport &vp = _view->getViewport(); + Log("Image width %d height %d vp width %d height %d", _imageWidth, _imageHeight, vp.width, vp.height); + + float xScale = float(vp.width) / float(_imageWidth); + + float yScale; + if (fillHeight) + { + yScale = 1.0f; + } + else + { + yScale = float(vp.height) / float(_imageHeight); + } + + _imageScale = mat4f{xScale, 0.0f, 0.0f, 0.0f, 0.0f, yScale, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}; + + _imageMaterial->setDefaultParameter("transform", _imageScale); + _imageMaterial->setDefaultParameter("image", _imageTexture, _imageSampler); + _imageMaterial->setDefaultParameter("showImage", 1); + } + + /// + /// Translates the background image by (x,y) pixels. + /// If clamp is true, x/y are both clamped so that the left/top and right/bottom sides of the background image + /// are positioned at a max/min of -1/1 respectively + /// (i.e. you cannot set a position where the left/top or right/bottom sides would be "inside" the screen coordinate space). + /// + void FilamentViewer::setBackgroundImagePosition(float x, float y, bool clamp = false) + { + + // to translate the background image, we apply a transform to the UV coordinates of the quad texture, not the quad itself (see image.mat). + // this allows us to set a background colour for the quad when the texture has been translated outside the quad's bounds. + // so we need to munge the coordinates appropriately (and take into consideration the scale transform applied when the image was loaded). + + // first, convert x/y to a percentage of the original image size + x /= _imageWidth; + y /= _imageHeight; + + // now scale these by the viewport dimensions so they can be incorporated directly into the UV transform matrix. + // x *= _imageScale[0][0]; + // y *= _imageScale[1][1]; + + // TODO - I haven't updated the clamp calculations to work with scaled image width/height percentages so the below code is probably wrong, don't use it until it's fixed. + if (clamp) + { + Log("Clamping background image translation"); + // first, clamp x/y + auto xScale = float(_imageWidth) / _view->getViewport().width; + auto yScale = float(_imageHeight) / _view->getViewport().height; + + float xMin = 0; + float xMax = 0; + float yMin = 0; + float yMax = 0; + + // we need to clamp x so that it can only be translated between (left side touching viewport left) and (right side touching viewport right) + // if width is less than viewport, these values are 0/1-xScale respectively + if (xScale < 1) + { + xMin = 0; + xMax = 1 - xScale; + // otherwise, these value are (xScale-1 and 1-xScale) + } + else + { + xMin = 1 - xScale; + xMax = 0; + } + + // do the same for y + if (yScale < 1) + { + yMin = 0; + yMax = 1 - yScale; + } + else + { + yMin = 1 - yScale; + yMax = 0; + } + + x = std::max(xMin, std::min(x, xMax)); + y = std::max(yMin, std::min(y, yMax)); + } + + // these values are then negated to account for the fact that the transform is applied to the UV coordinates, not the vertices (see image.mat). + // i.e. translating the image right by 0.5 units means translating the UV coordinates left by 0.5 units. + x = -x; + y = -y; + Log("x %f y %f", x, y); + + Log("imageScale %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f ", _imageScale[0][0], _imageScale[0][1], _imageScale[0][2], _imageScale[0][3], + _imageScale[1][0], _imageScale[1][1], _imageScale[1][2], _imageScale[1][3], + _imageScale[2][0], _imageScale[2][1], _imageScale[2][2], _imageScale[2][3], + _imageScale[3][0], _imageScale[3][1], _imageScale[3][2], _imageScale[3][3]); + + auto transform = math::mat4f::translation(math::float3(x, y, 0.0f)) * _imageScale; + + Log("transform %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f ", transform[0][0], transform[0][1], transform[0][2], transform[0][3], + transform[1][0], transform[1][1], transform[1][2], transform[1][3], + transform[2][0], transform[2][1], transform[2][2], transform[2][3], + transform[3][0], transform[3][1], transform[3][2], transform[3][3]); + _imageMaterial->setDefaultParameter("transform", transform); + } + + FilamentViewer::~FilamentViewer() + { + clearAssets(); + delete _assetManager; + + for (auto it : _lights) + { + _engine->destroy(it); + } + + _engine->destroyCameraComponent(_mainCamera->getEntity()); + _mainCamera = nullptr; + _engine->destroy(_view); + _engine->destroy(_scene); + _engine->destroy(_renderer); + _engine->destroy(_swapChain); + + Engine::destroy(&_engine); // clears engine* + } + + Renderer *FilamentViewer::getRenderer() { return _renderer; } + + void FilamentViewer::createSwapChain(const void *window, uint32_t width, uint32_t height) + { +#if TARGET_OS_IPHONE + _swapChain = _engine->createSwapChain((void *)window, filament::backend::SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER); +#else + if (window) + { + _swapChain = _engine->createSwapChain((void *)window, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE); Log("Created window swapchain."); - } else { + } + else + { Log("Created headless swapchain."); _swapChain = _engine->createSwapChain(width, height, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE); } - #endif -} - -void FilamentViewer::createRenderTarget(intptr_t texture, uint32_t width, uint32_t height) { - // Create filament textures and render targets (note the color buffer has the import call) - _rtColor = filament::Texture::Builder() - .width(width) - .height(height) - .levels(1) - .usage(filament::Texture::Usage::COLOR_ATTACHMENT | filament::Texture::Usage::SAMPLEABLE) - .format(filament::Texture::InternalFormat::RGBA8) - .import(texture) - .build(*_engine); - _rtDepth = filament::Texture::Builder() - .width(width) - .height(height) - .levels(1) - .usage(filament::Texture::Usage::DEPTH_ATTACHMENT) - .format(filament::Texture::InternalFormat::DEPTH32F) - .build(*_engine); - _rt = filament::RenderTarget::Builder() - .texture(RenderTarget::AttachmentPoint::COLOR, _rtColor) - .texture(RenderTarget::AttachmentPoint::DEPTH, _rtDepth) - .build(*_engine); - - // Make a specific viewport just for our render target - _view->setRenderTarget(_rt); - - Log("Set render target for texture id %u to %u x %u", texture, width, height); - -} - -void FilamentViewer::destroySwapChain() { - if(_rt) { - _view->setRenderTarget(nullptr); - _engine->destroy(_rtDepth); - _engine->destroy(_rtColor); - _engine->destroy(_rt); - _rt = nullptr; - _rtDepth = nullptr; - _rtColor = nullptr; +#endif } - if (_swapChain) { - _engine->destroy(_swapChain); - _swapChain = nullptr; - Log("Swapchain destroyed."); - } -} -void FilamentViewer::clearAssets() { - Log("Clearing all assets"); - if(_mainCamera) { + void FilamentViewer::createRenderTarget(intptr_t texture, uint32_t width, uint32_t height) + { + // Create filament textures and render targets (note the color buffer has the import call) + _rtColor = filament::Texture::Builder() + .width(width) + .height(height) + .levels(1) + .usage(filament::Texture::Usage::COLOR_ATTACHMENT | filament::Texture::Usage::SAMPLEABLE) + .format(filament::Texture::InternalFormat::RGBA8) + .import(texture) + .build(*_engine); + _rtDepth = filament::Texture::Builder() + .width(width) + .height(height) + .levels(1) + .usage(filament::Texture::Usage::DEPTH_ATTACHMENT) + .format(filament::Texture::InternalFormat::DEPTH32F) + .build(*_engine); + _rt = filament::RenderTarget::Builder() + .texture(RenderTarget::AttachmentPoint::COLOR, _rtColor) + .texture(RenderTarget::AttachmentPoint::DEPTH, _rtDepth) + .build(*_engine); + + // Make a specific viewport just for our render target + _view->setRenderTarget(_rt); + + Log("Set render target for texture id %u to %u x %u", texture, width, height); + } + + void FilamentViewer::destroySwapChain() + { + if (_rt) + { + _view->setRenderTarget(nullptr); + _engine->destroy(_rtDepth); + _engine->destroy(_rtColor); + _engine->destroy(_rt); + _rt = nullptr; + _rtDepth = nullptr; + _rtColor = nullptr; + } + if (_swapChain) + { + _engine->destroy(_swapChain); + _swapChain = nullptr; + Log("Swapchain destroyed."); + } + } + + void FilamentViewer::clearAssets() + { + Log("Clearing all assets"); + if (_mainCamera) + { + _view->setCamera(_mainCamera); + } + + _assetManager->destroyAll(); + + Log("Cleared all assets"); + } + + void FilamentViewer::removeAsset(EntityId asset) + { + Log("Removing asset from scene"); + + mtx.lock(); + // todo - what if we are using a camera from this asset? _view->setCamera(_mainCamera); + _assetManager->remove(asset); + mtx.unlock(); } - _assetManager->destroyAll(); - - Log("Cleared all assets"); -} + /// + /// Set the exposure for the current active camera. + /// + void FilamentViewer::setCameraExposure(float aperture, float shutterSpeed, float sensitivity) + { + Camera &cam = _view->getCamera(); + Log("Setting aperture (%03f) shutterSpeed (%03f) and sensitivity (%03f)", aperture, shutterSpeed, sensitivity); + cam.setExposure(aperture, shutterSpeed, sensitivity); + } -void FilamentViewer::removeAsset(EntityId asset) { - Log("Removing asset from scene"); + /// + /// Set the focal length of the active camera. + /// + void FilamentViewer::setCameraFocalLength(float focalLength) + { + Camera &cam = _view->getCamera(); + _cameraFocalLength = focalLength; + cam.setLensProjection(_cameraFocalLength, 1.0f, kNearPlane, + kFarPlane); + } - mtx.lock(); - // todo - what if we are using a camera from this asset? - _view->setCamera(_mainCamera); - _assetManager->remove(asset); - mtx.unlock(); -} + /// + /// Set the focus distance of the active camera. + /// + void FilamentViewer::setCameraFocusDistance(float focusDistance) + { + Camera &cam = _view->getCamera(); + _cameraFocusDistance = focusDistance; + cam.setFocusDistance(_cameraFocusDistance); + } -/// -/// Set the exposure for the current active camera. -/// -void FilamentViewer::setCameraExposure(float aperture, float shutterSpeed, float sensitivity) { - Camera& cam =_view->getCamera(); - Log("Setting aperture (%03f) shutterSpeed (%03f) and sensitivity (%03f)", aperture, shutterSpeed, sensitivity); - cam.setExposure(aperture, shutterSpeed, sensitivity); -} - -/// -/// Set the focal length of the active camera. -/// -void FilamentViewer::setCameraFocalLength(float focalLength) { - Camera& cam =_view->getCamera(); - _cameraFocalLength = focalLength; - cam.setLensProjection(_cameraFocalLength, 1.0f, kNearPlane, - kFarPlane); -} - -/// -/// Set the focus distance of the active camera. -/// -void FilamentViewer::setCameraFocusDistance(float focusDistance) { - Camera& cam =_view->getCamera(); - _cameraFocusDistance = focusDistance; - cam.setFocusDistance(_cameraFocusDistance); -} - -/// -/// Sets the active camera to the GLTF camera node specified by [name] (or if null, the first camera found under that node). -/// N.B. Blender will generally export a three-node hierarchy - -/// Camera1->Camera_Orientation->Camera2. The correct name will be the Camera_Orientation. -/// -bool FilamentViewer::setCamera(EntityId entityId, const char *cameraName) { + /// + /// Sets the active camera to the GLTF camera node specified by [name] (or if null, the first camera found under that node). + /// N.B. Blender will generally export a three-node hierarchy - + /// Camera1->Camera_Orientation->Camera2. The correct name will be the Camera_Orientation. + /// + bool FilamentViewer::setCamera(EntityId entityId, const char *cameraName) + { auto asset = _assetManager->getAssetByEntityId(entityId); - if(!asset) { - Log("Failed to find asset under entity id %d.", entityId); - return false; + if (!asset) + { + Log("Failed to find asset under entity id %d.", entityId); + return false; } size_t count = asset->getCameraEntityCount(); - if (count == 0) { - Log("No cameras found attached to specified entity."); - return false; + if (count == 0) + { + Log("No cameras found attached to specified entity."); + return false; } - const utils::Entity* cameras = asset->getCameraEntities(); + const utils::Entity *cameras = asset->getCameraEntities(); utils::Entity target; - if(!cameraName) { - auto inst = _ncm->getInstance(cameras[0]); - const char *name = _ncm->getName(inst); - target = cameras[0]; - Log("No camera specified, using first camera node found (%s)", name); - } else { - for (int j = 0; j < count; j++) { - auto inst = _ncm->getInstance(cameras[j]); - const char *name = _ncm->getName(inst); - if (strcmp(name, cameraName) == 0) { - target = cameras[j]; - break; - } - } + if (!cameraName) + { + auto inst = _ncm->getInstance(cameras[0]); + const char *name = _ncm->getName(inst); + target = cameras[0]; + Log("No camera specified, using first camera node found (%s)", name); } - if(target.isNull()) { + else + { + for (int j = 0; j < count; j++) + { + auto inst = _ncm->getInstance(cameras[j]); + const char *name = _ncm->getName(inst); + if (strcmp(name, cameraName) == 0) + { + target = cameras[j]; + break; + } + } + } + if (target.isNull()) + { Log("Unable to locate camera under name %s ", cameraName); return false; } Camera *camera = _engine->getCameraComponent(target); - if(!camera) { - Log("Failed to retrieve camera component for target"); - return false; + if (!camera) + { + Log("Failed to retrieve camera component for target"); + return false; } _view->setCamera(camera); @@ -759,312 +825,400 @@ bool FilamentViewer::setCamera(EntityId entityId, const char *cameraName) { Log("Successfully set view camera to target"); return true; -} - -void FilamentViewer::loadSkybox(const char *const skyboxPath) { - - removeSkybox(); - - if (!skyboxPath) { - Log("No skybox path provided, removed skybox."); } - Log("Loading skybox from path %s", skyboxPath); - + void FilamentViewer::loadSkybox(const char *const skyboxPath) + { + + removeSkybox(); + + if (!skyboxPath) + { + Log("No skybox path provided, removed skybox."); + } + + Log("Loading skybox from path %s", skyboxPath); + ResourceBuffer skyboxBuffer = _resourceLoaderWrapper->load(skyboxPath); // because this will go out of scope before the texture callback is invoked, we need to make a copy to the heap - ResourceBuffer* skyboxBufferCopy = new ResourceBuffer(skyboxBuffer); - - if(skyboxBuffer.size <= 0) { + ResourceBuffer *skyboxBufferCopy = new ResourceBuffer(skyboxBuffer); + + if (skyboxBuffer.size <= 0) + { Log("Could not load skybox resource."); return; } Log("Loaded skybox data of length %d", skyboxBuffer.size); - std::vector* callbackData = new std::vector { (void*)_resourceLoaderWrapper, skyboxBufferCopy }; + std::vector *callbackData = new std::vector{(void *)_resourceLoaderWrapper, skyboxBufferCopy}; image::Ktx1Bundle *skyboxBundle = new image::Ktx1Bundle(static_cast(skyboxBuffer.data), static_cast(skyboxBuffer.size)); _skyboxTexture = - ktxreader::Ktx1Reader::createTexture(_engine, *skyboxBundle, false, [](void* userdata) { + ktxreader::Ktx1Reader::createTexture( + _engine, *skyboxBundle, false, [](void *userdata) + { std::vector* vec = (std::vector*)userdata; ResourceLoaderWrapper* loader = (ResourceLoaderWrapper*)vec->at(0); ResourceBuffer* rb = (ResourceBuffer*) vec->at(1); loader->free(*rb); delete rb; delete vec; - Log("Skybox load complete."); - }, callbackData); + Log("Skybox load complete."); }, + callbackData); _skybox = filament::Skybox::Builder().environment(_skyboxTexture).build(*_engine); _scene->setSkybox(_skybox); - -} - -void FilamentViewer::removeSkybox() { - Log("Removing skybox"); - _scene->setSkybox(nullptr); - if(_skybox) { - _engine->destroy(_skybox); - _skybox = nullptr; } - if(_skyboxTexture) { - _engine->destroy(_skyboxTexture); - _skyboxTexture = nullptr; - } -} -void FilamentViewer::removeIbl() { - if(_indirectLight) { - _engine->destroy(_indirectLight); - _engine->destroy(_iblTexture); - _indirectLight = nullptr; - _iblTexture = nullptr; - } - _scene->setIndirectLight(nullptr); -} - -void FilamentViewer::loadIbl(const char *const iblPath, float intensity) { - removeIbl(); - if (iblPath) { - Log("Loading IBL from %s", iblPath); - - // Load IBL. - ResourceBuffer iblBuffer = _resourceLoaderWrapper->load(iblPath); - // because this will go out of scope before the texture callback is invoked, we need to make a copy to the heap - ResourceBuffer* iblBufferCopy = new ResourceBuffer(iblBuffer); - - if(iblBuffer.size == 0) { - Log("Error loading IBL, resource could not be loaded."); - return; + void FilamentViewer::removeSkybox() + { + Log("Removing skybox"); + _scene->setSkybox(nullptr); + if (_skybox) + { + _engine->destroy(_skybox); + _skybox = nullptr; } + if (_skyboxTexture) + { + _engine->destroy(_skyboxTexture); + _skyboxTexture = nullptr; + } + } - image::Ktx1Bundle *iblBundle = - new image::Ktx1Bundle(static_cast(iblBuffer.data), - static_cast(iblBuffer.size)); - math::float3 harmonics[9]; - iblBundle->getSphericalHarmonics(harmonics); + void FilamentViewer::removeIbl() + { + if (_indirectLight) + { + _engine->destroy(_indirectLight); + _engine->destroy(_iblTexture); + _indirectLight = nullptr; + _iblTexture = nullptr; + } + _scene->setIndirectLight(nullptr); + } - std::vector* callbackData = new std::vector { (void*)_resourceLoaderWrapper, iblBufferCopy }; + void FilamentViewer::loadIbl(const char *const iblPath, float intensity) + { + removeIbl(); + if (iblPath) + { + Log("Loading IBL from %s", iblPath); + + // Load IBL. + ResourceBuffer iblBuffer = _resourceLoaderWrapper->load(iblPath); + // because this will go out of scope before the texture callback is invoked, we need to make a copy to the heap + ResourceBuffer *iblBufferCopy = new ResourceBuffer(iblBuffer); + + if (iblBuffer.size == 0) + { + Log("Error loading IBL, resource could not be loaded."); + return; + } + + image::Ktx1Bundle *iblBundle = + new image::Ktx1Bundle(static_cast(iblBuffer.data), + static_cast(iblBuffer.size)); + math::float3 harmonics[9]; + iblBundle->getSphericalHarmonics(harmonics); + + std::vector *callbackData = new std::vector{(void *)_resourceLoaderWrapper, iblBufferCopy}; _iblTexture = - ktxreader::Ktx1Reader::createTexture(_engine, *iblBundle, false, [](void* userdata) { + ktxreader::Ktx1Reader::createTexture( + _engine, *iblBundle, false, [](void *userdata) + { std::vector* vec = (std::vector*)userdata; ResourceLoaderWrapper* loader = (ResourceLoaderWrapper*)vec->at(0); ResourceBuffer* rb = (ResourceBuffer*) vec->at(1); loader->free(*rb); delete rb; - delete vec; - }, callbackData); - _indirectLight = IndirectLight::Builder() - .reflections(_iblTexture) - .irradiance(3, harmonics) - .intensity(intensity) - .build(*_engine); - _scene->setIndirectLight(_indirectLight); + delete vec; }, + callbackData); + _indirectLight = IndirectLight::Builder() + .reflections(_iblTexture) + .irradiance(3, harmonics) + .intensity(intensity) + .build(*_engine); + _scene->setIndirectLight(_indirectLight); - Log("IBL loaded."); - } -} - -double _elapsed = 0; -int _frameCount = 0; - -void FilamentViewer::render( - uint64_t frameTimeInNanos, - void* pixelBuffer, - void (*callback)(void *buf, size_t size, void *data), - void* data) { - - if (!_view || !_mainCamera || !_swapChain) { - Log("Not ready for rendering"); - return; - } - - if(_frameCount == 60) { - // Log("1 sec average for asset animation update %f", _elapsed / 60); - _elapsed = 0; - _frameCount = 0; - } - - Timer tmr; - - _assetManager->updateAnimations(); - - _elapsed += tmr.elapsed(); - _frameCount++; - - if(pixelBuffer) { - auto pbd = Texture::PixelBufferDescriptor( - pixelBuffer, size_t(1024 * 768 * 4), - Texture::Format::RGBA, - Texture::Type::BYTE, nullptr, callback, data); - - _renderer->beginFrame(_swapChain, 0); - _renderer->render(_view); - _renderer->readPixels(0,0,1024,768, std::move(pbd)); - _renderer->endFrame(); - } else { - // Render the scene, unless the renderer wants to skip the frame. - if (_renderer->beginFrame(_swapChain, frameTimeInNanos)) { - _renderer->render(_view); - _renderer->endFrame(); - } else { - // skipped frame + Log("IBL loaded."); } } - -} -void FilamentViewer::updateViewportAndCameraProjection( - int width, int height, float contentScaleFactor) { - if (!_view || !_mainCamera) { - Log("Skipping camera update, no view or camrea"); - return; + double _elapsed = 0; + int _frameCount = 0; + + void FilamentViewer::render( + uint64_t frameTimeInNanos, + void *pixelBuffer, + void (*callback)(void *buf, size_t size, void *data), + void *data) + { + + if (!_view || !_mainCamera || !_swapChain) + { + Log("Not ready for rendering"); + return; + } + + if (_frameCount == 60) + { + // Log("1 sec average for asset animation update %f", _elapsed / 60); + _elapsed = 0; + _frameCount = 0; + } + + Timer tmr; + + _assetManager->updateAnimations(); + + _elapsed += tmr.elapsed(); + _frameCount++; + + // if a manipulator is active, update the active camera orientation + if(_manipulator) { + math::double3 eye, target, upward; + Camera& cam =_view->getCamera(); + _manipulator->getLookAt(&eye, &target, &upward); + cam.lookAt(eye, target, upward); + } + + // TODO - this was an experiment but probably useful to keep for debugging + // if pixelBuffer is provided, we will copy the framebuffer into the pixelBuffer. + if (pixelBuffer) + { + auto pbd = Texture::PixelBufferDescriptor( + pixelBuffer, size_t(1024 * 768 * 4), + Texture::Format::RGBA, + Texture::Type::BYTE, nullptr, callback, data); + + _renderer->beginFrame(_swapChain, 0); + _renderer->render(_view); + _renderer->readPixels(0, 0, 1024, 768, std::move(pbd)); + _renderer->endFrame(); + } + else + { + // Render the scene, unless the renderer wants to skip the frame. + if (_renderer->beginFrame(_swapChain, frameTimeInNanos)) + { + _renderer->render(_view); + _renderer->endFrame(); + } + else + { + // skipped frame + } + } } - const uint32_t _width = width * contentScaleFactor; - const uint32_t _height = height * contentScaleFactor; - _view->setViewport({0, 0, _width, _height}); + void FilamentViewer::updateViewportAndCameraProjection( + int width, int height, float contentScaleFactor) + { + if (!_view || !_mainCamera) + { + Log("Skipping camera update, no view or camrea"); + return; + } - const double aspect = (double)width / height; + const uint32_t _width = width * contentScaleFactor; + const uint32_t _height = height * contentScaleFactor; + _view->setViewport({0, 0, _width, _height}); - Camera& cam =_view->getCamera(); - cam.setLensProjection(_cameraFocalLength, 1.0f, kNearPlane, - kFarPlane); + const double aspect = (double)width / height; - cam.setScaling({1.0 / aspect, 1.0}); + Camera &cam = _view->getCamera(); + cam.setLensProjection(_cameraFocalLength, 1.0f, kNearPlane, + kFarPlane); - Log("Set viewport to width: %d height: %d aspect %f scaleFactor : %f", width, height, aspect, - contentScaleFactor); -} + cam.setScaling({1.0 / aspect, 1.0}); -void FilamentViewer::setViewFrustumCulling(bool enabled) { - _view->setFrustumCullingEnabled(enabled); -} + Log("Set viewport to width: %d height: %d aspect %f scaleFactor : %f", width, height, aspect, + contentScaleFactor); + } -void FilamentViewer::setCameraPosition(float x, float y, float z) { - Camera& cam =_view->getCamera(); + void FilamentViewer::setViewFrustumCulling(bool enabled) + { + _view->setFrustumCullingEnabled(enabled); + } - _cameraPosition = math::mat4f::translation(math::float3(x,y,z)); - cam.setModelMatrix(_cameraPosition * _cameraRotation); -} + void FilamentViewer::setCameraPosition(float x, float y, float z) + { + Camera &cam = _view->getCamera(); -void FilamentViewer::moveCameraToAsset(EntityId entityId) { - auto asset = _assetManager->getAssetByEntityId(entityId); - if(!asset) { + _cameraPosition = math::mat4f::translation(math::float3(x, y, z)); + cam.setModelMatrix(_cameraPosition * _cameraRotation); + } + + void FilamentViewer::moveCameraToAsset(EntityId entityId) + { + auto asset = _assetManager->getAssetByEntityId(entityId); + if (!asset) + { Log("Failed to find asset attached to specified entity id."); return; - } - - const filament::Aabb bb = asset->getBoundingBox(); - auto corners = bb.getCorners(); - Camera& cam =_view->getCamera(); - auto eye = corners.vertices[0] * 1.5; - auto lookAt = corners.vertices[7]; - cam.lookAt(eye, lookAt); - Log("Moved camera to %f %f %f, lookAt %f %f %f, near %f far %f", eye[0], eye[1], eye[2], lookAt[0], lookAt[1], lookAt[2], cam.getNear(), cam.getCullingFar()); -} - -void FilamentViewer::setCameraRotation(float rads, float x, float y, float z) { - Camera& cam =_view->getCamera(); - _cameraRotation = math::mat4f::rotation(rads, math::float3(x,y,z)); - cam.setModelMatrix(_cameraPosition * _cameraRotation); -} - -void FilamentViewer::setCameraModelMatrix(const float* const matrix) { - Camera& cam =_view->getCamera(); - - mat4 modelMatrix( - matrix[0], - matrix[1], - matrix[2], - matrix[3], - matrix[4], - matrix[5], - matrix[6], - matrix[7], - matrix[8], - matrix[9], - matrix[10], - matrix[11], - matrix[12], - matrix[13], - matrix[14], - matrix[15] -); - cam.setModelMatrix(modelMatrix); -} - -void FilamentViewer::grabBegin(float x, float y, bool pan) { - if (!_view || !_mainCamera || !_swapChain) { - Log("View not ready, ignoring grab"); - return; - } - _panning = pan; - _startX = x; - _startY = y; -} - -void FilamentViewer::grabUpdate(float x, float y) { - if (!_view || !_swapChain) { - Log("View not ready, ignoring grab"); - return; } - Camera& cam =_view->getCamera(); - auto eye = cam.getPosition(); - auto target = eye + cam.getForwardVector(); - auto upward = cam.getUpVector(); - Viewport const& vp = _view->getViewport(); - if(_panning) { - auto trans = cam.getModelMatrix() * mat4::translation(math::float3 { 50 * (x - _startX) / vp.width, 50 * (y - _startY) / vp.height, 0.0f }); - cam.setModelMatrix(trans); - } else { - auto trans = cam.getModelMatrix() * mat4::rotation(0.05, - math::float3 { (y - _startY) / vp.height, (x - _startX) / vp.width, 0.0f }); - cam.setModelMatrix(trans); - } - _startX = x; - _startY = y; -} - -void FilamentViewer::grabEnd() { - if (!_view || !_mainCamera || !_swapChain) { - Log("View not ready, ignoring grab"); - return; + const filament::Aabb bb = asset->getBoundingBox(); + auto corners = bb.getCorners(); + Camera &cam = _view->getCamera(); + auto eye = corners.vertices[0] * 1.5; + auto lookAt = corners.vertices[7]; + cam.lookAt(eye, lookAt); + Log("Moved camera to %f %f %f, lookAt %f %f %f, near %f far %f", eye[0], eye[1], eye[2], lookAt[0], lookAt[1], lookAt[2], cam.getNear(), cam.getCullingFar()); } -} -void FilamentViewer::scrollBegin() { - // noop -} + void FilamentViewer::setCameraRotation(float rads, float x, float y, float z) + { + Camera &cam = _view->getCamera(); + _cameraRotation = math::mat4f::rotation(rads, math::float3(x, y, z)); + cam.setModelMatrix(_cameraPosition * _cameraRotation); + } -void FilamentViewer::scrollUpdate(float x, float y, float delta) { - Camera& cam =_view->getCamera(); - Viewport const& vp = _view->getViewport(); - auto trans = cam.getModelMatrix() * mat4::translation(math::float3 {0.0f, 0.0f, delta }); - cam.setModelMatrix(trans); -} + void FilamentViewer::setCameraModelMatrix(const float *const matrix) + { + Camera &cam = _view->getCamera(); -void FilamentViewer::scrollEnd() { - // noop -} + mat4 modelMatrix( + matrix[0], + matrix[1], + matrix[2], + matrix[3], + matrix[4], + matrix[5], + matrix[6], + matrix[7], + matrix[8], + matrix[9], + matrix[10], + matrix[11], + matrix[12], + matrix[13], + matrix[14], + matrix[15]); + cam.setModelMatrix(modelMatrix); + } -void FilamentViewer::pick(uint32_t x, uint32_t y, EntityId* entityId) { - Log("Picking at %d,%d", x, y); - _view->pick(x, y, [=](filament::View::PickingQueryResult const & result) { - + void FilamentViewer::_createManipulator() + { + Camera &cam = _view->getCamera(); + math::double3 home = cam.getPosition(); + math::double3 up = cam.getUpVector(); + math::double3 target = home + cam.getForwardVector(); + Viewport const &vp = _view->getViewport(); + Log("Creating manipulator for viewport size %dx%d with camera norm %f", vp.width, vp.height, norm(home)); + float zoomSpeed = norm(home) / 10; + zoomSpeed = math::clamp(zoomSpeed, 0.1f, 100000.0f); + + _manipulator = Manipulator::Builder() + .viewport(vp.width, vp.height) + .orbitHomePosition(home[0], home[1], home[2]) + .upVector(up.x, up.y, up.z) + .zoomSpeed(zoomSpeed) + // .orbitSpeed(0.0001, 0.0001) + .targetPosition(target[0], target[1], target[2]) + .build(Mode::ORBIT); + } + + void FilamentViewer::grabBegin(float x, float y, bool pan) + { + if (!_view || !_mainCamera || !_swapChain) + { + Log("View not ready, ignoring grab"); + return; + } + if (!_manipulator) + { + _createManipulator(); + } + if(pan) { + Log("Beginning pan at %f %f", x, y); + } else { + Log("Beginning rotate at %f %f", x, y); + } + + _manipulator->grabBegin(x, y, pan); + } + + void FilamentViewer::grabUpdate(float x, float y) + { + if (!_view || !_swapChain) + { + Log("View not ready, ignoring grab"); + return; + } + + + Log("Updating grab at %f %f", x, y); + + + if (_manipulator) + { + _manipulator->grabUpdate(x, y); + } + else + { + Log("Error - trying to use a manipulator when one is not available. Ensure you call grabBegin before grabUpdate/grabEnd"); + } + } + + void FilamentViewer::grabEnd() + { + if (!_view || !_mainCamera || !_swapChain) + { + Log("View not ready, ignoring grab"); + return; + } + if (_manipulator) + { + _manipulator->grabEnd(); + } + else + { + Log("Error - trying to use a manipulator when one is not available. Ensure you call grabBegin before grabUpdate/grabEnd"); + } + delete _manipulator; + _manipulator = nullptr; + } + + void FilamentViewer::scrollBegin() + { + if (!_manipulator) + { + _createManipulator(); + } + } + + void FilamentViewer::scrollUpdate(float x, float y, float delta) + { + if (_manipulator) + { + _manipulator->scroll(int(x), int(y), delta); + } + else + { + Log("Error - trying to use a manipulator when one is not available. Ensure you call grabBegin before grabUpdate/grabEnd"); + } + } + + void FilamentViewer::scrollEnd() + { + delete _manipulator; + _manipulator = nullptr; + } + + void FilamentViewer::pick(uint32_t x, uint32_t y, EntityId *entityId) + { + _view->pick(x, y, [=](filament::View::PickingQueryResult const &result) { *entityId = Entity::smuggle(result.renderable); - Log("Got result %d", *entityId); - }); -} + }); + } } // namespace polyvox - - diff --git a/macos/src/PolyvoxFilamentApi.cpp b/macos/src/PolyvoxFilamentApi.cpp index 45d89a56..b03dd271 100644 --- a/macos/src/PolyvoxFilamentApi.cpp +++ b/macos/src/PolyvoxFilamentApi.cpp @@ -43,32 +43,32 @@ extern "C" { ((FilamentViewer*)viewer)->setBackgroundImage(path, fillHeight); } - FLUTTER_PLUGIN_EXPORT void set_background_image_position(const void* const viewer, float x, float y, bool clamp) { + FLUTTER_PLUGIN_EXPORT void set_background_image_position(const void* const viewer, float x, float y, bool clamp) { ((FilamentViewer*)viewer)->setBackgroundImagePosition(x, y, clamp); } - FLUTTER_PLUGIN_EXPORT void set_tone_mapping(const void* const viewer, int toneMapping) { + FLUTTER_PLUGIN_EXPORT void set_tone_mapping(const void* const viewer, int toneMapping) { ((FilamentViewer*)viewer)->setToneMapping((ToneMapping)toneMapping); } - FLUTTER_PLUGIN_EXPORT void set_bloom(const void* const viewer, float strength) { + FLUTTER_PLUGIN_EXPORT void set_bloom(const void* const viewer, float strength) { Log("Setting bloom to %f", strength); ((FilamentViewer*)viewer)->setBloom(strength); } - FLUTTER_PLUGIN_EXPORT void load_skybox(const void* const viewer, const char* skyboxPath) { + FLUTTER_PLUGIN_EXPORT void load_skybox(const void* const viewer, const char* skyboxPath) { ((FilamentViewer*)viewer)->loadSkybox(skyboxPath); } - FLUTTER_PLUGIN_EXPORT void load_ibl(const void* const viewer, const char* iblPath, float intensity) { + FLUTTER_PLUGIN_EXPORT void load_ibl(const void* const viewer, const char* iblPath, float intensity) { ((FilamentViewer*)viewer)->loadIbl(iblPath, intensity); } - FLUTTER_PLUGIN_EXPORT void remove_skybox(const void* const viewer) { + FLUTTER_PLUGIN_EXPORT void remove_skybox(const void* const viewer) { ((FilamentViewer*)viewer)->removeSkybox(); } - FLUTTER_PLUGIN_EXPORT void remove_ibl(const void* const viewer) { + FLUTTER_PLUGIN_EXPORT void remove_ibl(const void* const viewer) { ((FilamentViewer*)viewer)->removeIbl(); } @@ -76,11 +76,11 @@ extern "C" { return ((FilamentViewer*)viewer)->addLight((LightManager::Type)type, colour, intensity, posX, posY, posZ, dirX, dirY, dirZ, shadows); } - FLUTTER_PLUGIN_EXPORT void remove_light(const void* const viewer, int32_t entityId) { + FLUTTER_PLUGIN_EXPORT void remove_light(const void* const viewer, int32_t entityId) { ((FilamentViewer*)viewer)->removeLight(entityId); } - FLUTTER_PLUGIN_EXPORT void clear_lights(const void* const viewer) { + FLUTTER_PLUGIN_EXPORT void clear_lights(const void* const viewer) { ((FilamentViewer*)viewer)->clearLights(); } @@ -104,27 +104,27 @@ extern "C" { ((FilamentViewer*)viewer)->moveCameraToAsset(asset); } - FLUTTER_PLUGIN_EXPORT void set_camera_focus_distance(const void* const viewer, float distance) { + FLUTTER_PLUGIN_EXPORT void set_camera_focus_distance(const void* const viewer, float distance) { ((FilamentViewer*)viewer)->setCameraFocusDistance(distance); } - FLUTTER_PLUGIN_EXPORT void set_camera_exposure(const void* const viewer, float aperture, float shutterSpeed, float sensitivity) { + FLUTTER_PLUGIN_EXPORT void set_camera_exposure(const void* const viewer, float aperture, float shutterSpeed, float sensitivity) { ((FilamentViewer*)viewer)->setCameraExposure(aperture, shutterSpeed, sensitivity); } - FLUTTER_PLUGIN_EXPORT void set_camera_position(const void* const viewer, float x, float y, float z) { + FLUTTER_PLUGIN_EXPORT void set_camera_position(const void* const viewer, float x, float y, float z) { ((FilamentViewer*)viewer)->setCameraPosition(x, y, z); } - FLUTTER_PLUGIN_EXPORT void set_camera_rotation(const void* const viewer, float rads, float x, float y, float z) { + FLUTTER_PLUGIN_EXPORT void set_camera_rotation(const void* const viewer, float rads, float x, float y, float z) { ((FilamentViewer*)viewer)->setCameraRotation(rads, x, y, z); } - FLUTTER_PLUGIN_EXPORT void set_camera_model_matrix(const void* const viewer, const float* const matrix) { + FLUTTER_PLUGIN_EXPORT void set_camera_model_matrix(const void* const viewer, const float* const matrix) { ((FilamentViewer*)viewer)->setCameraModelMatrix(matrix); } - FLUTTER_PLUGIN_EXPORT void set_camera_focal_length(const void* const viewer, float focalLength) { + FLUTTER_PLUGIN_EXPORT void set_camera_focal_length(const void* const viewer, float focalLength) { ((FilamentViewer*)viewer)->setCameraFocalLength(focalLength); } @@ -137,46 +137,46 @@ extern "C" { ((FilamentViewer*)viewer)->render(frameTimeInNanos, pixelBuffer, callback, data); } - FLUTTER_PLUGIN_EXPORT void set_frame_interval( + FLUTTER_PLUGIN_EXPORT void set_frame_interval( const void* const viewer, float frameInterval ) { ((FilamentViewer*)viewer)->setFrameInterval(frameInterval); } - FLUTTER_PLUGIN_EXPORT void destroy_swap_chain(const void* const viewer) { + FLUTTER_PLUGIN_EXPORT void destroy_swap_chain(const void* const viewer) { ((FilamentViewer*)viewer)->destroySwapChain(); } - FLUTTER_PLUGIN_EXPORT void create_swap_chain(const void* const viewer, const void* const window, uint32_t width, uint32_t height) { + FLUTTER_PLUGIN_EXPORT void create_swap_chain(const void* const viewer, const void* const window, uint32_t width, uint32_t height) { ((FilamentViewer*)viewer)->createSwapChain(window, width, height); } - FLUTTER_PLUGIN_EXPORT void update_viewport_and_camera_projection(const void* const viewer, uint32_t width, uint32_t height, float scaleFactor) { + FLUTTER_PLUGIN_EXPORT void update_viewport_and_camera_projection(const void* const viewer, uint32_t width, uint32_t height, float scaleFactor) { return ((FilamentViewer*)viewer)->updateViewportAndCameraProjection(width, height, scaleFactor); } - FLUTTER_PLUGIN_EXPORT void scroll_update(const void* const viewer, float x, float y, float delta) { + FLUTTER_PLUGIN_EXPORT void scroll_update(const void* const viewer, float x, float y, float delta) { ((FilamentViewer*)viewer)->scrollUpdate(x, y, delta); } - FLUTTER_PLUGIN_EXPORT void scroll_begin(const void* const viewer) { + FLUTTER_PLUGIN_EXPORT void scroll_begin(const void* const viewer) { ((FilamentViewer*)viewer)->scrollBegin(); } - FLUTTER_PLUGIN_EXPORT void scroll_end(const void* const viewer) { + FLUTTER_PLUGIN_EXPORT void scroll_end(const void* const viewer) { ((FilamentViewer*)viewer)->scrollEnd(); } - FLUTTER_PLUGIN_EXPORT void grab_begin(const void* const viewer, float x, float y, bool pan) { + FLUTTER_PLUGIN_EXPORT void grab_begin(const void* const viewer, float x, float y, bool pan) { ((FilamentViewer*)viewer)->grabBegin(x, y, pan); } - FLUTTER_PLUGIN_EXPORT void grab_update(const void* const viewer, float x, float y) { + FLUTTER_PLUGIN_EXPORT void grab_update(const void* const viewer, float x, float y) { ((FilamentViewer*)viewer)->grabUpdate(x, y); } - FLUTTER_PLUGIN_EXPORT void grab_end(const void* const viewer) { + FLUTTER_PLUGIN_EXPORT void grab_end(const void* const viewer) { ((FilamentViewer*)viewer)->grabEnd(); } @@ -184,7 +184,7 @@ extern "C" { return (void*)((FilamentViewer*)viewer)->getAssetManager(); } - FLUTTER_PLUGIN_EXPORT void apply_weights( + FLUTTER_PLUGIN_EXPORT void apply_weights( void* assetManager, EntityId asset, const char* const entityName, @@ -193,7 +193,7 @@ extern "C" { // ((AssetManager*)assetManager)->setMorphTargetWeights(asset, entityName, weights, count); } - FLUTTER_PLUGIN_EXPORT void set_morph_target_weights( + FLUTTER_PLUGIN_EXPORT void set_morph_target_weights( void* assetManager, EntityId asset, const char* const entityName, @@ -288,7 +288,7 @@ extern "C" { // } - FLUTTER_PLUGIN_EXPORT void play_animation( + FLUTTER_PLUGIN_EXPORT void play_animation( void* assetManager, EntityId asset, int index, @@ -299,7 +299,7 @@ extern "C" { ((AssetManager*)assetManager)->playAnimation(asset, index, loop, reverse, replaceActive, crossfade); } - FLUTTER_PLUGIN_EXPORT void set_animation_frame( + FLUTTER_PLUGIN_EXPORT void set_animation_frame( void* assetManager, EntityId asset, int animationIndex, @@ -319,7 +319,7 @@ extern "C" { return (int)names->size(); } - FLUTTER_PLUGIN_EXPORT void get_animation_name( + FLUTTER_PLUGIN_EXPORT void get_animation_name( void* assetManager, EntityId asset, char* const outPtr, @@ -335,17 +335,17 @@ extern "C" { return (int)names->size(); } - FLUTTER_PLUGIN_EXPORT void get_morph_target_name(void* assetManager, EntityId asset, const char* meshName, char* const outPtr, int index ) { + FLUTTER_PLUGIN_EXPORT void get_morph_target_name(void* assetManager, EntityId asset, const char* meshName, char* const outPtr, int index ) { unique_ptr> names = ((AssetManager*)assetManager)->getMorphTargetNames(asset, meshName); string name = names->at(index); strcpy(outPtr, name.c_str()); } - FLUTTER_PLUGIN_EXPORT void remove_asset(const void* const viewer, EntityId asset) { + FLUTTER_PLUGIN_EXPORT void remove_asset(const void* const viewer, EntityId asset) { ((FilamentViewer*)viewer)->removeAsset(asset); } - FLUTTER_PLUGIN_EXPORT void clear_assets(const void* const viewer) { + FLUTTER_PLUGIN_EXPORT void clear_assets(const void* const viewer) { ((FilamentViewer*)viewer)->clearAssets(); } @@ -353,23 +353,23 @@ extern "C" { return ((AssetManager*)assetManager)->setMaterialColor(asset, meshName, materialIndex, r, g, b, a); } - FLUTTER_PLUGIN_EXPORT void transform_to_unit_cube(void* assetManager, EntityId asset) { + FLUTTER_PLUGIN_EXPORT void transform_to_unit_cube(void* assetManager, EntityId asset) { ((AssetManager*)assetManager)->transformToUnitCube(asset); } - FLUTTER_PLUGIN_EXPORT void set_position(void* assetManager, EntityId asset, float x, float y, float z) { + FLUTTER_PLUGIN_EXPORT void set_position(void* assetManager, EntityId asset, float x, float y, float z) { ((AssetManager*)assetManager)->setPosition(asset, x, y, z); } - FLUTTER_PLUGIN_EXPORT void set_rotation(void* assetManager, EntityId asset, float rads, float x, float y, float z) { + FLUTTER_PLUGIN_EXPORT void set_rotation(void* assetManager, EntityId asset, float rads, float x, float y, float z) { ((AssetManager*)assetManager)->setRotation(asset, rads, x, y, z); } - FLUTTER_PLUGIN_EXPORT void set_scale(void* assetManager, EntityId asset, float scale) { + FLUTTER_PLUGIN_EXPORT void set_scale(void* assetManager, EntityId asset, float scale) { ((AssetManager*)assetManager)->setScale(asset, scale); } - FLUTTER_PLUGIN_EXPORT void stop_animation(void* assetManager, EntityId asset, int index) { + FLUTTER_PLUGIN_EXPORT void stop_animation(void* assetManager, EntityId asset, int index) { ((AssetManager*)assetManager)->stopAnimation(asset, index); } diff --git a/macos/src/camutils/Bookmark.cpp b/macos/src/camutils/Bookmark.cpp new file mode 100644 index 00000000..9c16177b --- /dev/null +++ b/macos/src/camutils/Bookmark.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include + +using namespace filament::math; + +namespace filament { +namespace camutils { + +template +Bookmark Bookmark::interpolate(Bookmark a, Bookmark b, double t) { + Bookmark result; + using float3 = filament::math::vec3; + + if (a.mode == Mode::MAP) { + assert(b.mode == Mode::MAP); + const double rho = sqrt(2.0); + const double rho2 = 2, rho4 = 4; + const double ux0 = a.map.center.x, uy0 = a.map.center.y, w0 = a.map.extent; + const double ux1 = b.map.center.x, uy1 = b.map.center.y, w1 = b.map.extent; + const double dx = ux1 - ux0, dy = uy1 - uy0, d2 = dx * dx + dy * dy, d1 = sqrt(d2); + const double b0 = (w1 * w1 - w0 * w0 + rho4 * d2) / (2.0 * w0 * rho2 * d1); + const double b1 = (w1 * w1 - w0 * w0 - rho4 * d2) / (2.0 * w1 * rho2 * d1); + const double r0 = log(sqrt(b0 * b0 + 1.0) - b0); + const double r1 = log(sqrt(b1 * b1 + 1) - b1); + const double dr = r1 - r0; + const int valid = !std::isnan(dr) && dr != 0; + const double S = (valid ? dr : log(w1 / w0)) / rho; + const double s = t * S; + + // This performs Van Wijk interpolation to animate between two waypoints on a map. + if (valid) { + const double coshr0 = cosh(r0); + const double u = w0 / (rho2 * d1) * (coshr0 * tanh(rho * s + r0) - sinh(r0)); + Bookmark result; + result.map.center.x = ux0 + u * dx; + result.map.center.y = uy0 + u * dy; + result.map.extent = w0 * coshr0 / cosh(rho * s + r0); + return result; + } + + // For degenerate cases, fall back to a simplified interpolation method. + result.map.center.x = ux0 + t * dx; + result.map.center.y = uy0 + t * dy; + result.map.extent = w0 * exp(rho * s); + return result; +} + + assert(b.mode == Mode::ORBIT); + result.orbit.phi = lerp(a.orbit.phi, b.orbit.phi, FLOAT(t)); + result.orbit.theta = lerp(a.orbit.theta, b.orbit.theta, FLOAT(t)); + result.orbit.distance = lerp(a.orbit.distance, b.orbit.distance, FLOAT(t)); + result.orbit.pivot = lerp(a.orbit.pivot, b.orbit.pivot, float3(t)); + return result; +} + +// Uses the Van Wijk method to suggest a duration for animating between two waypoints on a map. +// This does not have units, so just use it as a multiplier. +template +double Bookmark::duration(Bookmark a, Bookmark b) { + assert(a.mode == Mode::ORBIT && b.mode == Mode::ORBIT); + const double rho = sqrt(2.0); + const double rho2 = 2, rho4 = 4; + const double ux0 = a.map.center.x, uy0 = a.map.center.y, w0 = a.map.extent; + const double ux1 = b.map.center.x, uy1 = b.map.center.y, w1 = b.map.extent; + const double dx = ux1 - ux0, dy = uy1 - uy0, d2 = dx * dx + dy * dy, d1 = sqrt(d2); + const double b0 = (w1 * w1 - w0 * w0 + rho4 * d2) / (2.0 * w0 * rho2 * d1); + const double b1 = (w1 * w1 - w0 * w0 - rho4 * d2) / (2.0 * w1 * rho2 * d1); + const double r0 = log(sqrt(b0 * b0 + 1.0) - b0); + const double r1 = log(sqrt(b1 * b1 + 1) - b1); + const double dr = r1 - r0; + const int valid = !std::isnan(dr) && dr != 0; + const double S = (valid ? dr : log(w1 / w0)) / rho; + return fabs(S); +} + +template class Bookmark; + +} // namespace camutils +} // namespace filament diff --git a/macos/src/camutils/FreeFlightManipulator.h b/macos/src/camutils/FreeFlightManipulator.h new file mode 100644 index 00000000..1df4dd56 --- /dev/null +++ b/macos/src/camutils/FreeFlightManipulator.h @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CAMUTILS_FREEFLIGHT_MANIPULATOR_H +#define CAMUTILS_FREEFLIGHT_MANIPULATOR_H + +#include + +#include +#include +#include +#include + +#include + +namespace filament { +namespace camutils { + +using namespace filament::math; + +template +class FreeFlightManipulator : public Manipulator { +public: + using vec2 = filament::math::vec2; + using vec3 = filament::math::vec3; + using vec4 = filament::math::vec4; + using Bookmark = filament::camutils::Bookmark; + using Base = Manipulator; + using Config = typename Base::Config; + + FreeFlightManipulator(Mode mode, const Config& props) : Base(mode, props) { + setProperties(props); + Base::mEye = Base::mProps.flightStartPosition; + const auto pitch = Base::mProps.flightStartPitch; + const auto yaw = Base::mProps.flightStartYaw; + mTargetEuler = {pitch, yaw}; + updateTarget(pitch, yaw); + } + + void setProperties(const Config& props) override { + Config resolved = props; + + if (resolved.flightPanSpeed == vec2(0, 0)) { + resolved.flightPanSpeed = vec2(0.01, 0.01); + } + if (resolved.flightMaxSpeed == 0.0) { + resolved.flightMaxSpeed = 10.0; + } + if (resolved.flightSpeedSteps == 0) { + resolved.flightSpeedSteps = 80; + } + + Base::setProperties(resolved); + } + + void updateTarget(FLOAT pitch, FLOAT yaw) { + Base::mTarget = Base::mEye + (mat3::eulerZYX(0, yaw, pitch) * vec3(0.0, 0.0, -1.0)); + } + + void grabBegin(int x, int y, bool strafe) override { + mGrabWin = {x, y}; + mGrabbing = true; + mGrabEuler = mTargetEuler; + } + + void grabUpdate(int x, int y) override { + if (!mGrabbing) { + return; + } + + const vec2 del = mGrabWin - vec2{x, y}; + + const auto& grabPitch = mGrabEuler.x; + const auto& grabYaw = mGrabEuler.y; + auto& pitch = mTargetEuler.x; + auto& yaw = mTargetEuler.y; + + constexpr double EPSILON = 0.001; + + auto panSpeed = Base::mProps.flightPanSpeed; + constexpr FLOAT minPitch = (-F_PI_2 + EPSILON); + constexpr FLOAT maxPitch = ( F_PI_2 - EPSILON); + pitch = clamp(grabPitch + del.y * -panSpeed.y, minPitch, maxPitch); + yaw = fmod(grabYaw + del.x * panSpeed.x, 2.0 * F_PI); + + updateTarget(pitch, yaw); + } + + void grabEnd() override { + mGrabbing = false; + } + + void keyDown(typename Base::Key key) override { + mKeyDown[(int) key] = true; + } + + void keyUp(typename Base::Key key) override { + mKeyDown[(int) key] = false; + } + + void scroll(int x, int y, FLOAT scrolldelta) override { + const FLOAT halfSpeedSteps = Base::mProps.flightSpeedSteps / 2; + mScrollWheel = clamp(mScrollWheel + scrolldelta, -halfSpeedSteps, halfSpeedSteps); + // Normalize the scroll position from -1 to 1 and calculate the move speed, in world + // units per second. + mScrollPositionNormalized = (mScrollWheel + halfSpeedSteps) / halfSpeedSteps - 1.0; + mMoveSpeed = pow(Base::mProps.flightMaxSpeed, mScrollPositionNormalized); + } + + void update(FLOAT deltaTime) override { + vec3 forceLocal { 0.0, 0.0, 0.0 }; + + if (mKeyDown[(int) Base::Key::FORWARD]) { + forceLocal += vec3{ 0.0, 0.0, -1.0 }; + } + if (mKeyDown[(int) Base::Key::LEFT]) { + forceLocal += vec3{ -1.0, 0.0, 0.0 }; + } + if (mKeyDown[(int) Base::Key::BACKWARD]) { + forceLocal += vec3{ 0.0, 0.0, 1.0 }; + } + if (mKeyDown[(int) Base::Key::RIGHT]) { + forceLocal += vec3{ 1.0, 0.0, 0.0 }; + } + + const mat4 orientation = mat4::lookAt(Base::mEye, Base::mTarget, Base::mProps.upVector); + vec3 forceWorld = (orientation * vec4{ forceLocal, 0.0f }).xyz; + + if (mKeyDown[(int) Base::Key::UP]) { + forceWorld += vec3{ 0.0, 1.0, 0.0 }; + } + if (mKeyDown[(int) Base::Key::DOWN]) { + forceWorld += vec3{ 0.0, -1.0, 0.0 }; + } + + forceWorld *= mMoveSpeed; + + const auto dampingFactor = Base::mProps.flightMoveDamping; + if (dampingFactor == 0.0) { + // Without damping, we simply treat the force as our velocity. + mEyeVelocity = forceWorld; + } else { + // The dampingFactor acts as "friction", which acts upon the camera in the direction + // opposite its velocity. + // Force is also multiplied by the dampingFactor, to "make up" for the friction. + // This ensures that the max velocity still approaches mMoveSpeed; + vec3 velocityDelta = (forceWorld - mEyeVelocity) * dampingFactor; + mEyeVelocity += velocityDelta * deltaTime; + } + + const vec3 positionDelta = mEyeVelocity * deltaTime; + + Base::mEye += positionDelta; + Base::mTarget += positionDelta; + } + + Bookmark getCurrentBookmark() const override { + Bookmark bookmark; + bookmark.flight.position = Base::mEye; + bookmark.flight.pitch = mTargetEuler.x; + bookmark.flight.yaw = mTargetEuler.y; + return bookmark; + } + + Bookmark getHomeBookmark() const override { + Bookmark bookmark; + bookmark.flight.position = Base::mProps.flightStartPosition; + bookmark.flight.pitch = Base::mProps.flightStartPitch; + bookmark.flight.yaw = Base::mProps.flightStartYaw; + return bookmark; + } + + void jumpToBookmark(const Bookmark& bookmark) override { + Base::mEye = bookmark.flight.position; + updateTarget(bookmark.flight.pitch, bookmark.flight.yaw); + } + +private: + vec2 mGrabWin; + vec2 mTargetEuler; // (pitch, yaw) + vec2 mGrabEuler; // (pitch, yaw) + bool mKeyDown[(int) Base::Key::COUNT] = {false}; + bool mGrabbing = false; + FLOAT mScrollWheel = 0.0f; + FLOAT mScrollPositionNormalized = 0.0f; + FLOAT mMoveSpeed = 1.0f; + vec3 mEyeVelocity; +}; + +} // namespace camutils +} // namespace filament + +#endif /* CAMUTILS_FREEFLIGHT_MANIPULATOR_H */ diff --git a/macos/src/camutils/Manipulator.cpp b/macos/src/camutils/Manipulator.cpp new file mode 100644 index 00000000..0c748b8e --- /dev/null +++ b/macos/src/camutils/Manipulator.cpp @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include "FreeFlightManipulator.h" +#include "MapManipulator.h" +#include "OrbitManipulator.h" + +using namespace filament::math; + +namespace filament { +namespace camutils { + +template typename +Manipulator::Builder& Manipulator::Builder::viewport(int width, int height) { + details.viewport[0] = width; + details.viewport[1] = height; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::targetPosition(FLOAT x, FLOAT y, FLOAT z) { + details.targetPosition = {x, y, z}; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::upVector(FLOAT x, FLOAT y, FLOAT z) { + details.upVector = {x, y, z}; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::zoomSpeed(FLOAT val) { + details.zoomSpeed = val; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::orbitHomePosition(FLOAT x, FLOAT y, FLOAT z) { + details.orbitHomePosition = {x, y, z}; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::orbitSpeed(FLOAT x, FLOAT y) { + details.orbitSpeed = {x, y}; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::fovDirection(Fov fov) { + details.fovDirection = fov; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::fovDegrees(FLOAT degrees) { + details.fovDegrees = degrees; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::farPlane(FLOAT distance) { + details.farPlane = distance; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::mapExtent(FLOAT worldWidth, FLOAT worldHeight) { + details.mapExtent = {worldWidth, worldHeight}; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::mapMinDistance(FLOAT mindist) { + details.mapMinDistance = mindist; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::flightStartPosition(FLOAT x, FLOAT y, FLOAT z) { + details.flightStartPosition = {x, y, z}; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::flightStartOrientation(FLOAT pitch, FLOAT yaw) { + details.flightStartPitch = pitch; + details.flightStartYaw = yaw; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::flightMaxMoveSpeed(FLOAT maxSpeed) { + details.flightMaxSpeed = maxSpeed; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::flightSpeedSteps(int steps) { + details.flightSpeedSteps = steps; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::flightPanSpeed(FLOAT x, FLOAT y) { + details.flightPanSpeed = {x, y}; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::flightMoveDamping(FLOAT damping) { + details.flightMoveDamping = damping; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::groundPlane(FLOAT a, FLOAT b, FLOAT c, FLOAT d) { + details.groundPlane = {a, b, c, d}; + return *this; +} + +template typename +Manipulator::Builder& Manipulator::Builder::raycastCallback(RayCallback cb, void* userdata) { + details.raycastCallback = cb; + details.raycastUserdata = userdata; + return *this; +} + +template +Manipulator* Manipulator::Builder::build(Mode mode) { + switch (mode) { + case Mode::FREE_FLIGHT: + return new FreeFlightManipulator(mode, details); + case Mode::MAP: + return new MapManipulator(mode, details); + case Mode::ORBIT: + return new OrbitManipulator(mode, details); + } +} + +template +Manipulator::Manipulator(Mode mode, const Config& props) : mMode(mode) { + setProperties(props); +} + +template +void Manipulator::setProperties(const Config& props) { + mProps = props; + + if (mProps.zoomSpeed == FLOAT(0)) { + mProps.zoomSpeed = 0.01; + } + + if (mProps.upVector == vec3(0)) { + mProps.upVector = vec3(0, 1, 0); + } + + if (mProps.fovDegrees == FLOAT(0)) { + mProps.fovDegrees = 33; + } + + if (mProps.farPlane == FLOAT(0)) { + mProps.farPlane = 5000; + } + + if (mProps.mapExtent == vec2(0)) { + mProps.mapExtent = vec2(512); + } +} + +template +void Manipulator::setViewport(int width, int height) { + Config props = mProps; + props.viewport[0] = width; + props.viewport[1] = height; + setProperties(props); +} + +template +void Manipulator::getLookAt(vec3* eyePosition, vec3* targetPosition, vec3* upward) const { + *targetPosition = mTarget; + *eyePosition = mEye; + const vec3 gaze = normalize(mTarget - mEye); + const vec3 right = cross(gaze, mProps.upVector); + *upward = cross(right, gaze); +} + +template +static bool raycastPlane(const filament::math::vec3& origin, + const filament::math::vec3& dir, FLOAT* t, void* userdata) { + using vec3 = filament::math::vec3; + using vec4 = filament::math::vec4; + auto props = (const typename Manipulator::Config*) userdata; + const vec4 plane = props->groundPlane; + const vec3 n = vec3(plane[0], plane[1], plane[2]); + const vec3 p0 = n * plane[3]; + const FLOAT denom = -dot(n, dir); + if (denom > 1e-6) { + const vec3 p0l0 = p0 - origin; + *t = dot(p0l0, n) / -denom; + return *t >= 0; + } + return false; +} + +template +void Manipulator::getRay(int x, int y, vec3* porigin, vec3* pdir) const { + const vec3 gaze = normalize(mTarget - mEye); + const vec3 right = normalize(cross(gaze, mProps.upVector)); + const vec3 upward = cross(right, gaze); + const FLOAT width = mProps.viewport[0]; + const FLOAT height = mProps.viewport[1]; + const FLOAT fov = mProps.fovDegrees * F_PI / 180.0; + + // Remap the grid coordinate into [-1, +1] and shift it to the pixel center. + const FLOAT u = 2.0 * (0.5 + x) / width - 1.0; + const FLOAT v = 2.0 * (0.5 + y) / height - 1.0; + + // Compute the tangent of the field-of-view angle as well as the aspect ratio. + const FLOAT tangent = tan(fov / 2.0); + const FLOAT aspect = width / height; + + // Adjust the gaze so it goes through the pixel of interest rather than the grid center. + vec3 dir = gaze; + if (mProps.fovDirection == Fov::VERTICAL) { + dir += right * tangent * u * aspect; + dir += upward * tangent * v; + } else { + dir += right * tangent * u; + dir += upward * tangent * v / aspect; + } + dir = normalize(dir); + + *porigin = mEye; + *pdir = dir; +} + +template +bool Manipulator::raycast(int x, int y, vec3* result) const { + vec3 origin, dir; + getRay(x, y, &origin, &dir); + + // Choose either the user's callback function or the plane intersector. + auto callback = mProps.raycastCallback; + auto fallback = raycastPlane; + void* userdata = mProps.raycastUserdata; + if (!callback) { + callback = fallback; + userdata = (void*) &mProps; + } + + // If the ray misses, then try the fallback function. + FLOAT t; + if (!callback(mEye, dir, &t, userdata)) { + if (callback == fallback || !fallback(mEye, dir, &t, (void*) &mProps)) { + return false; + } + } + + *result = mEye + dir * t; + return true; +} + +template +filament::math::vec3 Manipulator::raycastFarPlane(int x, int y) const { + const filament::math::vec3 gaze = normalize(mTarget - mEye); + const vec3 right = cross(gaze, mProps.upVector); + const vec3 upward = cross(right, gaze); + const FLOAT width = mProps.viewport[0]; + const FLOAT height = mProps.viewport[1]; + const FLOAT fov = mProps.fovDegrees * math::F_PI / 180.0; + + // Remap the grid coordinate into [-1, +1] and shift it to the pixel center. + const FLOAT u = 2.0 * (0.5 + x) / width - 1.0; + const FLOAT v = 2.0 * (0.5 + y) / height - 1.0; + + // Compute the tangent of the field-of-view angle as well as the aspect ratio. + const FLOAT tangent = tan(fov / 2.0); + const FLOAT aspect = width / height; + + // Adjust the gaze so it goes through the pixel of interest rather than the grid center. + vec3 dir = gaze; + if (mProps.fovDirection == Fov::VERTICAL) { + dir += right * tangent * u * aspect; + dir += upward * tangent * v; + } else { + dir += right * tangent * u; + dir += upward * tangent * v / aspect; + } + return mEye + dir * mProps.farPlane; +} + +template +void Manipulator::keyDown(Manipulator::Key key) { } + +template +void Manipulator::keyUp(Manipulator::Key key) { } + +template +void Manipulator::update(FLOAT deltaTime) { } + +template class Manipulator; +template class Manipulator; + +} // namespace camutils +} // namespace filament diff --git a/macos/src/camutils/MapManipulator.h b/macos/src/camutils/MapManipulator.h new file mode 100644 index 00000000..6df5b4c5 --- /dev/null +++ b/macos/src/camutils/MapManipulator.h @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CAMUTILS_MAP_MANIPULATOR_H +#define CAMUTILS_MAP_MANIPULATOR_H + +#include + +#include + +namespace filament { +namespace camutils { + +template +class MapManipulator : public Manipulator { +public: + using vec2 = math::vec2; + using vec3 = math::vec3; + using vec4 = math::vec4; + using Bookmark = filament::camutils::Bookmark; + using Base = Manipulator; + using Config = typename Manipulator::Config; + + MapManipulator(Mode mode, const Config& props) : Manipulator(mode, props) { + const FLOAT width = Base::mProps.mapExtent.x; + const FLOAT height = Base::mProps.mapExtent.y; + const bool horiz = Base::mProps.fovDirection == Fov::HORIZONTAL; + const vec3 targetToEye = Base::mProps.groundPlane.xyz; + const FLOAT halfExtent = (horiz ? width : height) / 2.0; + const FLOAT fov = Base::mProps.fovDegrees * math::F_PI / 180.0; + const FLOAT distance = halfExtent / tan(fov / 2.0); + Base::mTarget = Base::mProps.targetPosition; + Base::mEye = Base::mTarget + distance * targetToEye; + } + + void grabBegin(int x, int y, bool strafe) override { + if (strafe || !Base::raycast(x, y, &mGrabScene)) { + return; + } + mGrabFar = Base::raycastFarPlane(x, y); + mGrabEye = Base::mEye; + mGrabTarget = Base::mTarget; + mGrabbing = true; + } + + void grabUpdate(int x, int y) override { + if (mGrabbing) { + const FLOAT ulen = distance(mGrabScene, mGrabEye); + const FLOAT vlen = distance(mGrabFar, mGrabScene); + const vec3 translation = (mGrabFar - Base::raycastFarPlane(x, y)) * ulen / vlen; + const vec3 eyePosition = mGrabEye + translation; + const vec3 targetPosition = mGrabTarget + translation; + moveWithConstraints(eyePosition, targetPosition); + } + } + + void grabEnd() override { + mGrabbing = false; + } + + void scroll(int x, int y, FLOAT scrolldelta) override { + vec3 grabScene; + if (!Base::raycast(x, y, &grabScene)) { + return; + } + + // Find the direction of travel for the dolly. We do not normalize since it + // is desirable to move faster when further away from the targetPosition. + vec3 u = grabScene - Base::mEye; + + // Prevent getting stuck when zooming in. + if (scrolldelta < 0) { + const FLOAT distanceToSurface = length(u); + if (distanceToSurface < Base::mProps.zoomSpeed) { + return; + } + } + + u *= -scrolldelta * Base::mProps.zoomSpeed; + + const vec3 eyePosition = Base::mEye + u; + const vec3 targetPosition = Base::mTarget + u; + moveWithConstraints(eyePosition, targetPosition); + } + + Bookmark getCurrentBookmark() const override { + const vec3 dir = normalize(Base::mTarget - Base::mEye); + + FLOAT distance; + raycastPlane(Base::mEye, dir, &distance); + + const FLOAT fov = Base::mProps.fovDegrees * math::F_PI / 180.0; + const FLOAT halfExtent = distance * tan(fov / 2.0); + + vec3 targetPosition = Base::mEye + dir * distance; + + const vec3 targetToEye = Base::mProps.groundPlane.xyz; + const vec3 uvec = cross(Base::mProps.upVector, targetToEye); + const vec3 vvec = cross(targetToEye, uvec); + const vec3 centerToTarget = targetPosition - Base::mProps.targetPosition; + + Bookmark bookmark; + bookmark.mode = Mode::MAP; + bookmark.map.extent = halfExtent * 2.0; + bookmark.map.center.x = dot(uvec, centerToTarget); + bookmark.map.center.y = dot(vvec, centerToTarget); + + bookmark.orbit.theta = 0; + bookmark.orbit.phi = 0; + bookmark.orbit.pivot = Base::mProps.targetPosition + + uvec * bookmark.map.center.x + + vvec * bookmark.map.center.y; + bookmark.orbit.distance = halfExtent / tan(fov / 2.0); + + return bookmark; + } + + Bookmark getHomeBookmark() const override { + const FLOAT fov = Base::mProps.fovDegrees * math::F_PI / 180.0; + const FLOAT width = Base::mProps.mapExtent.x; + const FLOAT height = Base::mProps.mapExtent.y; + const bool horiz = Base::mProps.fovDirection == Fov::HORIZONTAL; + + Bookmark bookmark; + bookmark.mode = Mode::MAP; + bookmark.map.extent = horiz ? width : height; + bookmark.map.center.x = 0; + bookmark.map.center.y = 0; + + bookmark.orbit.theta = 0; + bookmark.orbit.phi = 0; + bookmark.orbit.pivot = Base::mTarget; + bookmark.orbit.distance = 0.5 * bookmark.map.extent / tan(fov / 2.0); + + // TODO: Add optional boundary constraints here. + + return bookmark; + } + + void jumpToBookmark(const Bookmark& bookmark) override { + const vec3 targetToEye = Base::mProps.groundPlane.xyz; + const FLOAT halfExtent = bookmark.map.extent / 2.0; + const FLOAT fov = Base::mProps.fovDegrees * math::F_PI / 180.0; + const FLOAT distance = halfExtent / tan(fov / 2.0); + vec3 uvec = cross(Base::mProps.upVector, targetToEye); + vec3 vvec = cross(targetToEye, uvec); + uvec = normalize(uvec) * bookmark.map.center.x; + vvec = normalize(vvec) * bookmark.map.center.y; + Base::mTarget = Base::mProps.targetPosition + uvec + vvec; + Base::mEye = Base::mTarget + distance * targetToEye; + } + +private: + bool raycastPlane(const vec3& origin, const vec3& dir, FLOAT* t) const { + const vec4 plane = Base::mProps.groundPlane; + const vec3 n = vec3(plane[0], plane[1], plane[2]); + const vec3 p0 = n * plane[3]; + const FLOAT denom = -dot(n, dir); + if (denom > 1e-6) { + const vec3 p0l0 = p0 - origin; + *t = dot(p0l0, n) / -denom; + return *t >= 0; + } + return false; + } + + void moveWithConstraints(vec3 eye, vec3 targetPosition) { + Base::mEye = eye; + Base::mTarget = targetPosition; + // TODO: Add optional boundary constraints here. + } + +private: + bool mGrabbing = false; + vec3 mGrabScene; + vec3 mGrabFar; + vec3 mGrabEye; + vec3 mGrabTarget; +}; + +} // namespace camutils +} // namespace filament + +#endif /* CAMUTILS_MAP_MANIPULATOR_H */ diff --git a/macos/src/camutils/OrbitManipulator.h b/macos/src/camutils/OrbitManipulator.h new file mode 100644 index 00000000..54e325cc --- /dev/null +++ b/macos/src/camutils/OrbitManipulator.h @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CAMUTILS_ORBIT_MANIPULATOR_H +#define CAMUTILS_ORBIT_MANIPULATOR_H + +#include + +#include + +#define MAX_PHI (F_PI / 2.0 - 0.001) + +namespace filament { +namespace camutils { + +using namespace filament::math; + +template +class OrbitManipulator : public Manipulator { +public: + using vec2 = filament::math::vec2; + using vec3 = filament::math::vec3; + using vec4 = filament::math::vec4; + using Bookmark = filament::camutils::Bookmark; + using Base = Manipulator; + using Config = typename Base::Config; + + enum GrabState { INACTIVE, ORBITING, PANNING }; + + OrbitManipulator(Mode mode, const Config& props) : Base(mode, props) { + setProperties(props); + Base::mEye = Base::mProps.orbitHomePosition; + mPivot = Base::mTarget = Base::mProps.targetPosition; + } + + void setProperties(const Config& props) override { + Config resolved = props; + + if (resolved.orbitHomePosition == vec3(0)) { + resolved.orbitHomePosition = vec3(0, 0, 1); + } + + if (resolved.orbitSpeed == vec2(0)) { + resolved.orbitSpeed = vec2(0.01); + } + + // By default, place the ground plane so that it aligns with the targetPosition position. + // This is used only when PANNING. + if (resolved.groundPlane == vec4(0)) { + const FLOAT d = length(resolved.targetPosition); + const vec3 n = normalize(resolved.orbitHomePosition - resolved.targetPosition); + resolved.groundPlane = vec4(n, -d); + } + + Base::setProperties(resolved); + } + + void grabBegin(int x, int y, bool strafe) override { + mGrabState = strafe ? PANNING : ORBITING; + mGrabPivot = mPivot; + mGrabEye = Base::mEye; + mGrabTarget = Base::mTarget; + mGrabBookmark = getCurrentBookmark(); + mGrabWinX = x; + mGrabWinY = y; + mGrabFar = Base::raycastFarPlane(x, y); + Base::raycast(x, y, &mGrabScene); + } + + void grabUpdate(int x, int y) override { + const int delx = mGrabWinX - x; + const int dely = mGrabWinY - y; + + if (mGrabState == ORBITING) { + Bookmark bookmark = getCurrentBookmark(); + + const FLOAT theta = delx * Base::mProps.orbitSpeed.x; + const FLOAT phi = dely * Base::mProps.orbitSpeed.y; + const FLOAT maxPhi = MAX_PHI; + + bookmark.orbit.phi = clamp(mGrabBookmark.orbit.phi + phi, -maxPhi, +maxPhi); + bookmark.orbit.theta = mGrabBookmark.orbit.theta + theta; + + jumpToBookmark(bookmark); + } + + if (mGrabState == PANNING) { + const FLOAT ulen = distance(mGrabScene, mGrabEye); + const FLOAT vlen = distance(mGrabFar, mGrabScene); + const vec3 translation = (mGrabFar - Base::raycastFarPlane(x, y)) * ulen / vlen; + mPivot = mGrabPivot + translation; + Base::mEye = mGrabEye + translation; + Base::mTarget = mGrabTarget + translation; + } + } + + void grabEnd() override { + mGrabState = INACTIVE; + } + + void scroll(int x, int y, FLOAT scrolldelta) override { + const vec3 gaze = normalize(Base::mTarget - Base::mEye); + const vec3 movement = gaze * Base::mProps.zoomSpeed * -scrolldelta; + const vec3 v0 = mPivot - Base::mEye; + Base::mEye += movement; + Base::mTarget += movement; + const vec3 v1 = mPivot - Base::mEye; + + // Check if the camera has moved past the point of interest. + if (dot(v0, v1) < 0) { + mFlipped = !mFlipped; + } + } + + Bookmark getCurrentBookmark() const override { + Bookmark bookmark; + bookmark.mode = Mode::ORBIT; + const vec3 pivotToEye = Base::mEye - mPivot; + const FLOAT d = length(pivotToEye); + const FLOAT x = pivotToEye.x / d; + const FLOAT y = pivotToEye.y / d; + const FLOAT z = pivotToEye.z / d; + + bookmark.orbit.phi = asin(y); + bookmark.orbit.theta = atan2(x, z); + bookmark.orbit.distance = mFlipped ? -d : d; + bookmark.orbit.pivot = mPivot; + + const FLOAT fov = Base::mProps.fovDegrees * math::F_PI / 180.0; + const FLOAT halfExtent = d * tan(fov / 2.0); + const vec3 targetToEye = Base::mProps.groundPlane.xyz; + const vec3 uvec = cross(Base::mProps.upVector, targetToEye); + const vec3 vvec = cross(targetToEye, uvec); + const vec3 centerToTarget = mPivot - Base::mProps.targetPosition; + + bookmark.map.extent = halfExtent * 2; + bookmark.map.center.x = dot(uvec, centerToTarget); + bookmark.map.center.y = dot(vvec, centerToTarget); + + return bookmark; + } + + Bookmark getHomeBookmark() const override { + Bookmark bookmark; + bookmark.mode = Mode::ORBIT; + bookmark.orbit.phi = FLOAT(0); + bookmark.orbit.theta = FLOAT(0); + bookmark.orbit.pivot = Base::mProps.targetPosition; + bookmark.orbit.distance = distance(Base::mProps.targetPosition, Base::mProps.orbitHomePosition); + + const FLOAT fov = Base::mProps.fovDegrees * math::F_PI / 180.0; + const FLOAT halfExtent = bookmark.orbit.distance * tan(fov / 2.0); + + bookmark.map.extent = halfExtent * 2; + bookmark.map.center.x = 0; + bookmark.map.center.y = 0; + + return bookmark; + } + + void jumpToBookmark(const Bookmark& bookmark) override { + mPivot = bookmark.orbit.pivot; + const FLOAT x = sin(bookmark.orbit.theta) * cos(bookmark.orbit.phi); + const FLOAT y = sin(bookmark.orbit.phi); + const FLOAT z = cos(bookmark.orbit.theta) * cos(bookmark.orbit.phi); + Base::mEye = mPivot + vec3(x, y, z) * abs(bookmark.orbit.distance); + mFlipped = bookmark.orbit.distance < 0; + Base::mTarget = Base::mEye + vec3(x, y, z) * (mFlipped ? 1.0 : -1.0); + } + +private: + GrabState mGrabState = INACTIVE; + bool mFlipped = false; + vec3 mGrabPivot; + vec3 mGrabScene; + vec3 mGrabFar; + vec3 mGrabEye; + vec3 mGrabTarget; + Bookmark mGrabBookmark; + int mGrabWinX; + int mGrabWinY; + vec3 mPivot; +}; + +} // namespace camutils +} // namespace filament + +#endif /* CAMUTILS_ORBIT_MANIPULATOR_H */