camera fixes for assets with large bounding boxes

This commit is contained in:
Nick Fisher
2023-10-11 14:12:04 +08:00
parent 2923f5907f
commit b7f50df2dc
25 changed files with 4080 additions and 1692 deletions

View File

@@ -177,7 +177,7 @@ namespace polyvox {
void loadPngTexture(string path, ResourceBuffer data); void loadPngTexture(string path, ResourceBuffer data);
void loadTextureFromPath(string path); void loadTextureFromPath(string path);
Manipulator<double>* _manipulator = nullptr;
void _createManipulator(); void _createManipulator();
uint32_t _lastFrameTimeInNanos; uint32_t _lastFrameTimeInNanos;
}; };

View File

@@ -13,7 +13,7 @@ A new flutter plugin project.
s.license = { :file => '../LICENSE' } s.license = { :file => '../LICENSE' }
s.author = { 'Your Company' => 'email@example.com' } s.author = { 'Your Company' => 'email@example.com' }
s.source = { :path => '.' } 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.public_header_files = 'include/SwiftPolyvoxFilamentPlugin-Bridging-Header.h', 'include/PolyvoxFilamentApi.h', 'include/PolyvoxFilamentFFIApi.h', 'include/ResourceBuffer.hpp', 'include/Log.hpp'
s.dependency 'Flutter' s.dependency 'Flutter'
s.platform = :ios, '12.1' s.platform = :ios, '12.1'
@@ -26,7 +26,7 @@ A new flutter plugin project.
'OTHER_CFLAGS' => '"-fvisibility=default" "$(inherited)"', '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)"', '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', '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)"', '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)"', '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)"', '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', '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)"', 'LIBRARY_SEARCH_PATHS' => '"${PODS_ROOT}/../.symlinks/plugins/polyvox_filament/ios/lib" "$(inherited)"',
} }

View File

