691 lines
25 KiB
C++
691 lines
25 KiB
C++
#include <memory>
|
|
|
|
/*
|
|
* Copyright (C) 2015 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 "FilamentApp.h"
|
|
|
|
#if !defined(WIN32)
|
|
# include <unistd.h>
|
|
#else
|
|
# include <SDL_syswm.h>
|
|
# include <utils/unwindows.h>
|
|
#endif
|
|
|
|
#include <imgui.h>
|
|
|
|
#include <utils/Panic.h>
|
|
#include <utils/Path.h>
|
|
|
|
#include <filament/Camera.h>
|
|
#include <filament/Material.h>
|
|
#include <filament/MaterialInstance.h>
|
|
#include <filament/Renderer.h>
|
|
#include <filament/RenderableManager.h>
|
|
#include <filament/Scene.h>
|
|
#include <filament/Skybox.h>
|
|
#include <filament/View.h>
|
|
|
|
#include <filagui/ImGuiHelper.h>
|
|
|
|
#include "Cube.h"
|
|
#include "NativeWindowHelper.h"
|
|
|
|
using namespace filament;
|
|
using namespace filagui;
|
|
using namespace math;
|
|
using namespace utils;
|
|
|
|
static constexpr uint8_t AI_DEFAULT_MAT_PACKAGE[] = {
|
|
#include "generated/material/aiDefaultMat.inc"
|
|
};
|
|
|
|
static constexpr uint8_t TRANSPARENT_COLOR_PACKAGE[] = {
|
|
#include "generated/material/transparentColor.inc"
|
|
};
|
|
|
|
static constexpr uint8_t DEPTH_VISUALIZER_PACKAGE[] = {
|
|
#include "generated/material/depthVisualizer.inc"
|
|
};
|
|
|
|
FilamentApp& FilamentApp::get() {
|
|
static FilamentApp filamentApp;
|
|
return filamentApp;
|
|
}
|
|
|
|
FilamentApp::FilamentApp() {
|
|
initSDL();
|
|
}
|
|
|
|
FilamentApp::~FilamentApp() {
|
|
SDL_Quit();
|
|
}
|
|
|
|
void FilamentApp::run(const Config& config, SetupCallback setupCallback,
|
|
CleanupCallback cleanupCallback, ImGuiCallback imguiCallback,
|
|
PreRenderCallback preRender, PostRenderCallback postRender,
|
|
size_t width, size_t height) {
|
|
std::unique_ptr<FilamentApp::Window> window(
|
|
new FilamentApp::Window(this, config, config.title, width, height));
|
|
|
|
mDepthMaterial = Material::Builder()
|
|
.package((void*) DEPTH_VISUALIZER_PACKAGE, sizeof(DEPTH_VISUALIZER_PACKAGE))
|
|
.build(*mEngine);
|
|
|
|
mDepthMI = mDepthMaterial->createInstance();
|
|
|
|
mDefaultMaterial = Material::Builder()
|
|
.package((void*) AI_DEFAULT_MAT_PACKAGE, sizeof(AI_DEFAULT_MAT_PACKAGE))
|
|
.build(*mEngine);
|
|
|
|
mTransparentMaterial = Material::Builder()
|
|
.package((void*) TRANSPARENT_COLOR_PACKAGE, sizeof(TRANSPARENT_COLOR_PACKAGE))
|
|
.build(*mEngine);
|
|
|
|
std::unique_ptr<Cube> cameraCube(new Cube(*mEngine, mTransparentMaterial, {1,0,0}));
|
|
// we can't cull the light-frustum because it's not applied a rigid transform
|
|
// and currently, filament assumes that for culling
|
|
std::unique_ptr<Cube> lightmapCube(new Cube(*mEngine, mTransparentMaterial, {0,1,0}, false));
|
|
mScene = mEngine->createScene();
|
|
|
|
window->mMainView->getView()->setVisibleLayers(0x4, 0x4);
|
|
|
|
window->mUiView->getView()->setClearTargets(false, false, false);
|
|
window->mUiView->getView()->setRenderTarget(View::TargetBufferFlags::DEPTH_AND_STENCIL);
|
|
window->mUiView->getView()->setPostProcessingEnabled(false);
|
|
window->mUiView->getView()->setShadowsEnabled(false);
|
|
|
|
if (config.splitView) {
|
|
auto& rcm = mEngine->getRenderableManager();
|
|
|
|
rcm.setLayerMask(rcm.getInstance(cameraCube->getSolidRenderable()), 0x3, 0x2);
|
|
rcm.setLayerMask(rcm.getInstance(cameraCube->getWireFrameRenderable()), 0x3, 0x2);
|
|
cameraCube->mapFrustum(*mEngine, window->mMainCameraMan.getCamera());
|
|
|
|
rcm.setLayerMask(rcm.getInstance(lightmapCube->getSolidRenderable()), 0x3, 0x2);
|
|
rcm.setLayerMask(rcm.getInstance(lightmapCube->getWireFrameRenderable()), 0x3, 0x2);
|
|
|
|
// Create the camera mesh
|
|
window->mMainCameraMan.setCameraChangedCallback(
|
|
[&cameraCube, &lightmapCube, &window, engine = mEngine](Camera const* camera) {
|
|
cameraCube->mapFrustum(*engine, camera);
|
|
lightmapCube->mapFrustum(*engine,
|
|
window->mMainView->getView()->getDirectionalLightCamera());
|
|
});
|
|
|
|
mScene->addEntity(cameraCube->getWireFrameRenderable());
|
|
mScene->addEntity(cameraCube->getSolidRenderable());
|
|
|
|
mScene->addEntity(lightmapCube->getWireFrameRenderable());
|
|
mScene->addEntity(lightmapCube->getSolidRenderable());
|
|
|
|
window->mGodView->getView()->setVisibleLayers(0x6, 0x6);
|
|
window->mOrthoView->getView()->setVisibleLayers(0x6, 0x6);
|
|
|
|
// only preserve the color buffer for additional views; depth and stencil can be discarded.
|
|
window->mDepthView->getView()->setRenderTarget(View::TargetBufferFlags::DEPTH_AND_STENCIL);
|
|
window->mGodView->getView()->setRenderTarget(View::TargetBufferFlags::DEPTH_AND_STENCIL);
|
|
window->mOrthoView->getView()->setRenderTarget(View::TargetBufferFlags::DEPTH_AND_STENCIL);
|
|
|
|
window->mDepthView->getView()->setShadowsEnabled(false);
|
|
window->mGodView->getView()->setShadowsEnabled(false);
|
|
window->mOrthoView->getView()->setShadowsEnabled(false);
|
|
}
|
|
|
|
loadIBL(config);
|
|
if (mIBL != nullptr) {
|
|
mIBL->getSkybox()->setLayerMask(0x7, 0x4);
|
|
mScene->setSkybox(mIBL->getSkybox());
|
|
mScene->setIndirectLight(mIBL->getIndirectLight());
|
|
}
|
|
|
|
for (auto& view : window->mViews) {
|
|
if (view.get() != window->mUiView) {
|
|
view->getView()->setScene(mScene);
|
|
}
|
|
}
|
|
|
|
setupCallback(mEngine, window->mMainView->getView(), mScene);
|
|
|
|
if (imguiCallback) {
|
|
mImGuiHelper = std::make_unique<ImGuiHelper>(mEngine, window->mUiView->getView(),
|
|
getRootPath() + "assets/fonts/Roboto-Medium.ttf");
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
#ifdef WIN32
|
|
SDL_SysWMinfo wmInfo;
|
|
SDL_VERSION(&wmInfo.version);
|
|
SDL_GetWindowWMInfo(window->getSDLWindow(), &wmInfo);
|
|
io.ImeWindowHandle = wmInfo.info.win.window;
|
|
#endif
|
|
io.KeyMap[ImGuiKey_Tab] = SDL_SCANCODE_TAB;
|
|
io.KeyMap[ImGuiKey_LeftArrow] = SDL_SCANCODE_LEFT;
|
|
io.KeyMap[ImGuiKey_RightArrow] = SDL_SCANCODE_RIGHT;
|
|
io.KeyMap[ImGuiKey_UpArrow] = SDL_SCANCODE_UP;
|
|
io.KeyMap[ImGuiKey_DownArrow] = SDL_SCANCODE_DOWN;
|
|
io.KeyMap[ImGuiKey_PageUp] = SDL_SCANCODE_PAGEUP;
|
|
io.KeyMap[ImGuiKey_PageDown] = SDL_SCANCODE_PAGEDOWN;
|
|
io.KeyMap[ImGuiKey_Home] = SDL_SCANCODE_HOME;
|
|
io.KeyMap[ImGuiKey_End] = SDL_SCANCODE_END;
|
|
io.KeyMap[ImGuiKey_Insert] = SDL_SCANCODE_INSERT;
|
|
io.KeyMap[ImGuiKey_Delete] = SDL_SCANCODE_DELETE;
|
|
io.KeyMap[ImGuiKey_Backspace] = SDL_SCANCODE_BACKSPACE;
|
|
io.KeyMap[ImGuiKey_Space] = SDL_SCANCODE_SPACE;
|
|
io.KeyMap[ImGuiKey_Enter] = SDL_SCANCODE_RETURN;
|
|
io.KeyMap[ImGuiKey_Escape] = SDL_SCANCODE_ESCAPE;
|
|
io.KeyMap[ImGuiKey_A] = SDL_SCANCODE_A;
|
|
io.KeyMap[ImGuiKey_C] = SDL_SCANCODE_C;
|
|
io.KeyMap[ImGuiKey_V] = SDL_SCANCODE_V;
|
|
io.KeyMap[ImGuiKey_X] = SDL_SCANCODE_X;
|
|
io.KeyMap[ImGuiKey_Y] = SDL_SCANCODE_Y;
|
|
io.KeyMap[ImGuiKey_Z] = SDL_SCANCODE_Z;
|
|
io.SetClipboardTextFn = [](void*, const char* text) {
|
|
SDL_SetClipboardText(text);
|
|
};
|
|
io.GetClipboardTextFn = [](void*) -> const char* {
|
|
return SDL_GetClipboardText();
|
|
};
|
|
io.ClipboardUserData = nullptr;
|
|
}
|
|
|
|
bool mousePressed[3] = { false };
|
|
|
|
while (!mClosed) {
|
|
|
|
if (!UTILS_HAS_THREADING) {
|
|
mEngine->execute();
|
|
}
|
|
|
|
// Allow the app to animate the scene if desired.
|
|
if (mAnimation) {
|
|
double now = (double) SDL_GetPerformanceCounter() / SDL_GetPerformanceFrequency();
|
|
mAnimation(mEngine, window->mMainView->getView(), now);
|
|
}
|
|
|
|
// Loop over fresh events twice: first stash them and let ImGui process them, then allow
|
|
// the app to process the stashed events. This is done because ImGui might wish to block
|
|
// certain events from the app (e.g., when dragging the mouse over an obscuring window).
|
|
constexpr int kMaxEvents = 16;
|
|
SDL_Event events[kMaxEvents];
|
|
int nevents = 0;
|
|
while (nevents < kMaxEvents && SDL_PollEvent(&events[nevents]) != 0) {
|
|
if (mImGuiHelper) {
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
SDL_Event* event = &events[nevents];
|
|
switch (event->type) {
|
|
case SDL_MOUSEWHEEL: {
|
|
if (event->wheel.x > 0) io.MouseWheelH += 1;
|
|
if (event->wheel.x < 0) io.MouseWheelH -= 1;
|
|
if (event->wheel.y > 0) io.MouseWheel += 1;
|
|
if (event->wheel.y < 0) io.MouseWheel -= 1;
|
|
break;
|
|
}
|
|
case SDL_MOUSEBUTTONDOWN: {
|
|
if (event->button.button == SDL_BUTTON_LEFT) mousePressed[0] = true;
|
|
if (event->button.button == SDL_BUTTON_RIGHT) mousePressed[1] = true;
|
|
if (event->button.button == SDL_BUTTON_MIDDLE) mousePressed[2] = true;
|
|
break;
|
|
}
|
|
case SDL_TEXTINPUT: {
|
|
io.AddInputCharactersUTF8(event->text.text);
|
|
break;
|
|
}
|
|
case SDL_KEYDOWN:
|
|
case SDL_KEYUP: {
|
|
int key = event->key.keysym.scancode;
|
|
IM_ASSERT(key >= 0 && key < IM_ARRAYSIZE(io.KeysDown));
|
|
io.KeysDown[key] = (event->type == SDL_KEYDOWN);
|
|
io.KeyShift = ((SDL_GetModState() & KMOD_SHIFT) != 0);
|
|
io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0);
|
|
io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0);
|
|
io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
nevents++;
|
|
}
|
|
|
|
// Now, loop over the events a second time for app-side processing.
|
|
for (int i = 0; i < nevents; i++) {
|
|
const SDL_Event& event = events[i];
|
|
ImGuiIO* io = mImGuiHelper ? &ImGui::GetIO() : nullptr;
|
|
switch (event.type) {
|
|
case SDL_QUIT:
|
|
mClosed = true;
|
|
break;
|
|
case SDL_KEYDOWN:
|
|
if (event.key.keysym.scancode == SDL_SCANCODE_ESCAPE) {
|
|
mClosed = true;
|
|
}
|
|
break;
|
|
case SDL_MOUSEWHEEL:
|
|
if (!io || !io->WantCaptureMouse)
|
|
window->mouseWheel(event.wheel.y);
|
|
break;
|
|
case SDL_MOUSEBUTTONDOWN:
|
|
if (!io || !io->WantCaptureMouse)
|
|
window->mouseDown(event.button.button, event.button.x, event.button.y);
|
|
break;
|
|
case SDL_MOUSEBUTTONUP:
|
|
if (!io || !io->WantCaptureMouse)
|
|
window->mouseUp(event.button.x, event.button.y);
|
|
break;
|
|
case SDL_MOUSEMOTION:
|
|
if (!io || !io->WantCaptureMouse)
|
|
window->mouseMoved(event.motion.x, event.motion.y);
|
|
break;
|
|
case SDL_WINDOWEVENT:
|
|
switch (event.window.event) {
|
|
case SDL_WINDOWEVENT_RESIZED:
|
|
window->resize();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Populate the UI scene, regardless of whether Filament wants to a skip frame. We should
|
|
// always let ImGui generate a command list; if it skips a frame it'll destroy its widgets.
|
|
if (mImGuiHelper) {
|
|
|
|
// Inform ImGui of the current window size in case it was resized.
|
|
int windowWidth, windowHeight;
|
|
int displayWidth, displayHeight;
|
|
SDL_GetWindowSize(window->mWindow, &windowWidth, &windowHeight);
|
|
SDL_GL_GetDrawableSize(window->mWindow, &displayWidth, &displayHeight);
|
|
mImGuiHelper->setDisplaySize(windowWidth, windowHeight,
|
|
windowWidth > 0 ? ((float)displayWidth / windowWidth) : 0,
|
|
displayHeight > 0 ? ((float)displayHeight / windowHeight) : 0);
|
|
|
|
// Setup mouse inputs (we already got mouse wheel, keyboard keys & characters
|
|
// from our event handler)
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
int mx, my;
|
|
Uint32 buttons = SDL_GetMouseState(&mx, &my);
|
|
io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
|
|
io.MouseDown[0] = mousePressed[0] || (buttons & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0;
|
|
io.MouseDown[1] = mousePressed[1] || (buttons & SDL_BUTTON(SDL_BUTTON_RIGHT)) != 0;
|
|
io.MouseDown[2] = mousePressed[2] || (buttons & SDL_BUTTON(SDL_BUTTON_MIDDLE)) != 0;
|
|
mousePressed[0] = mousePressed[1] = mousePressed[2] = false;
|
|
|
|
// TODO: Update to a newer SDL and use SDL_CaptureMouse() to retrieve mouse coordinates
|
|
// outside of the client area; see the imgui SDL example.
|
|
if ((SDL_GetWindowFlags(window->mWindow) & SDL_WINDOW_INPUT_FOCUS) != 0) {
|
|
io.MousePos = ImVec2((float)mx, (float)my);
|
|
}
|
|
|
|
// Populate the UI Scene.
|
|
static Uint64 frequency = SDL_GetPerformanceFrequency();
|
|
Uint64 now = SDL_GetPerformanceCounter();
|
|
float timeStep = mTime > 0 ? (float)((double)(now - mTime) / frequency) :
|
|
(float)(1.0f / 60.0f);
|
|
mTime = now;
|
|
mImGuiHelper->render(timeStep, imguiCallback);
|
|
}
|
|
|
|
window->mMainCameraMan.updateCameraTransform();
|
|
|
|
// TODO: we need better timing or use SDL_GL_SetSwapInterval
|
|
SDL_Delay(16);
|
|
|
|
Renderer* renderer = window->getRenderer();
|
|
|
|
if (preRender) {
|
|
for (auto const& view : window->mViews) {
|
|
if (view.get() != window->mUiView) {
|
|
preRender(mEngine, view->getView(), mScene, renderer);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (renderer->beginFrame(window->getSwapChain())) {
|
|
for (auto const& view : window->mViews) {
|
|
renderer->render(view->getView());
|
|
}
|
|
renderer->endFrame();
|
|
}
|
|
|
|
if (postRender) {
|
|
for (auto const& view : window->mViews) {
|
|
if (view.get() != window->mUiView) {
|
|
postRender(mEngine, view->getView(), mScene, renderer);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mImGuiHelper) {
|
|
mImGuiHelper.reset();
|
|
}
|
|
|
|
cleanupCallback(mEngine, window->mMainView->getView(), mScene);
|
|
|
|
cameraCube.reset();
|
|
lightmapCube.reset();
|
|
window.reset();
|
|
|
|
mIBL.reset();
|
|
mEngine->destroy(mDepthMI);
|
|
mEngine->destroy(mDepthMaterial);
|
|
mEngine->destroy(mDefaultMaterial);
|
|
mEngine->destroy(mTransparentMaterial);
|
|
mEngine->destroy(mScene);
|
|
Engine::destroy(&mEngine);
|
|
mEngine = nullptr;
|
|
}
|
|
|
|
void FilamentApp::loadIBL(const Config& config) {
|
|
if (!config.iblDirectory.empty()) {
|
|
Path iblPath(config.iblDirectory);
|
|
|
|
if (!iblPath.exists()) {
|
|
std::cerr << "The specified IBL path does not exist: " << iblPath << std::endl;
|
|
return;
|
|
}
|
|
|
|
if (!iblPath.isDirectory()) {
|
|
std::cerr << "The specified IBL path is not a directory: " << iblPath << std::endl;
|
|
return;
|
|
}
|
|
|
|
mIBL = std::make_unique<IBL>(*mEngine);
|
|
if (!mIBL->loadFromDirectory(iblPath)) {
|
|
std::cerr << "Could not load the specified IBL: " << iblPath << std::endl;
|
|
mIBL.reset(nullptr);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FilamentApp::initSDL() {
|
|
ASSERT_POSTCONDITION(SDL_Init(SDL_INIT_EVENTS) == 0, "SDL_Init Failure");
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
|
|
FilamentApp::Window::Window(FilamentApp* filamentApp,
|
|
const Config& config, std::string title, size_t w, size_t h)
|
|
: mFilamentApp(filamentApp) {
|
|
const int x = SDL_WINDOWPOS_CENTERED;
|
|
const int y = SDL_WINDOWPOS_CENTERED;
|
|
const uint32_t windowFlags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;
|
|
mWindow = SDL_CreateWindow(title.c_str(), x, y, (int) w, (int) h, windowFlags);
|
|
|
|
// Create the Engine after the window in case this happens to be a single-threaded platform.
|
|
// For single-threaded platforms, we need to ensure that Filament's OpenGL context is current,
|
|
// rather than the one created by SDL.
|
|
mFilamentApp->mEngine = Engine::create(config.backend);
|
|
|
|
// HACK: We don't use SDL's 2D rendering functionality, but by invoking it we cause
|
|
// SDL to create a Metal backing layer, which allows us to run Vulkan apps via MoltenVK.
|
|
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN) && defined(__APPLE__)
|
|
constexpr int METAL_DRIVER = 2;
|
|
SDL_CreateRenderer(mWindow, METAL_DRIVER, SDL_RENDERER_ACCELERATED);
|
|
#endif
|
|
|
|
void* nativeWindow = ::getNativeWindow(mWindow);
|
|
mSwapChain = mFilamentApp->mEngine->createSwapChain(nativeWindow);
|
|
mRenderer = mFilamentApp->mEngine->createRenderer();
|
|
|
|
// create cameras
|
|
mCameras[0] = mMainCamera = mFilamentApp->mEngine->createCamera();
|
|
mCameras[1] = mDebugCamera = mFilamentApp->mEngine->createCamera();
|
|
mCameras[2] = mOrthoCamera = mFilamentApp->mEngine->createCamera();
|
|
mCameras[3] = mUiCamera = mFilamentApp->mEngine->createCamera();
|
|
|
|
// set exposure
|
|
for (auto camera : mCameras) {
|
|
camera->setExposure(16.0f, 1 / 125.0f, 100.0f);
|
|
}
|
|
|
|
// create views
|
|
mViews.emplace_back(mMainView = new CView(*mRenderer, "Main View"));
|
|
if (config.splitView) {
|
|
mViews.emplace_back(mDepthView = new CView(*mRenderer, "Depth View"));
|
|
mViews.emplace_back(mGodView = new GodView(*mRenderer, "God View"));
|
|
mViews.emplace_back(mOrthoView = new CView(*mRenderer, "Ortho View"));
|
|
mDepthView->getView()->setDepthPrepass(View::DepthPrepass::DISABLED);
|
|
}
|
|
mViews.emplace_back(mUiView = new CView(*mRenderer, "UI View"));
|
|
|
|
// set-up the camera manipulators
|
|
double3 at(0, 0, -4);
|
|
mMainCameraMan.setCamera(mMainCamera);
|
|
mDebugCameraMan.setCamera(mDebugCamera);
|
|
mMainView->setCamera(mMainCamera);
|
|
mMainView->setCameraManipulator(&mMainCameraMan);
|
|
mUiView->setCamera(mUiCamera);
|
|
if (config.splitView) {
|
|
// Depth view always uses the main camera
|
|
mDepthView->setCamera(mMainCamera);
|
|
|
|
// The god view uses the main camera for culling, but the debug camera for viewing
|
|
mGodView->setCamera(mMainCamera);
|
|
mGodView->setGodCamera(mDebugCamera);
|
|
|
|
// Ortho view obviously uses an ortho camera
|
|
mOrthoView->setCamera( (Camera *)mMainView->getView()->getDirectionalLightCamera() );
|
|
|
|
mDepthView->setCameraManipulator(&mMainCameraMan);
|
|
mGodView->setCameraManipulator(&mDebugCameraMan);
|
|
mOrthoView->setCameraManipulator(&mOrthoCameraMan);
|
|
}
|
|
|
|
// configure the cameras
|
|
configureCamerasForWindow();
|
|
|
|
mMainCameraMan.lookAt(at + double3{ 0, 0, 4 }, at);
|
|
mDebugCameraMan.lookAt(at + double3{ 0, 0, 4 }, at);
|
|
mOrthoCameraMan.lookAt(at + double3{ 0, 0, 4 }, at);
|
|
}
|
|
|
|
FilamentApp::Window::~Window() {
|
|
mViews.clear();
|
|
for (auto& camera : mCameras) {
|
|
mFilamentApp->mEngine->destroy(camera);
|
|
}
|
|
mFilamentApp->mEngine->destroy(mRenderer);
|
|
mFilamentApp->mEngine->destroy(mSwapChain);
|
|
SDL_DestroyWindow(mWindow);
|
|
}
|
|
|
|
void FilamentApp::Window::mouseDown(int button, ssize_t x, ssize_t y) {
|
|
fixupMouseCoordinatesForHdpi(x, y);
|
|
y = mHeight - y;
|
|
for (auto const& view : mViews) {
|
|
if (view->intersects(x, y)) {
|
|
mEventTarget = view.get();
|
|
view->mouseDown(button, x, y);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FilamentApp::Window::mouseWheel(ssize_t x) {
|
|
if (mEventTarget) {
|
|
mEventTarget->mouseWheel(x);
|
|
} else {
|
|
for (auto const& view : mViews) {
|
|
if (view->intersects(mLastX, mLastY)) {
|
|
view->mouseWheel(x);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FilamentApp::Window::mouseUp(ssize_t x, ssize_t y) {
|
|
fixupMouseCoordinatesForHdpi(x, y);
|
|
if (mEventTarget) {
|
|
y = mHeight - y;
|
|
mEventTarget->mouseUp(x, y);
|
|
mEventTarget = nullptr;
|
|
}
|
|
}
|
|
|
|
void FilamentApp::Window::mouseMoved(ssize_t x, ssize_t y) {
|
|
fixupMouseCoordinatesForHdpi(x, y);
|
|
y = mHeight - y;
|
|
if (mEventTarget) {
|
|
mEventTarget->mouseMoved(x, y);
|
|
}
|
|
mLastX = x;
|
|
mLastY = y;
|
|
}
|
|
|
|
void FilamentApp::Window::fixupMouseCoordinatesForHdpi(ssize_t& x, ssize_t& y) const {
|
|
int dw, dh, ww, wh;
|
|
SDL_GL_GetDrawableSize(mWindow, &dw, &dh);
|
|
SDL_GetWindowSize(mWindow, &ww, &wh);
|
|
x = x * dw / ww;
|
|
y = y * dh / wh;
|
|
}
|
|
|
|
void FilamentApp::Window::resize() {
|
|
mFilamentApp->mEngine->destroy(mSwapChain);
|
|
mSwapChain = mFilamentApp->mEngine->createSwapChain(::getNativeWindow(mWindow));
|
|
configureCamerasForWindow();
|
|
}
|
|
|
|
void FilamentApp::Window::configureCamerasForWindow() {
|
|
|
|
// Determine the current size of the window in physical pixels.
|
|
uint32_t w, h;
|
|
SDL_GL_GetDrawableSize(mWindow, (int*) &w, (int*) &h);
|
|
mWidth = (size_t) w;
|
|
mHeight = (size_t) h;
|
|
|
|
// Compute the "virtual pixels to physical pixels" scale factor that the
|
|
// the platform uses for UI elements.
|
|
int virtualWidth, virtualHeight;
|
|
SDL_GetWindowSize(mWindow, &virtualWidth, &virtualHeight);
|
|
float dpiScaleX = (float) w / virtualWidth;
|
|
float dpiScaleY = (float) h / virtualHeight;
|
|
|
|
const float3 at(0, 0, -4);
|
|
const double ratio = double(h) / double(w);
|
|
|
|
double near = 0.1;
|
|
double far = 50;
|
|
mMainCamera->setProjection(45.0, double(w) / h, near, far, Camera::Fov::VERTICAL);
|
|
mDebugCamera->setProjection(45.0, double(w) / h, 0.0625, 4096, Camera::Fov::VERTICAL);
|
|
mOrthoCamera->setProjection(Camera::Projection::ORTHO, -3, 3, -3 * ratio, 3 * ratio, near, far);
|
|
mOrthoCamera->lookAt(at + float3{ 4, 0, 0 }, at);
|
|
mUiCamera->setProjection(Camera::Projection::ORTHO,
|
|
0.0, w / dpiScaleX,
|
|
h / dpiScaleY, 0.0,
|
|
0.0, 1.0);
|
|
|
|
// We're in split view when there are more views than just the Main and UI views.
|
|
if (mViews.size() > 2) {
|
|
uint32_t vpw = w / 2;
|
|
uint32_t vph = h / 2;
|
|
mMainView->setViewport ({ 0, 0, vpw, vph });
|
|
mDepthView->setViewport({ int32_t(vpw), 0, w - vpw, vph });
|
|
mGodView->setViewport ({ int32_t(vpw), int32_t(vph), w - vpw, h - vph });
|
|
mOrthoView->setViewport({ 0, int32_t(vph), vpw, h - vph });
|
|
|
|
mMainView->getCameraManipulator()->updateCameraTransform();
|
|
mDepthView->getCameraManipulator()->updateCameraTransform();
|
|
mGodView->getCameraManipulator()->updateCameraTransform();
|
|
mOrthoView->getCameraManipulator()->updateCameraTransform();
|
|
} else {
|
|
mMainView->setViewport({ 0, 0, w, h });
|
|
}
|
|
mUiView->setViewport({ 0, 0, w, h });
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
|
|
FilamentApp::CView::CView(Renderer& renderer, std::string name)
|
|
: engine(*renderer.getEngine()), mName(name) {
|
|
view = engine.createView();
|
|
view->setClearColor({ 0 });
|
|
view->setName(name.c_str());
|
|
}
|
|
|
|
FilamentApp::CView::~CView() {
|
|
engine.destroy(view);
|
|
}
|
|
|
|
void FilamentApp::CView::setViewport(Viewport const& viewport) {
|
|
mViewport = viewport;
|
|
view->setViewport(viewport);
|
|
if (mCameraManipulator) {
|
|
mCameraManipulator->setViewport(viewport.width, viewport.height);
|
|
}
|
|
}
|
|
|
|
void FilamentApp::CView::mouseDown(int button, ssize_t x, ssize_t y) {
|
|
mLastMousePosition = double2(x, y);
|
|
if (button == 1) {
|
|
mMode = Mode::ROTATE;
|
|
} else if (button == 3) {
|
|
mMode = Mode::TRACK;
|
|
}
|
|
}
|
|
|
|
void FilamentApp::CView::mouseUp(ssize_t x, ssize_t y) {
|
|
mMode = Mode::NONE;
|
|
}
|
|
|
|
void FilamentApp::CView::mouseMoved(ssize_t x, ssize_t y) {
|
|
if (mCameraManipulator) {
|
|
double2 delta = double2(x, y) - mLastMousePosition;
|
|
mLastMousePosition = double2(x, y);
|
|
switch (mMode) {
|
|
case Mode::NONE:
|
|
break;
|
|
case Mode::ROTATE:
|
|
mCameraManipulator->rotate(delta);
|
|
break;
|
|
case Mode::TRACK:
|
|
mCameraManipulator->track(delta);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FilamentApp::CView::mouseWheel(ssize_t x) {
|
|
if (mCameraManipulator){
|
|
mCameraManipulator->dolly(x);
|
|
}
|
|
}
|
|
|
|
bool FilamentApp::CView::intersects(ssize_t x, ssize_t y) {
|
|
if (x >= mViewport.left && x < mViewport.left + mViewport.width)
|
|
if (y >= mViewport.bottom && y < mViewport.bottom + mViewport.height)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void FilamentApp::CView::setCameraManipulator(CameraManipulator* cm) {
|
|
mCameraManipulator = cm;
|
|
}
|
|
|
|
void FilamentApp::CView::setCamera(Camera* camera) {
|
|
view->setCamera(camera);
|
|
}
|
|
|
|
void FilamentApp::GodView::setGodCamera(Camera* camera) {
|
|
getView()->setDebugCamera(camera);
|
|
}
|