@@ -22,6 +22,7 @@
* limitations under the License. * limitations under the License.
*/ */
#include <filament/Camera.h> #include <filament/Camera.h>
#include <backend/DriverEnums.h> #include <backend/DriverEnums.h>
#include <backend/platforms/OpenGLPlatform.h> #include <backend/platforms/OpenGLPlatform.h>
#include <filament/ColorGrading.h> #include <filament/ColorGrading.h>
@@ -86,12 +87,14 @@ using namespace gltfio;
using namespace utils; using namespace utils;
using namespace image; using namespace image;
namespace filament { namespace filament
{
class IndirectLight; class IndirectLight;
class LightManager; class LightManager;
} // namespace filament } // namespace filament
namespace polyvox { namespace polyvox
{
const double kNearPlane = 0.05; // 5 cm const double kNearPlane = 0.05; // 5 cm
const double kFarPlane = 1000.0; // 1 km const double kFarPlane = 1000.0; // 1 km
@@ -99,7 +102,8 @@ const double kFarPlane = 1000.0; // 1 km
// const float kAperture = 1.0f; // const float kAperture = 1.0f;
// const float kShutterSpeed = 1.0f; // const float kShutterSpeed = 1.0f;
// const float kSensitivity = 50.0f; // const float kSensitivity = 50.0f;
struct Vertex { struct Vertex
{
filament::math::float2 position; filament::math::float2 position;
uint32_t color; uint32_t color;
}; };
@@ -112,7 +116,8 @@ static constexpr float4 sFullScreenTriangleVertices[3] = {
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) FilamentViewer::FilamentViewer(const void *sharedContext, const ResourceLoaderWrapper *const resourceLoaderWrapper, void *const platform, const char *uberArchivePath)
: _resourceLoaderWrapper(resourceLoaderWrapper) { : _resourceLoaderWrapper(resourceLoaderWrapper)
{
ASSERT_POSTCONDITION(_resourceLoaderWrapper != nullptr, "Resource loader must be non-null"); ASSERT_POSTCONDITION(_resourceLoaderWrapper != nullptr, "Resource loader must be non-null");
@@ -193,8 +198,7 @@ FilamentViewer::FilamentViewer(const void* sharedContext, const ResourceLoaderWr
_ncm, _ncm,
_engine, _engine,
_scene, _scene,
uberArchivePath uberArchivePath);
);
_imageTexture = Texture::Builder() _imageTexture = Texture::Builder()
.width(1) .width(1)
@@ -203,7 +207,8 @@ FilamentViewer::FilamentViewer(const void* sharedContext, const ResourceLoaderWr
.format(Texture::InternalFormat::RGB16F) .format(Texture::InternalFormat::RGB16F)
.sampler(Texture::Sampler::SAMPLER_2D) .sampler(Texture::Sampler::SAMPLER_2D)
.build(*_engine); .build(*_engine);
try { try
{
_imageMaterial = _imageMaterial =
Material::Builder() Material::Builder()
.package(IMAGE_IMAGE_DATA, IMAGE_IMAGE_SIZE) .package(IMAGE_IMAGE_DATA, IMAGE_IMAGE_SIZE)
@@ -211,7 +216,9 @@ FilamentViewer::FilamentViewer(const void* sharedContext, const ResourceLoaderWr
_imageMaterial->setDefaultParameter("showImage", 0); _imageMaterial->setDefaultParameter("showImage", 0);
_imageMaterial->setDefaultParameter("backgroundColor", RgbaType::sRGB, float4(0.5f, 0.5f, 0.5f, 1.0f)); _imageMaterial->setDefaultParameter("backgroundColor", RgbaType::sRGB, float4(0.5f, 0.5f, 0.5f, 1.0f));
_imageMaterial->setDefaultParameter("image", _imageTexture, _imageSampler); _imageMaterial->setDefaultParameter("image", _imageTexture, _imageSampler);
} catch(...) { }
catch (...)
{
Log("Failed to load background image material provider"); Log("Failed to load background image material provider");
std::rethrow_exception(std::current_exception()); std::rethrow_exception(std::current_exception());
} }
@@ -250,21 +257,25 @@ FilamentViewer::FilamentViewer(const void* sharedContext, const ResourceLoaderWr
_scene->addEntity(imageEntity); _scene->addEntity(imageEntity);
} }
void FilamentViewer::setPostProcessing(bool enabled) { void FilamentViewer::setPostProcessing(bool enabled)
{
_view->setPostProcessingEnabled(enabled); _view->setPostProcessingEnabled(enabled);
} }
void FilamentViewer::setBloom(float strength) { void FilamentViewer::setBloom(float strength)
{
decltype(_view->getBloomOptions()) opts; decltype(_view->getBloomOptions()) opts;
opts.enabled = true; opts.enabled = true;
opts.strength = strength; opts.strength = strength;
_view->setBloomOptions(opts); _view->setBloomOptions(opts);
} }
void FilamentViewer::setToneMapping(ToneMapping toneMapping) { void FilamentViewer::setToneMapping(ToneMapping toneMapping)
{
ToneMapper *tm; ToneMapper *tm;
switch(toneMapping) { switch (toneMapping)
{
case ToneMapping::ACES: case ToneMapping::ACES:
Log("Setting tone mapping to ACES"); Log("Setting tone mapping to ACES");
tm = new ACESToneMapper(); tm = new ACESToneMapper();
@@ -282,21 +293,22 @@ void FilamentViewer::setToneMapping(ToneMapping toneMapping) {
return; return;
} }
auto newColorGrading = ColorGrading::Builder().toneMapper(tm).build(*_engine); auto newColorGrading = ColorGrading::Builder().toneMapper(tm).build(*_engine);
_view->setColorGrading(newColorGrading); _view->setColorGrading(newColorGrading);
_engine->destroy(colorGrading); _engine->destroy(colorGrading);
delete tm; delete tm;
} }
void FilamentViewer::setFrameInterval(float frameInterval) { void FilamentViewer::setFrameInterval(float frameInterval)
{
Renderer::FrameRateOptions fro; Renderer::FrameRateOptions fro;
fro.interval = frameInterval; fro.interval = frameInterval;
_renderer->setFrameRateOptions(fro); _renderer->setFrameRateOptions(fro);
Log("Set framerate interval to %f", frameInterval); 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) { 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(); auto light = EntityManager::get().create();
LightManager::Builder(t) LightManager::Builder(t)
.color(Color::cct(colour)) .color(Color::cct(colour))
@@ -312,30 +324,37 @@ int32_t FilamentViewer::addLight(LightManager::Type t, float colour, float inten
return entityId; return entityId;
} }
void FilamentViewer::removeLight(EntityId entityId) { void FilamentViewer::removeLight(EntityId entityId)
{
Log("Removing light with entity ID %d", entityId); Log("Removing light with entity ID %d", entityId);
auto entity = utils::Entity::import(entityId); auto entity = utils::Entity::import(entityId);
if(entity.isNull()) { if (entity.isNull())
{
Log("Error: light entity not found under ID %d", entityId); Log("Error: light entity not found under ID %d", entityId);
} else { }
else
{
remove(_lights.begin(), _lights.end(), entity); remove(_lights.begin(), _lights.end(), entity);
_scene->remove(entity); _scene->remove(entity);
EntityManager::get().destroy(1, &entity); EntityManager::get().destroy(1, &entity);
} }
} }
void FilamentViewer::clearLights() { void FilamentViewer::clearLights()
{
Log("Removing all lights"); Log("Removing all lights");
_scene->removeEntities(_lights.data(), _lights.size()); _scene->removeEntities(_lights.data(), _lights.size());
EntityManager::get().destroy(_lights.size(), _lights.data()); EntityManager::get().destroy(_lights.size(), _lights.data());
_lights.clear(); _lights.clear();
} }
static bool endsWith(string path, string ending) { static bool endsWith(string path, string ending)
{
return path.compare(path.length() - ending.length(), ending.length(), ending) == 0; return path.compare(path.length() - ending.length(), ending.length(), ending) == 0;
} }
void FilamentViewer::loadKtx2Texture(string path, ResourceBuffer rb) { void FilamentViewer::loadKtx2Texture(string path, ResourceBuffer rb)
{
// TODO - check all this // TODO - check all this
@@ -356,34 +375,36 @@ void FilamentViewer::loadKtx2Texture(string path, ResourceBuffer rb) {
// ktxreader::Ktx2Reader::TransferFunction::LINEAR); // ktxreader::Ktx2Reader::TransferFunction::LINEAR);
} }
void FilamentViewer::loadKtxTexture(string path, ResourceBuffer rb) { void FilamentViewer::loadKtxTexture(string path, ResourceBuffer rb)
{
ktxreader::Ktx1Bundle *bundle = ktxreader::Ktx1Bundle *bundle =
new ktxreader::Ktx1Bundle(static_cast<const uint8_t *>(rb.data), new ktxreader::Ktx1Bundle(static_cast<const uint8_t *>(rb.data),
static_cast<uint32_t>(rb.size)); static_cast<uint32_t>(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 // 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<void *> *callbackData = new std::vector<void *>{(void *)_resourceLoaderWrapper, rbCopy}; std::vector<void *> *callbackData = new std::vector<void *>{(void *)_resourceLoaderWrapper, rbCopy};
_imageTexture = _imageTexture =
ktxreader::Ktx1Reader::createTexture(_engine, *bundle, false, [](void* userdata) { ktxreader::Ktx1Reader::createTexture(
_engine, *bundle, false, [](void *userdata)
{
std::vector<void*>* vec = (std::vector<void*>*)userdata; std::vector<void*>* vec = (std::vector<void*>*)userdata;
ResourceLoaderWrapper* loader = (ResourceLoaderWrapper*)vec->at(0); ResourceLoaderWrapper* loader = (ResourceLoaderWrapper*)vec->at(0);
ResourceBuffer* rb = (ResourceBuffer*) vec->at(1); ResourceBuffer* rb = (ResourceBuffer*) vec->at(1);
loader->free(*rb); loader->free(*rb);
delete rb; delete rb;
delete vec; delete vec; },
}, callbackData); callbackData);
auto info = bundle->getInfo(); auto info = bundle->getInfo();
_imageWidth = info.pixelWidth; _imageWidth = info.pixelWidth;
_imageHeight = info.pixelHeight; _imageHeight = info.pixelHeight;
} }
void FilamentViewer::loadPngTexture(string path, ResourceBuffer rb) { void FilamentViewer::loadPngTexture(string path, ResourceBuffer rb)
{
polyvox::StreamBufferAdapter sb((char *)rb.data, (char *)rb.data + rb.size); polyvox::StreamBufferAdapter sb((char *)rb.data, (char *)rb.data + rb.size);
@@ -392,7 +413,8 @@ void FilamentViewer::loadPngTexture(string path, ResourceBuffer rb) {
LinearImage *image = new LinearImage(ImageDecoder::decode( LinearImage *image = new LinearImage(ImageDecoder::decode(
inputStream, path.c_str(), ImageDecoder::ColorSpace::SRGB)); inputStream, path.c_str(), ImageDecoder::ColorSpace::SRGB));
if (!image->isValid()) { if (!image->isValid())
{
Log("Invalid image : %s", path.c_str()); Log("Invalid image : %s", path.c_str());
return; return;
} }
@@ -411,7 +433,8 @@ void FilamentViewer::loadPngTexture(string path, ResourceBuffer rb) {
.build(*_engine); .build(*_engine);
Texture::PixelBufferDescriptor::Callback freeCallback = [](void *buf, size_t, Texture::PixelBufferDescriptor::Callback freeCallback = [](void *buf, size_t,
void *data) { void *data)
{
delete reinterpret_cast<LinearImage *>(data); delete reinterpret_cast<LinearImage *>(data);
}; };
@@ -426,43 +449,54 @@ void FilamentViewer::loadPngTexture(string path, ResourceBuffer rb) {
_resourceLoaderWrapper->free(rb); _resourceLoaderWrapper->free(rb);
} }
void FilamentViewer::loadTextureFromPath(string path) { void FilamentViewer::loadTextureFromPath(string path)
{
string ktxExt(".ktx"); string ktxExt(".ktx");
string ktx2Ext(".ktx2"); string ktx2Ext(".ktx2");
string pngExt(".png"); string pngExt(".png");
if (path.length() < 5) { if (path.length() < 5)
{
Log("Invalid resource path : %s", path.c_str()); Log("Invalid resource path : %s", path.c_str());
return; return;
} }
ResourceBuffer rb = _resourceLoaderWrapper->load(path.c_str()); ResourceBuffer rb = _resourceLoaderWrapper->load(path.c_str());
if(endsWith(path, ktxExt)) { if (endsWith(path, ktxExt))
{
loadKtxTexture(path, rb); loadKtxTexture(path, rb);
} else if(endsWith(path, ktx2Ext)) { }
else if (endsWith(path, ktx2Ext))
{
loadKtx2Texture(path, rb); loadKtx2Texture(path, rb);
} else if(endsWith(path, pngExt)) { }
else if (endsWith(path, pngExt))
{
loadPngTexture(path, rb); loadPngTexture(path, rb);
} }
} }
void FilamentViewer::setBackgroundColor(const float r, const float g, const float b, const float a) { void FilamentViewer::setBackgroundColor(const float r, const float g, const float b, const float a)
{
_imageMaterial->setDefaultParameter("showImage", 0); _imageMaterial->setDefaultParameter("showImage", 0);
_imageMaterial->setDefaultParameter("backgroundColor", RgbaType::sRGB, float4(r, g, b, a)); _imageMaterial->setDefaultParameter("backgroundColor", RgbaType::sRGB, float4(r, g, b, a));
_imageMaterial->setDefaultParameter("transform", _imageScale); _imageMaterial->setDefaultParameter("transform", _imageScale);
} }
void FilamentViewer::clearBackgroundImage() { void FilamentViewer::clearBackgroundImage()
{
_imageMaterial->setDefaultParameter("showImage", 0); _imageMaterial->setDefaultParameter("showImage", 0);
if (_imageTexture) { if (_imageTexture)
{
_engine->destroy(_imageTexture); _engine->destroy(_imageTexture);
_imageTexture = nullptr; _imageTexture = nullptr;
Log("Destroyed background image texture"); Log("Destroyed background image texture");
} }
} }
void FilamentViewer::setBackgroundImage(const char *resourcePath, bool fillHeight) { void FilamentViewer::setBackgroundImage(const char *resourcePath, bool fillHeight)
{
string resourcePathString(resourcePath); string resourcePathString(resourcePath);
@@ -477,13 +511,15 @@ void FilamentViewer::setBackgroundImage(const char *resourcePath, bool fillHeigh
const Viewport &vp = _view->getViewport(); const Viewport &vp = _view->getViewport();
Log("Image width %d height %d vp width %d height %d", _imageWidth, _imageHeight, vp.width, vp.height); 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 xScale = float(vp.width) / float(_imageWidth);
float yScale; float yScale;
if(fillHeight) { if (fillHeight)
{
yScale = 1.0f; yScale = 1.0f;
} else { }
else
{
yScale = float(vp.height) / float(_imageHeight); yScale = float(vp.height) / float(_imageHeight);
} }
@@ -492,17 +528,16 @@ void FilamentViewer::setBackgroundImage(const char *resourcePath, bool fillHeigh
_imageMaterial->setDefaultParameter("transform", _imageScale); _imageMaterial->setDefaultParameter("transform", _imageScale);
_imageMaterial->setDefaultParameter("image", _imageTexture, _imageSampler); _imageMaterial->setDefaultParameter("image", _imageTexture, _imageSampler);
_imageMaterial->setDefaultParameter("showImage", 1); _imageMaterial->setDefaultParameter("showImage", 1);
} }
/// ///
/// Translates the background image by (x,y) pixels. /// 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 /// 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 /// 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). /// (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) { 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). // 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. // this allows us to set a background colour for the quad when the texture has been translated outside the quad's bounds.
@@ -517,7 +552,8 @@ void FilamentViewer::setBackgroundImagePosition(float x, float y, bool clamp=fal
// y *= _imageScale[1][1]; // 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. // 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) { if (clamp)
{
Log("Clamping background image translation"); Log("Clamping background image translation");
// first, clamp x/y // first, clamp x/y
auto xScale = float(_imageWidth) / _view->getViewport().width; auto xScale = float(_imageWidth) / _view->getViewport().width;
@@ -530,20 +566,26 @@ void FilamentViewer::setBackgroundImagePosition(float x, float y, bool clamp=fal
// we need to clamp x so that it can only be translated between (left side touching viewport left) and (right side touching viewport right) // 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 width is less than viewport, these values are 0/1-xScale respectively
if(xScale < 1) { if (xScale < 1)
{
xMin = 0; xMin = 0;
xMax = 1 - xScale; xMax = 1 - xScale;
// otherwise, these value are (xScale-1 and 1-xScale) // otherwise, these value are (xScale-1 and 1-xScale)
} else { }
else
{
xMin = 1 - xScale; xMin = 1 - xScale;
xMax = 0; xMax = 0;
} }
// do the same for y // do the same for y
if(yScale < 1) { if (yScale < 1)
{
yMin = 0; yMin = 0;
yMax = 1 - yScale; yMax = 1 - yScale;
} else { }
else
{
yMin = 1 - yScale; yMin = 1 - yScale;
yMax = 0; yMax = 0;
} }
@@ -558,26 +600,27 @@ void FilamentViewer::setBackgroundImagePosition(float x, float y, bool clamp=fal
y = -y; y = -y;
Log("x %f y %f", x, 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], \ 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[1][0], _imageScale[1][1], _imageScale[1][2], _imageScale[1][3],
_imageScale[2][0],_imageScale[2][1],_imageScale[2][2], _imageScale[2][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]); _imageScale[3][0], _imageScale[3][1], _imageScale[3][2], _imageScale[3][3]);
auto transform = math::mat4f::translation(math::float3(x, y, 0.0f)) * _imageScale; 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],
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[1][0],transform[1][1],transform[1][2], transform[1][3],\ transform[2][0], transform[2][1], transform[2][2], transform[2][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]); transform[3][0], transform[3][1], transform[3][2], transform[3][3]);
_imageMaterial->setDefaultParameter("transform", transform); _imageMaterial->setDefaultParameter("transform", transform);
} }
FilamentViewer::~FilamentViewer() { FilamentViewer::~FilamentViewer()
{
clearAssets(); clearAssets();
delete _assetManager; delete _assetManager;
for(auto it : _lights) { for (auto it : _lights)
{
_engine->destroy(it); _engine->destroy(it);
} }
@@ -593,21 +636,26 @@ FilamentViewer::~FilamentViewer() {
Renderer *FilamentViewer::getRenderer() { return _renderer; } Renderer *FilamentViewer::getRenderer() { return _renderer; }
void FilamentViewer::createSwapChain(const void *window, uint32_t width, uint32_t height) { void FilamentViewer::createSwapChain(const void *window, uint32_t width, uint32_t height)
{
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
_swapChain = _engine->createSwapChain((void *)window, filament::backend::SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER); _swapChain = _engine->createSwapChain((void *)window, filament::backend::SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER);
#else #else
if(window) { if (window)
{
_swapChain = _engine->createSwapChain((void *)window, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE); _swapChain = _engine->createSwapChain((void *)window, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE);
Log("Created window swapchain."); Log("Created window swapchain.");
} else { }
else
{
Log("Created headless swapchain."); Log("Created headless swapchain.");
_swapChain = _engine->createSwapChain(width, height, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE); _swapChain = _engine->createSwapChain(width, height, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE);
} }
#endif #endif
} }
void FilamentViewer::createRenderTarget(intptr_t texture, uint32_t width, uint32_t height) { 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) // Create filament textures and render targets (note the color buffer has the import call)
_rtColor = filament::Texture::Builder() _rtColor = filament::Texture::Builder()
.width(width) .width(width)
@@ -633,11 +681,12 @@ void FilamentViewer::createRenderTarget(intptr_t texture, uint32_t width, uint32
_view->setRenderTarget(_rt); _view->setRenderTarget(_rt);
Log("Set render target for texture id %u to %u x %u", texture, width, height); Log("Set render target for texture id %u to %u x %u", texture, width, height);
} }
void FilamentViewer::destroySwapChain() { void FilamentViewer::destroySwapChain()
if(_rt) { {
if (_rt)
{
_view->setRenderTarget(nullptr); _view->setRenderTarget(nullptr);
_engine->destroy(_rtDepth); _engine->destroy(_rtDepth);
_engine->destroy(_rtColor); _engine->destroy(_rtColor);
@@ -646,16 +695,19 @@ void FilamentViewer::destroySwapChain() {
_rtDepth = nullptr; _rtDepth = nullptr;
_rtColor = nullptr; _rtColor = nullptr;
} }
if (_swapChain) { if (_swapChain)
{
_engine->destroy(_swapChain); _engine->destroy(_swapChain);
_swapChain = nullptr; _swapChain = nullptr;
Log("Swapchain destroyed."); Log("Swapchain destroyed.");
} }
} }
void FilamentViewer::clearAssets() { void FilamentViewer::clearAssets()
{
Log("Clearing all assets"); Log("Clearing all assets");
if(_mainCamera) { if (_mainCamera)
{
_view->setCamera(_mainCamera); _view->setCamera(_mainCamera);
} }
@@ -664,7 +716,8 @@ void FilamentViewer::clearAssets() {
Log("Cleared all assets"); Log("Cleared all assets");
} }
void FilamentViewer::removeAsset(EntityId asset) { void FilamentViewer::removeAsset(EntityId asset)
{
Log("Removing asset from scene"); Log("Removing asset from scene");
mtx.lock(); mtx.lock();
@@ -677,7 +730,8 @@ void FilamentViewer::removeAsset(EntityId asset) {
/// ///
/// Set the exposure for the current active camera. /// Set the exposure for the current active camera.
/// ///
void FilamentViewer::setCameraExposure(float aperture, float shutterSpeed, float sensitivity) { void FilamentViewer::setCameraExposure(float aperture, float shutterSpeed, float sensitivity)
{
Camera &cam = _view->getCamera(); Camera &cam = _view->getCamera();
Log("Setting aperture (%03f) shutterSpeed (%03f) and sensitivity (%03f)", aperture, shutterSpeed, sensitivity); Log("Setting aperture (%03f) shutterSpeed (%03f) and sensitivity (%03f)", aperture, shutterSpeed, sensitivity);
cam.setExposure(aperture, shutterSpeed, sensitivity); cam.setExposure(aperture, shutterSpeed, sensitivity);
@@ -686,7 +740,8 @@ void FilamentViewer::setCameraExposure(float aperture, float shutterSpeed, float
/// ///
/// Set the focal length of the active camera. /// Set the focal length of the active camera.
/// ///
void FilamentViewer::setCameraFocalLength(float focalLength) { void FilamentViewer::setCameraFocalLength(float focalLength)
{
Camera &cam = _view->getCamera(); Camera &cam = _view->getCamera();
_cameraFocalLength = focalLength; _cameraFocalLength = focalLength;
cam.setLensProjection(_cameraFocalLength, 1.0f, kNearPlane, cam.setLensProjection(_cameraFocalLength, 1.0f, kNearPlane,
@@ -696,7 +751,8 @@ void FilamentViewer::setCameraFocalLength(float focalLength) {
/// ///
/// Set the focus distance of the active camera. /// Set the focus distance of the active camera.
/// ///
void FilamentViewer::setCameraFocusDistance(float focusDistance) { void FilamentViewer::setCameraFocusDistance(float focusDistance)
{
Camera &cam = _view->getCamera(); Camera &cam = _view->getCamera();
_cameraFocusDistance = focusDistance; _cameraFocusDistance = focusDistance;
cam.setFocusDistance(_cameraFocusDistance); cam.setFocusDistance(_cameraFocusDistance);
@@ -707,15 +763,18 @@ void FilamentViewer::setCameraFocusDistance(float focusDistance) {
/// N.B. Blender will generally export a three-node hierarchy - /// N.B. Blender will generally export a three-node hierarchy -
/// Camera1->Camera_Orientation->Camera2. The correct name will be the Camera_Orientation. /// Camera1->Camera_Orientation->Camera2. The correct name will be the Camera_Orientation.
/// ///
bool FilamentViewer::setCamera(EntityId entityId, const char *cameraName) { bool FilamentViewer::setCamera(EntityId entityId, const char *cameraName)
{
auto asset = _assetManager->getAssetByEntityId(entityId); auto asset = _assetManager->getAssetByEntityId(entityId);
if(!asset) { if (!asset)
{
Log("Failed to find asset under entity id %d.", entityId); Log("Failed to find asset under entity id %d.", entityId);
return false; return false;
} }
size_t count = asset->getCameraEntityCount(); size_t count = asset->getCameraEntityCount();
if (count == 0) { if (count == 0)
{
Log("No cameras found attached to specified entity."); Log("No cameras found attached to specified entity.");
return false; return false;
} }
@@ -724,28 +783,35 @@ bool FilamentViewer::setCamera(EntityId entityId, const char *cameraName) {
utils::Entity target; utils::Entity target;
if(!cameraName) { if (!cameraName)
{
auto inst = _ncm->getInstance(cameras[0]); auto inst = _ncm->getInstance(cameras[0]);
const char *name = _ncm->getName(inst); const char *name = _ncm->getName(inst);
target = cameras[0]; target = cameras[0];
Log("No camera specified, using first camera node found (%s)", name); Log("No camera specified, using first camera node found (%s)", name);
} else { }
for (int j = 0; j < count; j++) { else
{
for (int j = 0; j < count; j++)
{
auto inst = _ncm->getInstance(cameras[j]); auto inst = _ncm->getInstance(cameras[j]);
const char *name = _ncm->getName(inst); const char *name = _ncm->getName(inst);
if (strcmp(name, cameraName) == 0) { if (strcmp(name, cameraName) == 0)
{
target = cameras[j]; target = cameras[j];
break; break;
} }
} }
} }
if(target.isNull()) { if (target.isNull())
{
Log("Unable to locate camera under name %s ", cameraName); Log("Unable to locate camera under name %s ", cameraName);
return false; return false;
} }
Camera *camera = _engine->getCameraComponent(target); Camera *camera = _engine->getCameraComponent(target);
if(!camera) { if (!camera)
{
Log("Failed to retrieve camera component for target"); Log("Failed to retrieve camera component for target");
return false; return false;
} }
@@ -761,11 +827,13 @@ bool FilamentViewer::setCamera(EntityId entityId, const char *cameraName) {
return true; return true;
} }
void FilamentViewer::loadSkybox(const char *const skyboxPath) { void FilamentViewer::loadSkybox(const char *const skyboxPath)
{
removeSkybox(); removeSkybox();
if (!skyboxPath) { if (!skyboxPath)
{
Log("No skybox path provided, removed skybox."); Log("No skybox path provided, removed skybox.");
} }
@@ -776,7 +844,8 @@ void FilamentViewer::loadSkybox(const char *const skyboxPath) {
// because this will go out of scope before the texture callback is invoked, we need to make a copy to the heap // 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); ResourceBuffer *skyboxBufferCopy = new ResourceBuffer(skyboxBuffer);
if(skyboxBuffer.size <= 0) { if (skyboxBuffer.size <= 0)
{
Log("Could not load skybox resource."); Log("Could not load skybox resource.");
return; return;
} }
@@ -790,37 +859,43 @@ void FilamentViewer::loadSkybox(const char *const skyboxPath) {
static_cast<uint32_t>(skyboxBuffer.size)); static_cast<uint32_t>(skyboxBuffer.size));
_skyboxTexture = _skyboxTexture =
ktxreader::Ktx1Reader::createTexture(_engine, *skyboxBundle, false, [](void* userdata) { ktxreader::Ktx1Reader::createTexture(
_engine, *skyboxBundle, false, [](void *userdata)
{
std::vector<void*>* vec = (std::vector<void*>*)userdata; std::vector<void*>* vec = (std::vector<void*>*)userdata;
ResourceLoaderWrapper* loader = (ResourceLoaderWrapper*)vec->at(0); ResourceLoaderWrapper* loader = (ResourceLoaderWrapper*)vec->at(0);
ResourceBuffer* rb = (ResourceBuffer*) vec->at(1); ResourceBuffer* rb = (ResourceBuffer*) vec->at(1);
loader->free(*rb); loader->free(*rb);
delete rb; delete rb;
delete vec; delete vec;
Log("Skybox load complete."); Log("Skybox load complete."); },
}, callbackData); callbackData);
_skybox = _skybox =
filament::Skybox::Builder().environment(_skyboxTexture).build(*_engine); filament::Skybox::Builder().environment(_skyboxTexture).build(*_engine);
_scene->setSkybox(_skybox); _scene->setSkybox(_skybox);
} }
void FilamentViewer::removeSkybox() { void FilamentViewer::removeSkybox()
{
Log("Removing skybox"); Log("Removing skybox");
_scene->setSkybox(nullptr); _scene->setSkybox(nullptr);
if(_skybox) { if (_skybox)
{
_engine->destroy(_skybox); _engine->destroy(_skybox);
_skybox = nullptr; _skybox = nullptr;
} }
if(_skyboxTexture) { if (_skyboxTexture)
{
_engine->destroy(_skyboxTexture); _engine->destroy(_skyboxTexture);
_skyboxTexture = nullptr; _skyboxTexture = nullptr;
} }
} }
void FilamentViewer::removeIbl() { void FilamentViewer::removeIbl()
if(_indirectLight) { {
if (_indirectLight)
{
_engine->destroy(_indirectLight); _engine->destroy(_indirectLight);
_engine->destroy(_iblTexture); _engine->destroy(_iblTexture);
_indirectLight = nullptr; _indirectLight = nullptr;
@@ -829,9 +904,11 @@ void FilamentViewer::removeIbl() {
_scene->setIndirectLight(nullptr); _scene->setIndirectLight(nullptr);
} }
void FilamentViewer::loadIbl(const char *const iblPath, float intensity) { void FilamentViewer::loadIbl(const char *const iblPath, float intensity)
{
removeIbl(); removeIbl();
if (iblPath) { if (iblPath)
{
Log("Loading IBL from %s", iblPath); Log("Loading IBL from %s", iblPath);
// Load IBL. // Load IBL.
@@ -839,7 +916,8 @@ void FilamentViewer::loadIbl(const char *const iblPath, float intensity) {
// because this will go out of scope before the texture callback is invoked, we need to make a copy to the heap // 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); ResourceBuffer *iblBufferCopy = new ResourceBuffer(iblBuffer);
if(iblBuffer.size == 0) { if (iblBuffer.size == 0)
{
Log("Error loading IBL, resource could not be loaded."); Log("Error loading IBL, resource could not be loaded.");
return; return;
} }
@@ -853,14 +931,16 @@ void FilamentViewer::loadIbl(const char *const iblPath, float intensity) {
std::vector<void *> *callbackData = new std::vector<void *>{(void *)_resourceLoaderWrapper, iblBufferCopy}; std::vector<void *> *callbackData = new std::vector<void *>{(void *)_resourceLoaderWrapper, iblBufferCopy};
_iblTexture = _iblTexture =
ktxreader::Ktx1Reader::createTexture(_engine, *iblBundle, false, [](void* userdata) { ktxreader::Ktx1Reader::createTexture(
_engine, *iblBundle, false, [](void *userdata)
{
std::vector<void*>* vec = (std::vector<void*>*)userdata; std::vector<void*>* vec = (std::vector<void*>*)userdata;
ResourceLoaderWrapper* loader = (ResourceLoaderWrapper*)vec->at(0); ResourceLoaderWrapper* loader = (ResourceLoaderWrapper*)vec->at(0);
ResourceBuffer* rb = (ResourceBuffer*) vec->at(1); ResourceBuffer* rb = (ResourceBuffer*) vec->at(1);
loader->free(*rb); loader->free(*rb);
delete rb; delete rb;
delete vec; delete vec; },
}, callbackData); callbackData);
_indirectLight = IndirectLight::Builder() _indirectLight = IndirectLight::Builder()
.reflections(_iblTexture) .reflections(_iblTexture)
.irradiance(3, harmonics) .irradiance(3, harmonics)
@@ -879,14 +959,17 @@ void FilamentViewer::render(
uint64_t frameTimeInNanos, uint64_t frameTimeInNanos,
void *pixelBuffer, void *pixelBuffer,
void (*callback)(void *buf, size_t size, void *data), void (*callback)(void *buf, size_t size, void *data),
void* data) { void *data)
{
if (!_view || !_mainCamera || !_swapChain) { if (!_view || !_mainCamera || !_swapChain)
{
Log("Not ready for rendering"); Log("Not ready for rendering");
return; return;
} }
if(_frameCount == 60) { if (_frameCount == 60)
{
// Log("1 sec average for asset animation update %f", _elapsed / 60); // Log("1 sec average for asset animation update %f", _elapsed / 60);
_elapsed = 0; _elapsed = 0;
_frameCount = 0; _frameCount = 0;
@@ -899,7 +982,18 @@ void FilamentViewer::render(
_elapsed += tmr.elapsed(); _elapsed += tmr.elapsed();
_frameCount++; _frameCount++;
if(pixelBuffer) { // 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( auto pbd = Texture::PixelBufferDescriptor(
pixelBuffer, size_t(1024 * 768 * 4), pixelBuffer, size_t(1024 * 768 * 4),
Texture::Format::RGBA, Texture::Format::RGBA,
@@ -909,21 +1003,27 @@ void FilamentViewer::render(
_renderer->render(_view); _renderer->render(_view);
_renderer->readPixels(0, 0, 1024, 768, std::move(pbd)); _renderer->readPixels(0, 0, 1024, 768, std::move(pbd));
_renderer->endFrame(); _renderer->endFrame();
} else { }
else
{
// Render the scene, unless the renderer wants to skip the frame. // Render the scene, unless the renderer wants to skip the frame.
if (_renderer->beginFrame(_swapChain, frameTimeInNanos)) { if (_renderer->beginFrame(_swapChain, frameTimeInNanos))
{
_renderer->render(_view); _renderer->render(_view);
_renderer->endFrame(); _renderer->endFrame();
} else { }
else
{
// skipped frame // skipped frame
} }
} }
} }
void FilamentViewer::updateViewportAndCameraProjection( void FilamentViewer::updateViewportAndCameraProjection(
int width, int height, float contentScaleFactor) { int width, int height, float contentScaleFactor)
if (!_view || !_mainCamera) { {
if (!_view || !_mainCamera)
{
Log("Skipping camera update, no view or camrea"); Log("Skipping camera update, no view or camrea");
return; return;
} }
@@ -944,20 +1044,24 @@ void FilamentViewer::updateViewportAndCameraProjection(
contentScaleFactor); contentScaleFactor);
} }
void FilamentViewer::setViewFrustumCulling(bool enabled) { void FilamentViewer::setViewFrustumCulling(bool enabled)
{
_view->setFrustumCullingEnabled(enabled); _view->setFrustumCullingEnabled(enabled);
} }
void FilamentViewer::setCameraPosition(float x, float y, float z) { void FilamentViewer::setCameraPosition(float x, float y, float z)
{
Camera &cam = _view->getCamera(); Camera &cam = _view->getCamera();
_cameraPosition = math::mat4f::translation(math::float3(x, y, z)); _cameraPosition = math::mat4f::translation(math::float3(x, y, z));
cam.setModelMatrix(_cameraPosition * _cameraRotation); cam.setModelMatrix(_cameraPosition * _cameraRotation);
} }
void FilamentViewer::moveCameraToAsset(EntityId entityId) { void FilamentViewer::moveCameraToAsset(EntityId entityId)
{
auto asset = _assetManager->getAssetByEntityId(entityId); auto asset = _assetManager->getAssetByEntityId(entityId);
if(!asset) { if (!asset)
{
Log("Failed to find asset attached to specified entity id."); Log("Failed to find asset attached to specified entity id.");
return; return;
} }
@@ -971,13 +1075,15 @@ void FilamentViewer::moveCameraToAsset(EntityId entityId) {
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()); 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) { void FilamentViewer::setCameraRotation(float rads, float x, float y, float z)
{
Camera &cam = _view->getCamera(); Camera &cam = _view->getCamera();
_cameraRotation = math::mat4f::rotation(rads, math::float3(x, y, z)); _cameraRotation = math::mat4f::rotation(rads, math::float3(x, y, z));
cam.setModelMatrix(_cameraPosition * _cameraRotation); cam.setModelMatrix(_cameraPosition * _cameraRotation);
} }
void FilamentViewer::setCameraModelMatrix(const float* const matrix) { void FilamentViewer::setCameraModelMatrix(const float *const matrix)
{
Camera &cam = _view->getCamera(); Camera &cam = _view->getCamera();
mat4 modelMatrix( mat4 modelMatrix(
@@ -996,75 +1102,123 @@ void FilamentViewer::setCameraModelMatrix(const float* const matrix) {
matrix[12], matrix[12],
matrix[13], matrix[13],
matrix[14], matrix[14],
matrix[15] matrix[15]);
);
cam.setModelMatrix(modelMatrix); cam.setModelMatrix(modelMatrix);
} }
void FilamentViewer::grabBegin(float x, float y, bool pan) { void FilamentViewer::_createManipulator()
if (!_view || !_mainCamera || !_swapChain) { {
Log("View not ready, ignoring grab"); Camera &cam = _view->getCamera();
return; math::double3 home = cam.getPosition();
} math::double3 up = cam.getUpVector();
_panning = pan; math::double3 target = home + cam.getForwardVector();
_startX = x; Viewport const &vp = _view->getViewport();
_startY = y; 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<double>::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::grabUpdate(float x, float y) { void FilamentViewer::grabBegin(float x, float y, bool pan)
if (!_view || !_swapChain) { {
if (!_view || !_mainCamera || !_swapChain)
{
Log("View not ready, ignoring grab"); Log("View not ready, ignoring grab");
return; return;
} }
Camera& cam =_view->getCamera(); if (!_manipulator)
auto eye = cam.getPosition(); {
auto target = eye + cam.getForwardVector(); _createManipulator();
auto upward = cam.getUpVector(); }
Viewport const& vp = _view->getViewport(); if(pan) {
if(_panning) { Log("Beginning pan at %f %f", x, y);
auto trans = cam.getModelMatrix() * mat4::translation(math::float3 { 50 * (x - _startX) / vp.width, 50 * (y - _startY) / vp.height, 0.0f });
cam.setModelMatrix(trans);
} else { } else {
auto trans = cam.getModelMatrix() * mat4::rotation(0.05, Log("Beginning rotate at %f %f", x, y);
math::float3 { (y - _startY) / vp.height, (x - _startX) / vp.width, 0.0f });
cam.setModelMatrix(trans);
}
_startX = x;
_startY = y;
} }
void FilamentViewer::grabEnd() { _manipulator->grabBegin(x, y, pan);
if (!_view || !_mainCamera || !_swapChain) { }
void FilamentViewer::grabUpdate(float x, float y)
{
if (!_view || !_swapChain)
{
Log("View not ready, ignoring grab"); Log("View not ready, ignoring grab");
return; 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::scrollBegin() { void FilamentViewer::grabEnd()
// noop {
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::scrollUpdate(float x, float y, float delta) { void FilamentViewer::scrollBegin()
Camera& cam =_view->getCamera(); {
Viewport const& vp = _view->getViewport(); if (!_manipulator)
auto trans = cam.getModelMatrix() * mat4::translation(math::float3 {0.0f, 0.0f, delta }); {
cam.setModelMatrix(trans); _createManipulator();
}
} }
void FilamentViewer::scrollEnd() { void FilamentViewer::scrollUpdate(float x, float y, float delta)
// noop {
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::pick(uint32_t x, uint32_t y, EntityId* entityId) { void FilamentViewer::scrollEnd()
Log("Picking at %d,%d", x, y); {
delete _manipulator;
_manipulator = nullptr;
}
void FilamentViewer::pick(uint32_t x, uint32_t y, EntityId *entityId)
{
_view->pick(x, y, [=](filament::View::PickingQueryResult const &result) { _view->pick(x, y, [=](filament::View::PickingQueryResult const &result) {
*entityId = Entity::smuggle(result.renderable); *entityId = Entity::smuggle(result.renderable);
Log("Got result %d", *entityId);
}); });
} }
} // namespace polyvox } // namespace polyvox

View File

@@ -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 <camutils/Bookmark.h>
#include <camutils/Manipulator.h>
#include <math/scalar.h>
#include <math/vec3.h>
using namespace filament::math;
namespace filament {
namespace camutils {
template <typename FLOAT>
Bookmark<FLOAT> Bookmark<FLOAT>::interpolate(Bookmark<FLOAT> a, Bookmark<FLOAT> b, double t) {
Bookmark<FLOAT> result;
using float3 = filament::math::vec3<FLOAT>;
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<FLOAT> 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 <typename FLOAT>
double Bookmark<FLOAT>::duration(Bookmark<FLOAT> a, Bookmark<FLOAT> 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<float>;
} // namespace camutils
} // namespace filament

View File

@@ -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 <camutils/Manipulator.h>
#include <math/scalar.h>
#include <math/mat3.h>
#include <math/mat4.h>
#include <math/quat.h>
#include <cmath>
namespace filament {
namespace camutils {
using namespace filament::math;
template<typename FLOAT>
class FreeFlightManipulator : public Manipulator<FLOAT> {
public:
using vec2 = filament::math::vec2<FLOAT>;
using vec3 = filament::math::vec3<FLOAT>;
using vec4 = filament::math::vec4<FLOAT>;
using Bookmark = filament::camutils::Bookmark<FLOAT>;
using Base = Manipulator<FLOAT>;
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 */

View File

@@ -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 <camutils/Manipulator.h>
#include <math/scalar.h>
#include "FreeFlightManipulator.h"
#include "MapManipulator.h"
#include "OrbitManipulator.h"
using namespace filament::math;
namespace filament {
namespace camutils {
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::viewport(int width, int height) {
details.viewport[0] = width;
details.viewport[1] = height;
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::targetPosition(FLOAT x, FLOAT y, FLOAT z) {
details.targetPosition = {x, y, z};
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::upVector(FLOAT x, FLOAT y, FLOAT z) {
details.upVector = {x, y, z};
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::zoomSpeed(FLOAT val) {
details.zoomSpeed = val;
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::orbitHomePosition(FLOAT x, FLOAT y, FLOAT z) {
details.orbitHomePosition = {x, y, z};
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::orbitSpeed(FLOAT x, FLOAT y) {
details.orbitSpeed = {x, y};
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::fovDirection(Fov fov) {
details.fovDirection = fov;
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::fovDegrees(FLOAT degrees) {
details.fovDegrees = degrees;
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::farPlane(FLOAT distance) {
details.farPlane = distance;
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::mapExtent(FLOAT worldWidth, FLOAT worldHeight) {
details.mapExtent = {worldWidth, worldHeight};
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::mapMinDistance(FLOAT mindist) {
details.mapMinDistance = mindist;
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::flightStartPosition(FLOAT x, FLOAT y, FLOAT z) {
details.flightStartPosition = {x, y, z};
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::flightStartOrientation(FLOAT pitch, FLOAT yaw) {
details.flightStartPitch = pitch;
details.flightStartYaw = yaw;
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::flightMaxMoveSpeed(FLOAT maxSpeed) {
details.flightMaxSpeed = maxSpeed;
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::flightSpeedSteps(int steps) {
details.flightSpeedSteps = steps;
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::flightPanSpeed(FLOAT x, FLOAT y) {
details.flightPanSpeed = {x, y};
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::flightMoveDamping(FLOAT damping) {
details.flightMoveDamping = damping;
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::groundPlane(FLOAT a, FLOAT b, FLOAT c, FLOAT d) {
details.groundPlane = {a, b, c, d};
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::raycastCallback(RayCallback cb, void* userdata) {
details.raycastCallback = cb;
details.raycastUserdata = userdata;
return *this;
}
template <typename FLOAT>
Manipulator<FLOAT>* Manipulator<FLOAT>::Builder::build(Mode mode) {
switch (mode) {
case Mode::FREE_FLIGHT:
return new FreeFlightManipulator<FLOAT>(mode, details);
case Mode::MAP:
return new MapManipulator<FLOAT>(mode, details);
case Mode::ORBIT:
return new OrbitManipulator<FLOAT>(mode, details);
}
}
template <typename FLOAT>
Manipulator<FLOAT>::Manipulator(Mode mode, const Config& props) : mMode(mode) {
setProperties(props);
}
template <typename FLOAT>
void Manipulator<FLOAT>::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 <typename FLOAT>
void Manipulator<FLOAT>::setViewport(int width, int height) {
Config props = mProps;
props.viewport[0] = width;
props.viewport[1] = height;
setProperties(props);
}
template <typename FLOAT>
void Manipulator<FLOAT>::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<typename FLOAT>
static bool raycastPlane(const filament::math::vec3<FLOAT>& origin,
const filament::math::vec3<FLOAT>& dir, FLOAT* t, void* userdata) {
using vec3 = filament::math::vec3<FLOAT>;
using vec4 = filament::math::vec4<FLOAT>;
auto props = (const typename Manipulator<FLOAT>::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 <typename FLOAT>
void Manipulator<FLOAT>::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 <typename FLOAT>
bool Manipulator<FLOAT>::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<FLOAT>;
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 <typename FLOAT>
filament::math::vec3<FLOAT> Manipulator<FLOAT>::raycastFarPlane(int x, int y) const {
const filament::math::vec3<FLOAT> 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 <typename FLOAT>
void Manipulator<FLOAT>::keyDown(Manipulator<FLOAT>::Key key) { }
template <typename FLOAT>
void Manipulator<FLOAT>::keyUp(Manipulator<FLOAT>::Key key) { }
template <typename FLOAT>
void Manipulator<FLOAT>::update(FLOAT deltaTime) { }
template class Manipulator<float>;
template class Manipulator<double>;
} // namespace camutils
} // namespace filament

View File

@@ -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 <camutils/Manipulator.h>
#include <math/vec3.h>
namespace filament {
namespace camutils {
template<typename FLOAT>
class MapManipulator : public Manipulator<FLOAT> {
public:
using vec2 = math::vec2<FLOAT>;
using vec3 = math::vec3<FLOAT>;
using vec4 = math::vec4<FLOAT>;
using Bookmark = filament::camutils::Bookmark<FLOAT>;
using Base = Manipulator<FLOAT>;
using Config = typename Manipulator<FLOAT>::Config;
MapManipulator(Mode mode, const Config& props) : Manipulator<FLOAT>(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 */

View File

@@ -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 <camutils/Manipulator.h>
#include <math/scalar.h>
#define MAX_PHI (F_PI / 2.0 - 0.001)
namespace filament {
namespace camutils {
using namespace filament::math;
template<typename FLOAT>
class OrbitManipulator : public Manipulator<FLOAT> {
public:
using vec2 = filament::math::vec2<FLOAT>;
using vec3 = filament::math::vec3<FLOAT>;
using vec4 = filament::math::vec4<FLOAT>;
using Bookmark = filament::camutils::Bookmark<FLOAT>;
using Base = Manipulator<FLOAT>;
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 */

View File

@@ -197,7 +197,7 @@ abstract class FilamentController {
/// ///
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself. /// 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. /// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.

View File

@@ -191,16 +191,45 @@ class FilamentControllerFFI extends FilamentController {
_isReadyForScene.complete(true); _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 @override
Future resize(int width, int height, {double scaleFactor = 1.0}) async { Future resize(int width, int height, {double scaleFactor = 1.0}) async {
_resizing = true; _resizing = true;
setRendering(false); setRendering(false);
_lib.destroy_swap_chain(_viewer!);
await destroyTexture();
size = ui.Size(width * _pixelRatio, height * _pixelRatio); size = ui.Size(width * _pixelRatio, height * _pixelRatio);
_textureId = await _channel
.invokeMethod("resize", [size.width, size.height, scaleFactor]); var textures =
_textureIdController.add(_textureId); 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<Void>.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( _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; _resizing = false;
setRendering(true); setRendering(true);
} }
@@ -553,11 +582,11 @@ class FilamentControllerFFI extends FilamentController {
} }
@override @override
Future zoomUpdate(double z) async { Future zoomUpdate(double x, double y, double z) async {
if (_viewer == null || _resizing) { if (_viewer == null || _resizing) {
throw Exception("No viewer available, ignoring"); throw Exception("No viewer available, ignoring");
} }
_lib.scroll_update(_viewer!, 0.0, 0.0, z); _lib.scroll_update(_viewer!, x, y, z);
} }
@override @override

View File

@@ -434,11 +434,11 @@ class FilamentControllerMethodChannel extends FilamentController {
await _channel.invokeMethod("scrollBegin"); await _channel.invokeMethod("scrollBegin");
} }
Future zoomUpdate(double z) async { Future zoomUpdate(double x, double y, double z) async {
if (_viewer == null || _resizing) { if (_viewer == null || _resizing) {
throw Exception("No viewer available, ignoring"); 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 { Future zoomEnd() async {

View File

@@ -38,15 +38,12 @@ class FilamentGestureDetector extends StatelessWidget {
/// ///
final bool listenerEnabled; final bool listenerEnabled;
final double zoomDelta;
const FilamentGestureDetector( const FilamentGestureDetector(
{Key? key, {Key? key,
required this.controller, required this.controller,
this.child, this.child,
this.showControlOverlay = false, this.showControlOverlay = false,
this.listenerEnabled = true, this.listenerEnabled = true})
this.zoomDelta = 1})
: super(key: key); : super(key: key);
@override @override

View File

@@ -32,15 +32,12 @@ class FilamentGestureDetectorDesktop extends StatefulWidget {
/// ///
final bool listenerEnabled; final bool listenerEnabled;
final double zoomDelta;
const FilamentGestureDetectorDesktop( const FilamentGestureDetectorDesktop(
{Key? key, {Key? key,
required this.controller, required this.controller,
this.child, this.child,
this.showControlOverlay = false, this.showControlOverlay = false,
this.listenerEnabled = true, this.listenerEnabled = true})
this.zoomDelta = 1})
: super(key: key); : super(key: key);
@override @override
@@ -71,15 +68,18 @@ class _FilamentGestureDetectorDesktopState
/// ///
/// Scroll-wheel on desktop, interpreted as zoom /// Scroll-wheel on desktop, interpreted as zoom
/// ///
void _zoom(PointerScrollEvent pointerSignal) { void _zoom(PointerScrollEvent pointerSignal) async {
_scrollTimer?.cancel(); _scrollTimer?.cancel();
widget.controller.zoomBegin(); await widget.controller.zoomBegin();
widget.controller.zoomUpdate(pointerSignal.scrollDelta.dy > 0 await widget.controller.zoomUpdate(
? widget.zoomDelta pointerSignal.localPosition.dx,
: -widget.zoomDelta); pointerSignal.localPosition.dy,
_scrollTimer = Timer(const Duration(milliseconds: 100), () { pointerSignal.scrollDelta.dy > 0 ? 1 : -1);
widget.controller.zoomEnd();
_scrollTimer = null; // 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 this is the first move event, we need to call rotateStart/panStart to set the first coordinates
if (!_pointerMoving) { if (!_pointerMoving) {
if (d.buttons == kTertiaryButton) { if (d.buttons == kTertiaryButton) {
widget.controller.rotateStart(d.position.dx, d.position.dy); widget.controller
.rotateStart(d.localPosition.dx, d.localPosition.dy);
} else { } else {
widget.controller widget.controller
.panStart(d.localPosition.dx, d.localPosition.dy); .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 // set the _pointerMoving flag so we don't call rotateStart/panStart on future move events
_pointerMoving = true; _pointerMoving = true;
if (d.buttons == kTertiaryButton) { if (d.buttons == kTertiaryButton) {
widget.controller.rotateUpdate(d.position.dx, d.position.dy); widget.controller
.rotateUpdate(d.localPosition.dx, d.localPosition.dy);
} else { } else {
widget.controller.panUpdate(d.localPosition.dx, d.localPosition.dy); widget.controller.panUpdate(d.localPosition.dx, d.localPosition.dy);
} }

View File

@@ -158,8 +158,8 @@ class _FilamentGestureDetectorMobileState
}, },
onScaleUpdate: (ScaleUpdateDetails d) async { onScaleUpdate: (ScaleUpdateDetails d) async {
if (d.pointerCount == 2) { if (d.pointerCount == 2) {
widget.controller widget.controller.zoomUpdate(d.localFocalPoint.dx,
.zoomUpdate(d.horizontalScale > 1 ? 0.1 : -0.1); d.localFocalPoint.dy, d.horizontalScale > 1 ? 0.1 : -0.1);
} else if (!_scaling) { } else if (!_scaling) {
if (_rotateOnPointerMove) { if (_rotateOnPointerMove) {
widget.controller widget.controller

View File

@@ -64,8 +64,10 @@ public class SwiftPolyvoxFilamentPlugin: NSObject, FlutterPlugin, FlutterTexture
var markTextureFrameAvailable : @convention(c) (UnsafeMutableRawPointer?) -> () = { instancePtr in var markTextureFrameAvailable : @convention(c) (UnsafeMutableRawPointer?) -> () = { instancePtr in
let instance:SwiftPolyvoxFilamentPlugin = Unmanaged<SwiftPolyvoxFilamentPlugin>.fromOpaque(instancePtr!).takeUnretainedValue() let instance:SwiftPolyvoxFilamentPlugin = Unmanaged<SwiftPolyvoxFilamentPlugin>.fromOpaque(instancePtr!).takeUnretainedValue()
if(instance.flutterTextureId != nil) {
instance.registry.textureFrameAvailable(instance.flutterTextureId!) instance.registry.textureFrameAvailable(instance.flutterTextureId!)
} }
}
public func copyPixelBuffer() -> Unmanaged<CVPixelBuffer>? { public func copyPixelBuffer() -> Unmanaged<CVPixelBuffer>? {
if(pixelBuffer == nil) { if(pixelBuffer == nil) {
@@ -142,6 +144,10 @@ public class SwiftPolyvoxFilamentPlugin: NSObject, FlutterPlugin, FlutterTexture
Int(width), Int(height), Int(width), Int(height),
0, 0,
&cvMetalTexture); &cvMetalTexture);
if(cvret != 0) {
result(FlutterError())
return
}
metalTexture = CVMetalTextureGetTexture(cvMetalTexture!); metalTexture = CVMetalTextureGetTexture(cvMetalTexture!);
let pixelBufferPtr = CVPixelBufferGetBaseAddress(pixelBuffer!); let pixelBufferPtr = CVPixelBufferGetBaseAddress(pixelBuffer!);
let pixelBufferAddress = Int(bitPattern:pixelBufferPtr); let pixelBufferAddress = Int(bitPattern:pixelBufferPtr);
@@ -160,13 +166,7 @@ public class SwiftPolyvoxFilamentPlugin: NSObject, FlutterPlugin, FlutterTexture
let args = call.arguments as! [Any] let args = call.arguments as! [Any]
let width = UInt32(args[0] as! Int64) let width = UInt32(args[0] as! Int64)
let height = UInt32(args[1] as! Int64) let height = UInt32(args[1] as! Int64)
if(self.flutterTextureId != nil) { result(FlutterMethodNotImplemented)
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);
default: default:
result(FlutterMethodNotImplemented) result(FlutterMethodNotImplemented)
} }

View File

@@ -177,7 +177,7 @@ namespace polyvox {
void loadPngTexture(string path, ResourceBuffer data); void loadPngTexture(string path, ResourceBuffer data);
void loadTextureFromPath(string path); void loadTextureFromPath(string path);
Manipulator<double>* _manipulator = nullptr;
void _createManipulator(); void _createManipulator();
uint32_t _lastFrameTimeInNanos; uint32_t _lastFrameTimeInNanos;
}; };

View File

@@ -14,7 +14,7 @@ A new Flutter plugin project.
s.author = { 'Your Company' => 'email@example.com' } s.author = { 'Your Company' => 'email@example.com' }
s.source = { :path => '.' } 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.public_header_files = 'include/SwiftPolyvoxFilamentPlugin-Bridging-Header.h', 'include/PolyvoxFilamentApi.h', 'include/PolyvoxFilamentFFIApi.h', 'include/ResourceBuffer.hpp' #, 'include/filament/*'
s.dependency 'FlutterMacOS' s.dependency 'FlutterMacOS'
@@ -38,7 +38,7 @@ A new Flutter plugin project.
'OTHER_CFLAGS' => '"-fvisibility=default" "$(inherited)"', 'OTHER_CFLAGS' => '"-fvisibility=default" "$(inherited)"',
'USER_HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}/include" "${PODS_TARGET_SRCROOT}/include/filament" "$(inherited)"', 'USER_HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}/include" "${PODS_TARGET_SRCROOT}/include/filament" "$(inherited)"',
'ALWAYS_SEARCH_USER_PATHS' => 'YES', '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)"', 'LIBRARY_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}/lib" "$(inherited)"',
} }
s.swift_version = '5.0' s.swift_version = '5.0'

View File

@@ -22,6 +22,7 @@
* limitations under the License. * limitations under the License.
*/ */
#include <filament/Camera.h> #include <filament/Camera.h>
#include <backend/DriverEnums.h> #include <backend/DriverEnums.h>
#include <backend/platforms/OpenGLPlatform.h> #include <backend/platforms/OpenGLPlatform.h>
#include <filament/ColorGrading.h> #include <filament/ColorGrading.h>
@@ -86,12 +87,14 @@ using namespace gltfio;
using namespace utils; using namespace utils;
using namespace image; using namespace image;
namespace filament { namespace filament
{
class IndirectLight; class IndirectLight;
class LightManager; class LightManager;
} // namespace filament } // namespace filament
namespace polyvox { namespace polyvox
{
const double kNearPlane = 0.05; // 5 cm const double kNearPlane = 0.05; // 5 cm
const double kFarPlane = 1000.0; // 1 km const double kFarPlane = 1000.0; // 1 km
@@ -99,7 +102,8 @@ const double kFarPlane = 1000.0; // 1 km
// const float kAperture = 1.0f; // const float kAperture = 1.0f;
// const float kShutterSpeed = 1.0f; // const float kShutterSpeed = 1.0f;
// const float kSensitivity = 50.0f; // const float kSensitivity = 50.0f;
struct Vertex { struct Vertex
{
filament::math::float2 position; filament::math::float2 position;
uint32_t color; uint32_t color;
}; };
@@ -112,7 +116,8 @@ static constexpr float4 sFullScreenTriangleVertices[3] = {
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) FilamentViewer::FilamentViewer(const void *sharedContext, const ResourceLoaderWrapper *const resourceLoaderWrapper, void *const platform, const char *uberArchivePath)
: _resourceLoaderWrapper(resourceLoaderWrapper) { : _resourceLoaderWrapper(resourceLoaderWrapper)
{
ASSERT_POSTCONDITION(_resourceLoaderWrapper != nullptr, "Resource loader must be non-null"); ASSERT_POSTCONDITION(_resourceLoaderWrapper != nullptr, "Resource loader must be non-null");
@@ -193,8 +198,7 @@ FilamentViewer::FilamentViewer(const void* sharedContext, const ResourceLoaderWr
_ncm, _ncm,
_engine, _engine,
_scene, _scene,
uberArchivePath uberArchivePath);
);
_imageTexture = Texture::Builder() _imageTexture = Texture::Builder()
.width(1) .width(1)
@@ -203,7 +207,8 @@ FilamentViewer::FilamentViewer(const void* sharedContext, const ResourceLoaderWr
.format(Texture::InternalFormat::RGB16F) .format(Texture::InternalFormat::RGB16F)
.sampler(Texture::Sampler::SAMPLER_2D) .sampler(Texture::Sampler::SAMPLER_2D)
.build(*_engine); .build(*_engine);
try { try
{
_imageMaterial = _imageMaterial =
Material::Builder() Material::Builder()
.package(IMAGE_IMAGE_DATA, IMAGE_IMAGE_SIZE) .package(IMAGE_IMAGE_DATA, IMAGE_IMAGE_SIZE)
@@ -211,7 +216,9 @@ FilamentViewer::FilamentViewer(const void* sharedContext, const ResourceLoaderWr
_imageMaterial->setDefaultParameter("showImage", 0); _imageMaterial->setDefaultParameter("showImage", 0);
_imageMaterial->setDefaultParameter("backgroundColor", RgbaType::sRGB, float4(0.5f, 0.5f, 0.5f, 1.0f)); _imageMaterial->setDefaultParameter("backgroundColor", RgbaType::sRGB, float4(0.5f, 0.5f, 0.5f, 1.0f));
_imageMaterial->setDefaultParameter("image", _imageTexture, _imageSampler); _imageMaterial->setDefaultParameter("image", _imageTexture, _imageSampler);
} catch(...) { }
catch (...)
{
Log("Failed to load background image material provider"); Log("Failed to load background image material provider");
std::rethrow_exception(std::current_exception()); std::rethrow_exception(std::current_exception());
} }
@@ -250,21 +257,25 @@ FilamentViewer::FilamentViewer(const void* sharedContext, const ResourceLoaderWr
_scene->addEntity(imageEntity); _scene->addEntity(imageEntity);
} }
void FilamentViewer::setPostProcessing(bool enabled) { void FilamentViewer::setPostProcessing(bool enabled)
{
_view->setPostProcessingEnabled(enabled); _view->setPostProcessingEnabled(enabled);
} }
void FilamentViewer::setBloom(float strength) { void FilamentViewer::setBloom(float strength)
{
decltype(_view->getBloomOptions()) opts; decltype(_view->getBloomOptions()) opts;
opts.enabled = true; opts.enabled = true;
opts.strength = strength; opts.strength = strength;
_view->setBloomOptions(opts); _view->setBloomOptions(opts);
} }
void FilamentViewer::setToneMapping(ToneMapping toneMapping) { void FilamentViewer::setToneMapping(ToneMapping toneMapping)
{
ToneMapper *tm; ToneMapper *tm;
switch(toneMapping) { switch (toneMapping)
{
case ToneMapping::ACES: case ToneMapping::ACES:
Log("Setting tone mapping to ACES"); Log("Setting tone mapping to ACES");
tm = new ACESToneMapper(); tm = new ACESToneMapper();
@@ -282,21 +293,22 @@ void FilamentViewer::setToneMapping(ToneMapping toneMapping) {
return; return;
} }
auto newColorGrading = ColorGrading::Builder().toneMapper(tm).build(*_engine); auto newColorGrading = ColorGrading::Builder().toneMapper(tm).build(*_engine);
_view->setColorGrading(newColorGrading); _view->setColorGrading(newColorGrading);
_engine->destroy(colorGrading); _engine->destroy(colorGrading);
delete tm; delete tm;
} }
void FilamentViewer::setFrameInterval(float frameInterval) { void FilamentViewer::setFrameInterval(float frameInterval)
{
Renderer::FrameRateOptions fro; Renderer::FrameRateOptions fro;
fro.interval = frameInterval; fro.interval = frameInterval;
_renderer->setFrameRateOptions(fro); _renderer->setFrameRateOptions(fro);
Log("Set framerate interval to %f", frameInterval); 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) { 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(); auto light = EntityManager::get().create();
LightManager::Builder(t) LightManager::Builder(t)
.color(Color::cct(colour)) .color(Color::cct(colour))
@@ -312,30 +324,37 @@ int32_t FilamentViewer::addLight(LightManager::Type t, float colour, float inten
return entityId; return entityId;
} }
void FilamentViewer::removeLight(EntityId entityId) { void FilamentViewer::removeLight(EntityId entityId)
{
Log("Removing light with entity ID %d", entityId); Log("Removing light with entity ID %d", entityId);
auto entity = utils::Entity::import(entityId); auto entity = utils::Entity::import(entityId);
if(entity.isNull()) { if (entity.isNull())
{
Log("Error: light entity not found under ID %d", entityId); Log("Error: light entity not found under ID %d", entityId);
} else { }
else
{
remove(_lights.begin(), _lights.end(), entity); remove(_lights.begin(), _lights.end(), entity);
_scene->remove(entity); _scene->remove(entity);
EntityManager::get().destroy(1, &entity); EntityManager::get().destroy(1, &entity);
} }
} }
void FilamentViewer::clearLights() { void FilamentViewer::clearLights()
{
Log("Removing all lights"); Log("Removing all lights");
_scene->removeEntities(_lights.data(), _lights.size()); _scene->removeEntities(_lights.data(), _lights.size());
EntityManager::get().destroy(_lights.size(), _lights.data()); EntityManager::get().destroy(_lights.size(), _lights.data());
_lights.clear(); _lights.clear();
} }
static bool endsWith(string path, string ending) { static bool endsWith(string path, string ending)
{
return path.compare(path.length() - ending.length(), ending.length(), ending) == 0; return path.compare(path.length() - ending.length(), ending.length(), ending) == 0;
} }
void FilamentViewer::loadKtx2Texture(string path, ResourceBuffer rb) { void FilamentViewer::loadKtx2Texture(string path, ResourceBuffer rb)
{
// TODO - check all this // TODO - check all this
@@ -356,34 +375,36 @@ void FilamentViewer::loadKtx2Texture(string path, ResourceBuffer rb) {
// ktxreader::Ktx2Reader::TransferFunction::LINEAR); // ktxreader::Ktx2Reader::TransferFunction::LINEAR);
} }
void FilamentViewer::loadKtxTexture(string path, ResourceBuffer rb) { void FilamentViewer::loadKtxTexture(string path, ResourceBuffer rb)
{
ktxreader::Ktx1Bundle *bundle = ktxreader::Ktx1Bundle *bundle =
new ktxreader::Ktx1Bundle(static_cast<const uint8_t *>(rb.data), new ktxreader::Ktx1Bundle(static_cast<const uint8_t *>(rb.data),
static_cast<uint32_t>(rb.size)); static_cast<uint32_t>(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 // 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<void *> *callbackData = new std::vector<void *>{(void *)_resourceLoaderWrapper, rbCopy}; std::vector<void *> *callbackData = new std::vector<void *>{(void *)_resourceLoaderWrapper, rbCopy};
_imageTexture = _imageTexture =
ktxreader::Ktx1Reader::createTexture(_engine, *bundle, false, [](void* userdata) { ktxreader::Ktx1Reader::createTexture(
_engine, *bundle, false, [](void *userdata)
{
std::vector<void*>* vec = (std::vector<void*>*)userdata; std::vector<void*>* vec = (std::vector<void*>*)userdata;
ResourceLoaderWrapper* loader = (ResourceLoaderWrapper*)vec->at(0); ResourceLoaderWrapper* loader = (ResourceLoaderWrapper*)vec->at(0);
ResourceBuffer* rb = (ResourceBuffer*) vec->at(1); ResourceBuffer* rb = (ResourceBuffer*) vec->at(1);
loader->free(*rb); loader->free(*rb);
delete rb; delete rb;
delete vec; delete vec; },
}, callbackData); callbackData);
auto info = bundle->getInfo(); auto info = bundle->getInfo();
_imageWidth = info.pixelWidth; _imageWidth = info.pixelWidth;
_imageHeight = info.pixelHeight; _imageHeight = info.pixelHeight;
} }
void FilamentViewer::loadPngTexture(string path, ResourceBuffer rb) { void FilamentViewer::loadPngTexture(string path, ResourceBuffer rb)
{
polyvox::StreamBufferAdapter sb((char *)rb.data, (char *)rb.data + rb.size); polyvox::StreamBufferAdapter sb((char *)rb.data, (char *)rb.data + rb.size);
@@ -392,7 +413,8 @@ void FilamentViewer::loadPngTexture(string path, ResourceBuffer rb) {
LinearImage *image = new LinearImage(ImageDecoder::decode( LinearImage *image = new LinearImage(ImageDecoder::decode(
inputStream, path.c_str(), ImageDecoder::ColorSpace::SRGB)); inputStream, path.c_str(), ImageDecoder::ColorSpace::SRGB));
if (!image->isValid()) { if (!image->isValid())
{
Log("Invalid image : %s", path.c_str()); Log("Invalid image : %s", path.c_str());
return; return;
} }
@@ -411,7 +433,8 @@ void FilamentViewer::loadPngTexture(string path, ResourceBuffer rb) {
.build(*_engine); .build(*_engine);
Texture::PixelBufferDescriptor::Callback freeCallback = [](void *buf, size_t, Texture::PixelBufferDescriptor::Callback freeCallback = [](void *buf, size_t,
void *data) { void *data)
{
delete reinterpret_cast<LinearImage *>(data); delete reinterpret_cast<LinearImage *>(data);
}; };
@@ -426,43 +449,54 @@ void FilamentViewer::loadPngTexture(string path, ResourceBuffer rb) {
_resourceLoaderWrapper->free(rb); _resourceLoaderWrapper->free(rb);
} }
void FilamentViewer::loadTextureFromPath(string path) { void FilamentViewer::loadTextureFromPath(string path)
{
string ktxExt(".ktx"); string ktxExt(".ktx");
string ktx2Ext(".ktx2"); string ktx2Ext(".ktx2");
string pngExt(".png"); string pngExt(".png");
if (path.length() < 5) { if (path.length() < 5)
{
Log("Invalid resource path : %s", path.c_str()); Log("Invalid resource path : %s", path.c_str());
return; return;
} }
ResourceBuffer rb = _resourceLoaderWrapper->load(path.c_str()); ResourceBuffer rb = _resourceLoaderWrapper->load(path.c_str());
if(endsWith(path, ktxExt)) { if (endsWith(path, ktxExt))
{
loadKtxTexture(path, rb); loadKtxTexture(path, rb);
} else if(endsWith(path, ktx2Ext)) { }
else if (endsWith(path, ktx2Ext))
{
loadKtx2Texture(path, rb); loadKtx2Texture(path, rb);
} else if(endsWith(path, pngExt)) { }
else if (endsWith(path, pngExt))
{
loadPngTexture(path, rb); loadPngTexture(path, rb);
} }
} }
void FilamentViewer::setBackgroundColor(const float r, const float g, const float b, const float a) { void FilamentViewer::setBackgroundColor(const float r, const float g, const float b, const float a)
{
_imageMaterial->setDefaultParameter("showImage", 0); _imageMaterial->setDefaultParameter("showImage", 0);
_imageMaterial->setDefaultParameter("backgroundColor", RgbaType::sRGB, float4(r, g, b, a)); _imageMaterial->setDefaultParameter("backgroundColor", RgbaType::sRGB, float4(r, g, b, a));
_imageMaterial->setDefaultParameter("transform", _imageScale); _imageMaterial->setDefaultParameter("transform", _imageScale);
} }
void FilamentViewer::clearBackgroundImage() { void FilamentViewer::clearBackgroundImage()
{
_imageMaterial->setDefaultParameter("showImage", 0); _imageMaterial->setDefaultParameter("showImage", 0);
if (_imageTexture) { if (_imageTexture)
{
_engine->destroy(_imageTexture); _engine->destroy(_imageTexture);
_imageTexture = nullptr; _imageTexture = nullptr;
Log("Destroyed background image texture"); Log("Destroyed background image texture");
} }
} }
void FilamentViewer::setBackgroundImage(const char *resourcePath, bool fillHeight) { void FilamentViewer::setBackgroundImage(const char *resourcePath, bool fillHeight)
{
string resourcePathString(resourcePath); string resourcePathString(resourcePath);
@@ -477,13 +511,15 @@ void FilamentViewer::setBackgroundImage(const char *resourcePath, bool fillHeigh
const Viewport &vp = _view->getViewport(); const Viewport &vp = _view->getViewport();
Log("Image width %d height %d vp width %d height %d", _imageWidth, _imageHeight, vp.width, vp.height); 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 xScale = float(vp.width) / float(_imageWidth);
float yScale; float yScale;
if(fillHeight) { if (fillHeight)
{
yScale = 1.0f; yScale = 1.0f;
} else { }
else
{
yScale = float(vp.height) / float(_imageHeight); yScale = float(vp.height) / float(_imageHeight);
} }
@@ -492,17 +528,16 @@ void FilamentViewer::setBackgroundImage(const char *resourcePath, bool fillHeigh
_imageMaterial->setDefaultParameter("transform", _imageScale); _imageMaterial->setDefaultParameter("transform", _imageScale);
_imageMaterial->setDefaultParameter("image", _imageTexture, _imageSampler); _imageMaterial->setDefaultParameter("image", _imageTexture, _imageSampler);
_imageMaterial->setDefaultParameter("showImage", 1); _imageMaterial->setDefaultParameter("showImage", 1);
} }
/// ///
/// Translates the background image by (x,y) pixels. /// 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 /// 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 /// 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). /// (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) { 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). // 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. // this allows us to set a background colour for the quad when the texture has been translated outside the quad's bounds.
@@ -517,7 +552,8 @@ void FilamentViewer::setBackgroundImagePosition(float x, float y, bool clamp=fal
// y *= _imageScale[1][1]; // 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. // 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) { if (clamp)
{
Log("Clamping background image translation"); Log("Clamping background image translation");
// first, clamp x/y // first, clamp x/y
auto xScale = float(_imageWidth) / _view->getViewport().width; auto xScale = float(_imageWidth) / _view->getViewport().width;
@@ -530,20 +566,26 @@ void FilamentViewer::setBackgroundImagePosition(float x, float y, bool clamp=fal
// we need to clamp x so that it can only be translated between (left side touching viewport left) and (right side touching viewport right) // 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 width is less than viewport, these values are 0/1-xScale respectively
if(xScale < 1) { if (xScale < 1)
{
xMin = 0; xMin = 0;
xMax = 1 - xScale; xMax = 1 - xScale;
// otherwise, these value are (xScale-1 and 1-xScale) // otherwise, these value are (xScale-1 and 1-xScale)
} else { }
else
{
xMin = 1 - xScale; xMin = 1 - xScale;
xMax = 0; xMax = 0;
} }
// do the same for y // do the same for y
if(yScale < 1) { if (yScale < 1)
{
yMin = 0; yMin = 0;
yMax = 1 - yScale; yMax = 1 - yScale;
} else { }
else
{
yMin = 1 - yScale; yMin = 1 - yScale;
yMax = 0; yMax = 0;
} }
@@ -558,26 +600,27 @@ void FilamentViewer::setBackgroundImagePosition(float x, float y, bool clamp=fal
y = -y; y = -y;
Log("x %f y %f", x, 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], \ 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[1][0], _imageScale[1][1], _imageScale[1][2], _imageScale[1][3],
_imageScale[2][0],_imageScale[2][1],_imageScale[2][2], _imageScale[2][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]); _imageScale[3][0], _imageScale[3][1], _imageScale[3][2], _imageScale[3][3]);
auto transform = math::mat4f::translation(math::float3(x, y, 0.0f)) * _imageScale; 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],
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[1][0],transform[1][1],transform[1][2], transform[1][3],\ transform[2][0], transform[2][1], transform[2][2], transform[2][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]); transform[3][0], transform[3][1], transform[3][2], transform[3][3]);
_imageMaterial->setDefaultParameter("transform", transform); _imageMaterial->setDefaultParameter("transform", transform);
} }
FilamentViewer::~FilamentViewer() { FilamentViewer::~FilamentViewer()
{
clearAssets(); clearAssets();
delete _assetManager; delete _assetManager;
for(auto it : _lights) { for (auto it : _lights)
{
_engine->destroy(it); _engine->destroy(it);
} }
@@ -593,21 +636,26 @@ FilamentViewer::~FilamentViewer() {
Renderer *FilamentViewer::getRenderer() { return _renderer; } Renderer *FilamentViewer::getRenderer() { return _renderer; }
void FilamentViewer::createSwapChain(const void *window, uint32_t width, uint32_t height) { void FilamentViewer::createSwapChain(const void *window, uint32_t width, uint32_t height)
{
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
_swapChain = _engine->createSwapChain((void *)window, filament::backend::SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER); _swapChain = _engine->createSwapChain((void *)window, filament::backend::SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER);
#else #else
if(window) { if (window)
{
_swapChain = _engine->createSwapChain((void *)window, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE); _swapChain = _engine->createSwapChain((void *)window, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE);
Log("Created window swapchain."); Log("Created window swapchain.");
} else { }
else
{
Log("Created headless swapchain."); Log("Created headless swapchain.");
_swapChain = _engine->createSwapChain(width, height, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE); _swapChain = _engine->createSwapChain(width, height, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE);
} }
#endif #endif
} }
void FilamentViewer::createRenderTarget(intptr_t texture, uint32_t width, uint32_t height) { 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) // Create filament textures and render targets (note the color buffer has the import call)
_rtColor = filament::Texture::Builder() _rtColor = filament::Texture::Builder()
.width(width) .width(width)
@@ -633,11 +681,12 @@ void FilamentViewer::createRenderTarget(intptr_t texture, uint32_t width, uint32
_view->setRenderTarget(_rt); _view->setRenderTarget(_rt);
Log("Set render target for texture id %u to %u x %u", texture, width, height); Log("Set render target for texture id %u to %u x %u", texture, width, height);
} }
void FilamentViewer::destroySwapChain() { void FilamentViewer::destroySwapChain()
if(_rt) { {
if (_rt)
{
_view->setRenderTarget(nullptr); _view->setRenderTarget(nullptr);
_engine->destroy(_rtDepth); _engine->destroy(_rtDepth);
_engine->destroy(_rtColor); _engine->destroy(_rtColor);
@@ -646,16 +695,19 @@ void FilamentViewer::destroySwapChain() {
_rtDepth = nullptr; _rtDepth = nullptr;
_rtColor = nullptr; _rtColor = nullptr;
} }
if (_swapChain) { if (_swapChain)
{
_engine->destroy(_swapChain); _engine->destroy(_swapChain);
_swapChain = nullptr; _swapChain = nullptr;
Log("Swapchain destroyed."); Log("Swapchain destroyed.");
} }
} }
void FilamentViewer::clearAssets() { void FilamentViewer::clearAssets()
{
Log("Clearing all assets"); Log("Clearing all assets");
if(_mainCamera) { if (_mainCamera)
{
_view->setCamera(_mainCamera); _view->setCamera(_mainCamera);
} }
@@ -664,7 +716,8 @@ void FilamentViewer::clearAssets() {
Log("Cleared all assets"); Log("Cleared all assets");
} }
void FilamentViewer::removeAsset(EntityId asset) { void FilamentViewer::removeAsset(EntityId asset)
{
Log("Removing asset from scene"); Log("Removing asset from scene");
mtx.lock(); mtx.lock();
@@ -677,7 +730,8 @@ void FilamentViewer::removeAsset(EntityId asset) {
/// ///
/// Set the exposure for the current active camera. /// Set the exposure for the current active camera.
/// ///
void FilamentViewer::setCameraExposure(float aperture, float shutterSpeed, float sensitivity) { void FilamentViewer::setCameraExposure(float aperture, float shutterSpeed, float sensitivity)
{
Camera &cam = _view->getCamera(); Camera &cam = _view->getCamera();
Log("Setting aperture (%03f) shutterSpeed (%03f) and sensitivity (%03f)", aperture, shutterSpeed, sensitivity); Log("Setting aperture (%03f) shutterSpeed (%03f) and sensitivity (%03f)", aperture, shutterSpeed, sensitivity);
cam.setExposure(aperture, shutterSpeed, sensitivity); cam.setExposure(aperture, shutterSpeed, sensitivity);
@@ -686,7 +740,8 @@ void FilamentViewer::setCameraExposure(float aperture, float shutterSpeed, float
/// ///
/// Set the focal length of the active camera. /// Set the focal length of the active camera.
/// ///
void FilamentViewer::setCameraFocalLength(float focalLength) { void FilamentViewer::setCameraFocalLength(float focalLength)
{
Camera &cam = _view->getCamera(); Camera &cam = _view->getCamera();
_cameraFocalLength = focalLength; _cameraFocalLength = focalLength;
cam.setLensProjection(_cameraFocalLength, 1.0f, kNearPlane, cam.setLensProjection(_cameraFocalLength, 1.0f, kNearPlane,
@@ -696,7 +751,8 @@ void FilamentViewer::setCameraFocalLength(float focalLength) {
/// ///
/// Set the focus distance of the active camera. /// Set the focus distance of the active camera.
/// ///
void FilamentViewer::setCameraFocusDistance(float focusDistance) { void FilamentViewer::setCameraFocusDistance(float focusDistance)
{
Camera &cam = _view->getCamera(); Camera &cam = _view->getCamera();
_cameraFocusDistance = focusDistance; _cameraFocusDistance = focusDistance;
cam.setFocusDistance(_cameraFocusDistance); cam.setFocusDistance(_cameraFocusDistance);
@@ -707,15 +763,18 @@ void FilamentViewer::setCameraFocusDistance(float focusDistance) {
/// N.B. Blender will generally export a three-node hierarchy - /// N.B. Blender will generally export a three-node hierarchy -
/// Camera1->Camera_Orientation->Camera2. The correct name will be the Camera_Orientation. /// Camera1->Camera_Orientation->Camera2. The correct name will be the Camera_Orientation.
/// ///
bool FilamentViewer::setCamera(EntityId entityId, const char *cameraName) { bool FilamentViewer::setCamera(EntityId entityId, const char *cameraName)
{
auto asset = _assetManager->getAssetByEntityId(entityId); auto asset = _assetManager->getAssetByEntityId(entityId);
if(!asset) { if (!asset)
{
Log("Failed to find asset under entity id %d.", entityId); Log("Failed to find asset under entity id %d.", entityId);
return false; return false;
} }
size_t count = asset->getCameraEntityCount(); size_t count = asset->getCameraEntityCount();
if (count == 0) { if (count == 0)
{
Log("No cameras found attached to specified entity."); Log("No cameras found attached to specified entity.");
return false; return false;
} }
@@ -724,28 +783,35 @@ bool FilamentViewer::setCamera(EntityId entityId, const char *cameraName) {
utils::Entity target; utils::Entity target;
if(!cameraName) { if (!cameraName)
{
auto inst = _ncm->getInstance(cameras[0]); auto inst = _ncm->getInstance(cameras[0]);
const char *name = _ncm->getName(inst); const char *name = _ncm->getName(inst);
target = cameras[0]; target = cameras[0];
Log("No camera specified, using first camera node found (%s)", name); Log("No camera specified, using first camera node found (%s)", name);
} else { }
for (int j = 0; j < count; j++) { else
{
for (int j = 0; j < count; j++)
{
auto inst = _ncm->getInstance(cameras[j]); auto inst = _ncm->getInstance(cameras[j]);
const char *name = _ncm->getName(inst); const char *name = _ncm->getName(inst);
if (strcmp(name, cameraName) == 0) { if (strcmp(name, cameraName) == 0)
{
target = cameras[j]; target = cameras[j];
break; break;
} }
} }
} }
if(target.isNull()) { if (target.isNull())
{
Log("Unable to locate camera under name %s ", cameraName); Log("Unable to locate camera under name %s ", cameraName);
return false; return false;
} }
Camera *camera = _engine->getCameraComponent(target); Camera *camera = _engine->getCameraComponent(target);
if(!camera) { if (!camera)
{
Log("Failed to retrieve camera component for target"); Log("Failed to retrieve camera component for target");
return false; return false;
} }
@@ -761,11 +827,13 @@ bool FilamentViewer::setCamera(EntityId entityId, const char *cameraName) {
return true; return true;
} }
void FilamentViewer::loadSkybox(const char *const skyboxPath) { void FilamentViewer::loadSkybox(const char *const skyboxPath)
{
removeSkybox(); removeSkybox();
if (!skyboxPath) { if (!skyboxPath)
{
Log("No skybox path provided, removed skybox."); Log("No skybox path provided, removed skybox.");
} }
@@ -776,7 +844,8 @@ void FilamentViewer::loadSkybox(const char *const skyboxPath) {
// because this will go out of scope before the texture callback is invoked, we need to make a copy to the heap // 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); ResourceBuffer *skyboxBufferCopy = new ResourceBuffer(skyboxBuffer);
if(skyboxBuffer.size <= 0) { if (skyboxBuffer.size <= 0)
{
Log("Could not load skybox resource."); Log("Could not load skybox resource.");
return; return;
} }
@@ -790,37 +859,43 @@ void FilamentViewer::loadSkybox(const char *const skyboxPath) {
static_cast<uint32_t>(skyboxBuffer.size)); static_cast<uint32_t>(skyboxBuffer.size));
_skyboxTexture = _skyboxTexture =
ktxreader::Ktx1Reader::createTexture(_engine, *skyboxBundle, false, [](void* userdata) { ktxreader::Ktx1Reader::createTexture(
_engine, *skyboxBundle, false, [](void *userdata)
{
std::vector<void*>* vec = (std::vector<void*>*)userdata; std::vector<void*>* vec = (std::vector<void*>*)userdata;
ResourceLoaderWrapper* loader = (ResourceLoaderWrapper*)vec->at(0); ResourceLoaderWrapper* loader = (ResourceLoaderWrapper*)vec->at(0);
ResourceBuffer* rb = (ResourceBuffer*) vec->at(1); ResourceBuffer* rb = (ResourceBuffer*) vec->at(1);
loader->free(*rb); loader->free(*rb);
delete rb; delete rb;
delete vec; delete vec;
Log("Skybox load complete."); Log("Skybox load complete."); },
}, callbackData); callbackData);
_skybox = _skybox =
filament::Skybox::Builder().environment(_skyboxTexture).build(*_engine); filament::Skybox::Builder().environment(_skyboxTexture).build(*_engine);
_scene->setSkybox(_skybox); _scene->setSkybox(_skybox);
} }
void FilamentViewer::removeSkybox() { void FilamentViewer::removeSkybox()
{
Log("Removing skybox"); Log("Removing skybox");
_scene->setSkybox(nullptr); _scene->setSkybox(nullptr);
if(_skybox) { if (_skybox)
{
_engine->destroy(_skybox); _engine->destroy(_skybox);
_skybox = nullptr; _skybox = nullptr;
} }
if(_skyboxTexture) { if (_skyboxTexture)
{
_engine->destroy(_skyboxTexture); _engine->destroy(_skyboxTexture);
_skyboxTexture = nullptr; _skyboxTexture = nullptr;
} }
} }
void FilamentViewer::removeIbl() { void FilamentViewer::removeIbl()
if(_indirectLight) { {
if (_indirectLight)
{
_engine->destroy(_indirectLight); _engine->destroy(_indirectLight);
_engine->destroy(_iblTexture); _engine->destroy(_iblTexture);
_indirectLight = nullptr; _indirectLight = nullptr;
@@ -829,9 +904,11 @@ void FilamentViewer::removeIbl() {
_scene->setIndirectLight(nullptr); _scene->setIndirectLight(nullptr);
} }
void FilamentViewer::loadIbl(const char *const iblPath, float intensity) { void FilamentViewer::loadIbl(const char *const iblPath, float intensity)
{
removeIbl(); removeIbl();
if (iblPath) { if (iblPath)
{
Log("Loading IBL from %s", iblPath); Log("Loading IBL from %s", iblPath);
// Load IBL. // Load IBL.
@@ -839,7 +916,8 @@ void FilamentViewer::loadIbl(const char *const iblPath, float intensity) {
// because this will go out of scope before the texture callback is invoked, we need to make a copy to the heap // 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); ResourceBuffer *iblBufferCopy = new ResourceBuffer(iblBuffer);
if(iblBuffer.size == 0) { if (iblBuffer.size == 0)
{
Log("Error loading IBL, resource could not be loaded."); Log("Error loading IBL, resource could not be loaded.");
return; return;
} }
@@ -853,14 +931,16 @@ void FilamentViewer::loadIbl(const char *const iblPath, float intensity) {
std::vector<void *> *callbackData = new std::vector<void *>{(void *)_resourceLoaderWrapper, iblBufferCopy}; std::vector<void *> *callbackData = new std::vector<void *>{(void *)_resourceLoaderWrapper, iblBufferCopy};
_iblTexture = _iblTexture =
ktxreader::Ktx1Reader::createTexture(_engine, *iblBundle, false, [](void* userdata) { ktxreader::Ktx1Reader::createTexture(
_engine, *iblBundle, false, [](void *userdata)
{
std::vector<void*>* vec = (std::vector<void*>*)userdata; std::vector<void*>* vec = (std::vector<void*>*)userdata;
ResourceLoaderWrapper* loader = (ResourceLoaderWrapper*)vec->at(0); ResourceLoaderWrapper* loader = (ResourceLoaderWrapper*)vec->at(0);
ResourceBuffer* rb = (ResourceBuffer*) vec->at(1); ResourceBuffer* rb = (ResourceBuffer*) vec->at(1);
loader->free(*rb); loader->free(*rb);
delete rb; delete rb;
delete vec; delete vec; },
}, callbackData); callbackData);
_indirectLight = IndirectLight::Builder() _indirectLight = IndirectLight::Builder()
.reflections(_iblTexture) .reflections(_iblTexture)
.irradiance(3, harmonics) .irradiance(3, harmonics)
@@ -879,14 +959,17 @@ void FilamentViewer::render(
uint64_t frameTimeInNanos, uint64_t frameTimeInNanos,
void *pixelBuffer, void *pixelBuffer,
void (*callback)(void *buf, size_t size, void *data), void (*callback)(void *buf, size_t size, void *data),
void* data) { void *data)
{
if (!_view || !_mainCamera || !_swapChain) { if (!_view || !_mainCamera || !_swapChain)
{
Log("Not ready for rendering"); Log("Not ready for rendering");
return; return;
} }
if(_frameCount == 60) { if (_frameCount == 60)
{
// Log("1 sec average for asset animation update %f", _elapsed / 60); // Log("1 sec average for asset animation update %f", _elapsed / 60);
_elapsed = 0; _elapsed = 0;
_frameCount = 0; _frameCount = 0;
@@ -899,7 +982,18 @@ void FilamentViewer::render(
_elapsed += tmr.elapsed(); _elapsed += tmr.elapsed();
_frameCount++; _frameCount++;
if(pixelBuffer) { // 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( auto pbd = Texture::PixelBufferDescriptor(
pixelBuffer, size_t(1024 * 768 * 4), pixelBuffer, size_t(1024 * 768 * 4),
Texture::Format::RGBA, Texture::Format::RGBA,
@@ -909,21 +1003,27 @@ void FilamentViewer::render(
_renderer->render(_view); _renderer->render(_view);
_renderer->readPixels(0, 0, 1024, 768, std::move(pbd)); _renderer->readPixels(0, 0, 1024, 768, std::move(pbd));
_renderer->endFrame(); _renderer->endFrame();
} else { }
else
{
// Render the scene, unless the renderer wants to skip the frame. // Render the scene, unless the renderer wants to skip the frame.
if (_renderer->beginFrame(_swapChain, frameTimeInNanos)) { if (_renderer->beginFrame(_swapChain, frameTimeInNanos))
{
_renderer->render(_view); _renderer->render(_view);
_renderer->endFrame(); _renderer->endFrame();
} else { }
else
{
// skipped frame // skipped frame
} }
} }
} }
void FilamentViewer::updateViewportAndCameraProjection( void FilamentViewer::updateViewportAndCameraProjection(
int width, int height, float contentScaleFactor) { int width, int height, float contentScaleFactor)
if (!_view || !_mainCamera) { {
if (!_view || !_mainCamera)
{
Log("Skipping camera update, no view or camrea"); Log("Skipping camera update, no view or camrea");
return; return;
} }
@@ -944,20 +1044,24 @@ void FilamentViewer::updateViewportAndCameraProjection(
contentScaleFactor); contentScaleFactor);
} }
void FilamentViewer::setViewFrustumCulling(bool enabled) { void FilamentViewer::setViewFrustumCulling(bool enabled)
{
_view->setFrustumCullingEnabled(enabled); _view->setFrustumCullingEnabled(enabled);
} }
void FilamentViewer::setCameraPosition(float x, float y, float z) { void FilamentViewer::setCameraPosition(float x, float y, float z)
{
Camera &cam = _view->getCamera(); Camera &cam = _view->getCamera();
_cameraPosition = math::mat4f::translation(math::float3(x, y, z)); _cameraPosition = math::mat4f::translation(math::float3(x, y, z));
cam.setModelMatrix(_cameraPosition * _cameraRotation); cam.setModelMatrix(_cameraPosition * _cameraRotation);
} }
void FilamentViewer::moveCameraToAsset(EntityId entityId) { void FilamentViewer::moveCameraToAsset(EntityId entityId)
{
auto asset = _assetManager->getAssetByEntityId(entityId); auto asset = _assetManager->getAssetByEntityId(entityId);
if(!asset) { if (!asset)
{
Log("Failed to find asset attached to specified entity id."); Log("Failed to find asset attached to specified entity id.");
return; return;
} }
@@ -971,13 +1075,15 @@ void FilamentViewer::moveCameraToAsset(EntityId entityId) {
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()); 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) { void FilamentViewer::setCameraRotation(float rads, float x, float y, float z)
{
Camera &cam = _view->getCamera(); Camera &cam = _view->getCamera();
_cameraRotation = math::mat4f::rotation(rads, math::float3(x, y, z)); _cameraRotation = math::mat4f::rotation(rads, math::float3(x, y, z));
cam.setModelMatrix(_cameraPosition * _cameraRotation); cam.setModelMatrix(_cameraPosition * _cameraRotation);
} }
void FilamentViewer::setCameraModelMatrix(const float* const matrix) { void FilamentViewer::setCameraModelMatrix(const float *const matrix)
{
Camera &cam = _view->getCamera(); Camera &cam = _view->getCamera();
mat4 modelMatrix( mat4 modelMatrix(
@@ -996,75 +1102,123 @@ void FilamentViewer::setCameraModelMatrix(const float* const matrix) {
matrix[12], matrix[12],
matrix[13], matrix[13],
matrix[14], matrix[14],
matrix[15] matrix[15]);
);
cam.setModelMatrix(modelMatrix); cam.setModelMatrix(modelMatrix);
} }
void FilamentViewer::grabBegin(float x, float y, bool pan) { void FilamentViewer::_createManipulator()
if (!_view || !_mainCamera || !_swapChain) { {
Log("View not ready, ignoring grab"); Camera &cam = _view->getCamera();
return; math::double3 home = cam.getPosition();
} math::double3 up = cam.getUpVector();
_panning = pan; math::double3 target = home + cam.getForwardVector();
_startX = x; Viewport const &vp = _view->getViewport();
_startY = y; 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<double>::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::grabUpdate(float x, float y) { void FilamentViewer::grabBegin(float x, float y, bool pan)
if (!_view || !_swapChain) { {
if (!_view || !_mainCamera || !_swapChain)
{
Log("View not ready, ignoring grab"); Log("View not ready, ignoring grab");
return; return;
} }
Camera& cam =_view->getCamera(); if (!_manipulator)
auto eye = cam.getPosition(); {
auto target = eye + cam.getForwardVector(); _createManipulator();
auto upward = cam.getUpVector(); }
Viewport const& vp = _view->getViewport(); if(pan) {
if(_panning) { Log("Beginning pan at %f %f", x, y);
auto trans = cam.getModelMatrix() * mat4::translation(math::float3 { 50 * (x - _startX) / vp.width, 50 * (y - _startY) / vp.height, 0.0f });
cam.setModelMatrix(trans);
} else { } else {
auto trans = cam.getModelMatrix() * mat4::rotation(0.05, Log("Beginning rotate at %f %f", x, y);
math::float3 { (y - _startY) / vp.height, (x - _startX) / vp.width, 0.0f });
cam.setModelMatrix(trans);
}
_startX = x;
_startY = y;
} }
void FilamentViewer::grabEnd() { _manipulator->grabBegin(x, y, pan);
if (!_view || !_mainCamera || !_swapChain) { }
void FilamentViewer::grabUpdate(float x, float y)
{
if (!_view || !_swapChain)
{
Log("View not ready, ignoring grab"); Log("View not ready, ignoring grab");
return; 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::scrollBegin() { void FilamentViewer::grabEnd()
// noop {
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::scrollUpdate(float x, float y, float delta) { void FilamentViewer::scrollBegin()
Camera& cam =_view->getCamera(); {
Viewport const& vp = _view->getViewport(); if (!_manipulator)
auto trans = cam.getModelMatrix() * mat4::translation(math::float3 {0.0f, 0.0f, delta }); {
cam.setModelMatrix(trans); _createManipulator();
}
} }
void FilamentViewer::scrollEnd() { void FilamentViewer::scrollUpdate(float x, float y, float delta)
// noop {
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::pick(uint32_t x, uint32_t y, EntityId* entityId) { void FilamentViewer::scrollEnd()
Log("Picking at %d,%d", x, y); {
delete _manipulator;
_manipulator = nullptr;
}
void FilamentViewer::pick(uint32_t x, uint32_t y, EntityId *entityId)
{
_view->pick(x, y, [=](filament::View::PickingQueryResult const &result) { _view->pick(x, y, [=](filament::View::PickingQueryResult const &result) {
*entityId = Entity::smuggle(result.renderable); *entityId = Entity::smuggle(result.renderable);
Log("Got result %d", *entityId);
}); });
} }
} // namespace polyvox } // namespace polyvox

View File

@@ -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 <camutils/Bookmark.h>
#include <camutils/Manipulator.h>
#include <math/scalar.h>
#include <math/vec3.h>
using namespace filament::math;
namespace filament {
namespace camutils {
template <typename FLOAT>
Bookmark<FLOAT> Bookmark<FLOAT>::interpolate(Bookmark<FLOAT> a, Bookmark<FLOAT> b, double t) {
Bookmark<FLOAT> result;
using float3 = filament::math::vec3<FLOAT>;
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<FLOAT> 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 <typename FLOAT>
double Bookmark<FLOAT>::duration(Bookmark<FLOAT> a, Bookmark<FLOAT> 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<float>;
} // namespace camutils
} // namespace filament

View File

@@ -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 <camutils/Manipulator.h>
#include <math/scalar.h>
#include <math/mat3.h>
#include <math/mat4.h>
#include <math/quat.h>
#include <cmath>
namespace filament {
namespace camutils {
using namespace filament::math;
template<typename FLOAT>
class FreeFlightManipulator : public Manipulator<FLOAT> {
public:
using vec2 = filament::math::vec2<FLOAT>;
using vec3 = filament::math::vec3<FLOAT>;
using vec4 = filament::math::vec4<FLOAT>;
using Bookmark = filament::camutils::Bookmark<FLOAT>;
using Base = Manipulator<FLOAT>;
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 */

View File

@@ -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 <camutils/Manipulator.h>
#include <math/scalar.h>
#include "FreeFlightManipulator.h"
#include "MapManipulator.h"
#include "OrbitManipulator.h"
using namespace filament::math;
namespace filament {
namespace camutils {
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::viewport(int width, int height) {
details.viewport[0] = width;
details.viewport[1] = height;
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::targetPosition(FLOAT x, FLOAT y, FLOAT z) {
details.targetPosition = {x, y, z};
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::upVector(FLOAT x, FLOAT y, FLOAT z) {
details.upVector = {x, y, z};
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::zoomSpeed(FLOAT val) {
details.zoomSpeed = val;
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::orbitHomePosition(FLOAT x, FLOAT y, FLOAT z) {
details.orbitHomePosition = {x, y, z};
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::orbitSpeed(FLOAT x, FLOAT y) {
details.orbitSpeed = {x, y};
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::fovDirection(Fov fov) {
details.fovDirection = fov;
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::fovDegrees(FLOAT degrees) {
details.fovDegrees = degrees;
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::farPlane(FLOAT distance) {
details.farPlane = distance;
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::mapExtent(FLOAT worldWidth, FLOAT worldHeight) {
details.mapExtent = {worldWidth, worldHeight};
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::mapMinDistance(FLOAT mindist) {
details.mapMinDistance = mindist;
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::flightStartPosition(FLOAT x, FLOAT y, FLOAT z) {
details.flightStartPosition = {x, y, z};
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::flightStartOrientation(FLOAT pitch, FLOAT yaw) {
details.flightStartPitch = pitch;
details.flightStartYaw = yaw;
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::flightMaxMoveSpeed(FLOAT maxSpeed) {
details.flightMaxSpeed = maxSpeed;
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::flightSpeedSteps(int steps) {
details.flightSpeedSteps = steps;
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::flightPanSpeed(FLOAT x, FLOAT y) {
details.flightPanSpeed = {x, y};
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::flightMoveDamping(FLOAT damping) {
details.flightMoveDamping = damping;
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::groundPlane(FLOAT a, FLOAT b, FLOAT c, FLOAT d) {
details.groundPlane = {a, b, c, d};
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::raycastCallback(RayCallback cb, void* userdata) {
details.raycastCallback = cb;
details.raycastUserdata = userdata;
return *this;
}
template <typename FLOAT>
Manipulator<FLOAT>* Manipulator<FLOAT>::Builder::build(Mode mode) {
switch (mode) {
case Mode::FREE_FLIGHT:
return new FreeFlightManipulator<FLOAT>(mode, details);
case Mode::MAP:
return new MapManipulator<FLOAT>(mode, details);
case Mode::ORBIT:
return new OrbitManipulator<FLOAT>(mode, details);
}
}
template <typename FLOAT>
Manipulator<FLOAT>::Manipulator(Mode mode, const Config& props) : mMode(mode) {
setProperties(props);
}
template <typename FLOAT>
void Manipulator<FLOAT>::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 <typename FLOAT>
void Manipulator<FLOAT>::setViewport(int width, int height) {
Config props = mProps;
props.viewport[0] = width;
props.viewport[1] = height;
setProperties(props);
}
template <typename FLOAT>
void Manipulator<FLOAT>::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<typename FLOAT>
static bool raycastPlane(const filament::math::vec3<FLOAT>& origin,
const filament::math::vec3<FLOAT>& dir, FLOAT* t, void* userdata) {
using vec3 = filament::math::vec3<FLOAT>;
using vec4 = filament::math::vec4<FLOAT>;
auto props = (const typename Manipulator<FLOAT>::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 <typename FLOAT>
void Manipulator<FLOAT>::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 <typename FLOAT>
bool Manipulator<FLOAT>::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<FLOAT>;
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 <typename FLOAT>
filament::math::vec3<FLOAT> Manipulator<FLOAT>::raycastFarPlane(int x, int y) const {
const filament::math::vec3<FLOAT> 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 <typename FLOAT>
void Manipulator<FLOAT>::keyDown(Manipulator<FLOAT>::Key key) { }
template <typename FLOAT>
void Manipulator<FLOAT>::keyUp(Manipulator<FLOAT>::Key key) { }
template <typename FLOAT>
void Manipulator<FLOAT>::update(FLOAT deltaTime) { }
template class Manipulator<float>;
template class Manipulator<double>;
} // namespace camutils
} // namespace filament

View File

@@ -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 <camutils/Manipulator.h>
#include <math/vec3.h>
namespace filament {
namespace camutils {
template<typename FLOAT>
class MapManipulator : public Manipulator<FLOAT> {
public:
using vec2 = math::vec2<FLOAT>;
using vec3 = math::vec3<FLOAT>;
using vec4 = math::vec4<FLOAT>;
using Bookmark = filament::camutils::Bookmark<FLOAT>;
using Base = Manipulator<FLOAT>;
using Config = typename Manipulator<FLOAT>::Config;
MapManipulator(Mode mode, const Config& props) : Manipulator<FLOAT>(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 */

View File

@@ -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 <camutils/Manipulator.h>
#include <math/scalar.h>
#define MAX_PHI (F_PI / 2.0 - 0.001)
namespace filament {
namespace camutils {
using namespace filament::math;
template<typename FLOAT>
class OrbitManipulator : public Manipulator<FLOAT> {
public:
using vec2 = filament::math::vec2<FLOAT>;
using vec3 = filament::math::vec3<FLOAT>;
using vec4 = filament::math::vec4<FLOAT>;
using Bookmark = filament::camutils::Bookmark<FLOAT>;
using Base = Manipulator<FLOAT>;
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 */