Files
filament/libs/viewer/src/ViewerGui.cpp
Philip Rideout c4abe4f46a gltfio: allow zero-instance assets.
This is a feature request from Google. It allows users to "preload" an
asset, ie you can now create all VertexBuffer objects, Texture objects,
etc, without actually creating any entities or renderable components.

In the past we used TransformManager to help out with computing the big
asset-level bounding box, but now we use `gltf_node_transform_world()`
because entities might not yet exist.

One minor side effect is that `FilamentAsset::getBoundingBox()` now
returns the AABB that was determined at load time, and does not account
for instances. As a result, our `gltf_instances` sample app looks
slightly different but this is expected.
2022-09-14 16:40:47 -07:00

1093 lines
46 KiB
C++

/*
* 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 <viewer/ViewerGui.h>
#include <filament/RenderableManager.h>
#include <filament/TransformManager.h>
#include <filament/LightManager.h>
#include <filament/View.h>
#include <filament/Viewport.h>
#include <filagui/ImGuiHelper.h>
#include <utils/EntityManager.h>
#include <math/mat4.h>
#include <math/vec3.h>
#include <imgui.h>
#include <filagui/ImGuiExtensions.h>
#include <string>
#include <vector>
using namespace filagui;
using namespace filament::math;
namespace filament {
namespace viewer {
mat4f fitIntoUnitCube(const Aabb& bounds, float zoffset) {
float3 minpt = bounds.min;
float3 maxpt = bounds.max;
float maxExtent;
maxExtent = std::max(maxpt.x - minpt.x, maxpt.y - minpt.y);
maxExtent = std::max(maxExtent, maxpt.z - minpt.z);
float scaleFactor = 2.0f / maxExtent;
float3 center = (minpt + maxpt) / 2.0f;
center.z += zoffset / scaleFactor;
return mat4f::scaling(float3(scaleFactor)) * mat4f::translation(-center);
}
static void computeRangePlot(Settings& settings, float* rangePlot) {
float4& ranges = settings.view.colorGrading.ranges;
ranges.y = clamp(ranges.y, ranges.x + 1e-5f, ranges.w - 1e-5f); // darks
ranges.z = clamp(ranges.z, ranges.x + 1e-5f, ranges.w - 1e-5f); // lights
for (size_t i = 0; i < 1024; i++) {
float x = i / 1024.0f;
float s = 1.0f - smoothstep(ranges.x, ranges.y, x);
float h = smoothstep(ranges.z, ranges.w, x);
rangePlot[i] = s;
rangePlot[1024 + i] = 1.0f - s - h;
rangePlot[2048 + i] = h;
}
}
static void rangePlotSeriesStart(int series) {
switch (series) {
case 0:
ImGui::PushStyleColor(ImGuiCol_PlotLines, (ImVec4) ImColor::HSV(0.4f, 0.25f, 1.0f));
break;
case 1:
ImGui::PushStyleColor(ImGuiCol_PlotLines, (ImVec4) ImColor::HSV(0.8f, 0.25f, 1.0f));
break;
case 2:
ImGui::PushStyleColor(ImGuiCol_PlotLines, (ImVec4) ImColor::HSV(0.17f, 0.21f, 1.0f));
break;
}
}
static void rangePlotSeriesEnd(int series) {
if (series < 3) {
ImGui::PopStyleColor();
}
}
static float getRangePlotValue(int series, void* data, int index) {
return ((float*) data)[series * 1024 + index];
}
inline float3 curves(float3 v, float3 shadowGamma, float3 midPoint, float3 highlightScale) {
float3 d = 1.0f / (pow(midPoint, shadowGamma - 1.0f));
float3 dark = pow(v, shadowGamma) * d;
float3 light = highlightScale * (v - midPoint) + midPoint;
return float3{
v.r <= midPoint.r ? dark.r : light.r,
v.g <= midPoint.g ? dark.g : light.g,
v.b <= midPoint.b ? dark.b : light.b,
};
}
static void computeCurvePlot(Settings& settings, float* curvePlot) {
const auto& colorGradingOptions = settings.view.colorGrading;
for (size_t i = 0; i < 1024; i++) {
float3 x{i / 1024.0f * 2.0f};
float3 y = curves(x,
colorGradingOptions.gamma,
colorGradingOptions.midPoint,
colorGradingOptions.scale);
curvePlot[i] = y.r;
curvePlot[1024 + i] = y.g;
curvePlot[2048 + i] = y.b;
}
}
static void computeToneMapPlot(ColorGradingSettings& settings, float* plot) {
float hdrMax = 10.0f;
ToneMapper* mapper;
switch (settings.toneMapping) {
case ToneMapping::LINEAR:
mapper = new LinearToneMapper;
break;
case ToneMapping::ACES_LEGACY:
mapper = new ACESLegacyToneMapper;
break;
case ToneMapping::ACES:
mapper = new ACESToneMapper;
break;
case ToneMapping::FILMIC:
mapper = new FilmicToneMapper;
break;
case ToneMapping::GENERIC:
mapper = new GenericToneMapper(
settings.genericToneMapper.contrast,
settings.genericToneMapper.midGrayIn,
settings.genericToneMapper.midGrayOut,
settings.genericToneMapper.hdrMax
);
hdrMax = settings.genericToneMapper.hdrMax;
break;
case ToneMapping::DISPLAY_RANGE:
mapper = new DisplayRangeToneMapper;
break;
}
float a = std::log10(hdrMax * 1.5f / 1e-6f);
for (size_t i = 0; i < 1024; i++) {
float v = i;
float x = 1e-6f * std::pow(10.0f, a * v / 1023.0f);
plot[i] = (*mapper)(x).r;
}
delete mapper;
}
static void tooltipFloat(float value) {
if (ImGui::IsItemActive() || ImGui::IsItemHovered()) {
ImGui::SetTooltip("%.2f", value);
}
}
static void pushSliderColors(float hue) {
ImGui::PushStyleColor(ImGuiCol_FrameBg, (ImVec4) ImColor::HSV(hue, 0.5f, 0.5f));
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, (ImVec4) ImColor::HSV(hue, 0.6f, 0.5f));
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, (ImVec4) ImColor::HSV(hue, 0.7f, 0.5f));
ImGui::PushStyleColor(ImGuiCol_SliderGrab, (ImVec4) ImColor::HSV(hue, 0.9f, 0.9f));
}
static void popSliderColors() { ImGui::PopStyleColor(4); }
static void colorGradingUI(Settings& settings, float* rangePlot, float* curvePlot, float* toneMapPlot) {
const static ImVec2 verticalSliderSize(18.0f, 160.0f);
const static ImVec2 plotLinesSize(0.0f, 160.0f);
const static ImVec2 plotLinesWideSize(0.0f, 120.0f);
if (ImGui::CollapsingHeader("Color grading")) {
ColorGradingSettings& colorGrading = settings.view.colorGrading;
ImGui::Indent();
ImGui::Checkbox("Enabled##colorGrading", &colorGrading.enabled);
int quality = (int) colorGrading.quality;
ImGui::Combo("Quality##colorGradingQuality", &quality, "Low\0Medium\0High\0Ultra\0\0");
colorGrading.quality = (decltype(colorGrading.quality)) quality;
int toneMapping = (int) colorGrading.toneMapping;
ImGui::Combo("Tone-mapping", &toneMapping,
"Linear\0ACES (legacy)\0ACES\0Filmic\0Generic\0Display Range\0\0");
colorGrading.toneMapping = (decltype(colorGrading.toneMapping)) toneMapping;
if (colorGrading.toneMapping == ToneMapping::GENERIC) {
if (ImGui::CollapsingHeader("Tonemap parameters")) {
GenericToneMapperSettings& generic = colorGrading.genericToneMapper;
ImGui::SliderFloat("Contrast##genericToneMapper", &generic.contrast, 1e-5f, 3.0f);
ImGui::SliderFloat("Mid-gray in##genericToneMapper", &generic.midGrayIn, 0.0f, 1.0f);
ImGui::SliderFloat("Mid-gray out##genericToneMapper", &generic.midGrayOut, 0.0f, 1.0f);
ImGui::SliderFloat("HDR max", &generic.hdrMax, 1.0f, 64.0f);
}
}
computeToneMapPlot(colorGrading, toneMapPlot);
ImGui::PushStyleColor(ImGuiCol_PlotLines, (ImVec4) ImColor::HSV(0.17f, 0.21f, 0.9f));
ImGui::PlotLines("", toneMapPlot, 1024, 0, "Tone map", 0.0f, 1.05f, ImVec2(0, 160));
ImGui::PopStyleColor();
ImGui::Checkbox("Luminance scaling", &colorGrading.luminanceScaling);
ImGui::Checkbox("Gamut mapping", &colorGrading.gamutMapping);
ImGui::SliderFloat("Exposure", &colorGrading.exposure, -10.0f, 10.0f);
ImGui::SliderFloat("Night adaptation", &colorGrading.nightAdaptation, 0.0f, 1.0f);
if (ImGui::CollapsingHeader("White balance")) {
int temperature = colorGrading.temperature * 100.0f;
int tint = colorGrading.tint * 100.0f;
ImGui::SliderInt("Temperature", &temperature, -100, 100);
ImGui::SliderInt("Tint", &tint, -100, 100);
colorGrading.temperature = temperature / 100.0f;
colorGrading.tint = tint / 100.0f;
}
if (ImGui::CollapsingHeader("Channel mixer")) {
pushSliderColors(0.0f / 7.0f);
ImGui::VSliderFloat("##outRed.r", verticalSliderSize, &colorGrading.outRed.r, -2.0f, 2.0f, "");
tooltipFloat(colorGrading.outRed.r);
ImGui::SameLine();
ImGui::VSliderFloat("##outRed.g", verticalSliderSize, &colorGrading.outRed.g, -2.0f, 2.0f, "");
tooltipFloat(colorGrading.outRed.g);
ImGui::SameLine();
ImGui::VSliderFloat("##outRed.b", verticalSliderSize, &colorGrading.outRed.b, -2.0f, 2.0f, "");
tooltipFloat(colorGrading.outRed.b);
ImGui::SameLine(0.0f, 18.0f);
popSliderColors();
pushSliderColors(2.0f / 7.0f);
ImGui::VSliderFloat("##outGreen.r", verticalSliderSize, &colorGrading.outGreen.r, -2.0f, 2.0f, "");
tooltipFloat(colorGrading.outGreen.r);
ImGui::SameLine();
ImGui::VSliderFloat("##outGreen.g", verticalSliderSize, &colorGrading.outGreen.g, -2.0f, 2.0f, "");
tooltipFloat(colorGrading.outGreen.g);
ImGui::SameLine();
ImGui::VSliderFloat("##outGreen.b", verticalSliderSize, &colorGrading.outGreen.b, -2.0f, 2.0f, "");
tooltipFloat(colorGrading.outGreen.b);
ImGui::SameLine(0.0f, 18.0f);
popSliderColors();
pushSliderColors(4.0f / 7.0f);
ImGui::VSliderFloat("##outBlue.r", verticalSliderSize, &colorGrading.outBlue.r, -2.0f, 2.0f, "");
tooltipFloat(colorGrading.outBlue.r);
ImGui::SameLine();
ImGui::VSliderFloat("##outBlue.g", verticalSliderSize, &colorGrading.outBlue.g, -2.0f, 2.0f, "");
tooltipFloat(colorGrading.outBlue.g);
ImGui::SameLine();
ImGui::VSliderFloat("##outBlue.b", verticalSliderSize, &colorGrading.outBlue.b, -2.0f, 2.0f, "");
tooltipFloat(colorGrading.outBlue.b);
popSliderColors();
}
if (ImGui::CollapsingHeader("Tonal ranges")) {
ImGui::ColorEdit3("Shadows", &colorGrading.shadows.x);
ImGui::SliderFloat("Weight##shadowsWeight", &colorGrading.shadows.w, -2.0f, 2.0f);
ImGui::ColorEdit3("Mid-tones", &colorGrading.midtones.x);
ImGui::SliderFloat("Weight##midTonesWeight", &colorGrading.midtones.w, -2.0f, 2.0f);
ImGui::ColorEdit3("Highlights", &colorGrading.highlights.x);
ImGui::SliderFloat("Weight##highlightsWeight", &colorGrading.highlights.w, -2.0f, 2.0f);
ImGui::SliderFloat4("Ranges", &colorGrading.ranges.x, 0.0f, 1.0f);
computeRangePlot(settings, rangePlot);
ImGuiExt::PlotLinesSeries("", 3,
rangePlotSeriesStart, getRangePlotValue, rangePlotSeriesEnd,
rangePlot, 1024, 0, "", 0.0f, 1.0f, plotLinesWideSize);
}
if (ImGui::CollapsingHeader("Color decision list")) {
ImGui::SliderFloat3("Slope", &colorGrading.slope.x, 0.0f, 2.0f);
ImGui::SliderFloat3("Offset", &colorGrading.offset.x, -0.5f, 0.5f);
ImGui::SliderFloat3("Power", &colorGrading.power.x, 0.0f, 2.0f);
}
if (ImGui::CollapsingHeader("Adjustments")) {
ImGui::SliderFloat("Contrast", &colorGrading.contrast, 0.0f, 2.0f);
ImGui::SliderFloat("Vibrance", &colorGrading.vibrance, 0.0f, 2.0f);
ImGui::SliderFloat("Saturation", &colorGrading.saturation, 0.0f, 2.0f);
}
if (ImGui::CollapsingHeader("Curves")) {
ImGui::Checkbox("Linked curves", &colorGrading.linkedCurves);
computeCurvePlot(settings, curvePlot);
if (!colorGrading.linkedCurves) {
pushSliderColors(0.0f / 7.0f);
ImGui::VSliderFloat("##curveGamma.r", verticalSliderSize, &colorGrading.gamma.r, 0.0f, 4.0f, "");
tooltipFloat(colorGrading.gamma.r);
ImGui::SameLine();
ImGui::VSliderFloat("##curveMid.r", verticalSliderSize, &colorGrading.midPoint.r, 0.0f, 2.0f, "");
tooltipFloat(colorGrading.midPoint.r);
ImGui::SameLine();
ImGui::VSliderFloat("##curveScale.r", verticalSliderSize, &colorGrading.scale.r, 0.0f, 4.0f, "");
tooltipFloat(colorGrading.scale.r);
ImGui::SameLine(0.0f, 18.0f);
popSliderColors();
ImGui::PushStyleColor(ImGuiCol_PlotLines, (ImVec4) ImColor::HSV(0.0f, 0.7f, 0.8f));
ImGui::PlotLines("", curvePlot, 1024, 0, "Red", 0.0f, 2.0f, plotLinesSize);
ImGui::PopStyleColor();
pushSliderColors(2.0f / 7.0f);
ImGui::VSliderFloat("##curveGamma.g", verticalSliderSize, &colorGrading.gamma.g, 0.0f, 4.0f, "");
tooltipFloat(colorGrading.gamma.g);
ImGui::SameLine();
ImGui::VSliderFloat("##curveMid.g", verticalSliderSize, &colorGrading.midPoint.g, 0.0f, 2.0f, "");
tooltipFloat(colorGrading.midPoint.g);
ImGui::SameLine();
ImGui::VSliderFloat("##curveScale.g", verticalSliderSize, &colorGrading.scale.g, 0.0f, 4.0f, "");
tooltipFloat(colorGrading.scale.g);
ImGui::SameLine(0.0f, 18.0f);
popSliderColors();
ImGui::PushStyleColor(ImGuiCol_PlotLines, (ImVec4) ImColor::HSV(0.3f, 0.7f, 0.8f));
ImGui::PlotLines("", curvePlot + 1024, 1024, 0, "Green", 0.0f, 2.0f, plotLinesSize);
ImGui::PopStyleColor();
pushSliderColors(4.0f / 7.0f);
ImGui::VSliderFloat("##curveGamma.b", verticalSliderSize, &colorGrading.gamma.b, 0.0f, 4.0f, "");
tooltipFloat(colorGrading.gamma.b);
ImGui::SameLine();
ImGui::VSliderFloat("##curveMid.b", verticalSliderSize, &colorGrading.midPoint.b, 0.0f, 2.0f, "");
tooltipFloat(colorGrading.midPoint.b);
ImGui::SameLine();
ImGui::VSliderFloat("##curveScale.b", verticalSliderSize, &colorGrading.scale.b, 0.0f, 4.0f, "");
tooltipFloat(colorGrading.scale.b);
ImGui::SameLine(0.0f, 18.0f);
popSliderColors();
ImGui::PushStyleColor(ImGuiCol_PlotLines, (ImVec4) ImColor::HSV(0.6f, 0.7f, 0.8f));
ImGui::PlotLines("", curvePlot + 2048, 1024, 0, "Blue", 0.0f, 2.0f, plotLinesSize);
ImGui::PopStyleColor();
} else {
ImGui::VSliderFloat("##curveGamma", verticalSliderSize, &colorGrading.gamma.r, 0.0f, 4.0f, "");
tooltipFloat(colorGrading.gamma.r);
ImGui::SameLine();
ImGui::VSliderFloat("##curveMid", verticalSliderSize, &colorGrading.midPoint.r, 0.0f, 2.0f, "");
tooltipFloat(colorGrading.midPoint.r);
ImGui::SameLine();
ImGui::VSliderFloat("##curveScale", verticalSliderSize, &colorGrading.scale.r, 0.0f, 4.0f, "");
tooltipFloat(colorGrading.scale.r);
ImGui::SameLine(0.0f, 18.0f);
colorGrading.gamma = float3{colorGrading.gamma.r};
colorGrading.midPoint = float3{colorGrading.midPoint.r};
colorGrading.scale = float3{colorGrading.scale.r};
ImGui::PushStyleColor(ImGuiCol_PlotLines, (ImVec4) ImColor::HSV(0.17f, 0.21f, 0.9f));
ImGui::PlotLines("", curvePlot, 1024, 0, "RGB", 0.0f, 2.0f, plotLinesSize);
ImGui::PopStyleColor();
}
}
ImGui::Unindent();
}
}
ViewerGui::ViewerGui(filament::Engine* engine, filament::Scene* scene, filament::View* view,
int sidebarWidth) :
mEngine(engine), mScene(scene), mView(view),
mSunlight(utils::EntityManager::get().create()),
mSidebarWidth(sidebarWidth) {
mSettings.view.shadowType = ShadowType::PCF;
mSettings.view.vsmShadowOptions.anisotropy = 0;
mSettings.view.dithering = Dithering::TEMPORAL;
mSettings.view.antiAliasing = AntiAliasing::FXAA;
mSettings.view.msaa = { .enabled = true, .sampleCount = 4 };
mSettings.view.ssao.enabled = true;
mSettings.view.bloom.enabled = true;
using namespace filament;
LightManager::Builder(LightManager::Type::SUN)
.color(mSettings.lighting.sunlightColor)
.intensity(mSettings.lighting.sunlightIntensity)
.direction(normalize(mSettings.lighting.sunlightDirection))
.castShadows(true)
.sunAngularRadius(1.0f)
.sunHaloSize(2.0f)
.sunHaloFalloff(80.0f)
.build(*engine, mSunlight);
if (mSettings.lighting.enableSunlight) {
mScene->addEntity(mSunlight);
}
view->setAmbientOcclusionOptions({ .upsampling = View::QualityLevel::HIGH });
}
ViewerGui::~ViewerGui() {
mEngine->destroy(mSunlight);
delete mImGuiHelper;
}
void ViewerGui::setAsset(FilamentAsset* asset, FilamentInstance* instanceToAnimate) {
if (mAsset != asset) {
removeAsset();
mAsset = asset;
mVisibleScenes.reset();
mVisibleScenes.set(0);
mCurrentCamera = 0;
if (!asset) {
mAnimator = nullptr;
return;
}
mAnimator = instanceToAnimate ? instanceToAnimate->getAnimator() : asset->getAnimator();
assert_invariant(mAnimator && "Call loadResources or asyncBeginLoad before setAsset.");
updateRootTransform();
mScene->addEntities(asset->getLightEntities(), asset->getLightEntityCount());
auto& rcm = mEngine->getRenderableManager();
for (size_t i = 0, n = asset->getRenderableEntityCount(); i < n; i++) {
auto ri = rcm.getInstance(asset->getRenderableEntities()[i]);
rcm.setScreenSpaceContactShadows(ri, true);
}
}
}
void ViewerGui::populateScene() {
static constexpr int kNumAvailable = 128;
utils::Entity renderables[kNumAvailable];
while (size_t numWritten = mAsset->popRenderables(renderables, kNumAvailable)) {
mAsset->addEntitiesToScene(*mScene, renderables, numWritten, mVisibleScenes);
}
}
void ViewerGui::removeAsset() {
if (!isRemoteMode()) {
mScene->removeEntities(mAsset->getEntities(), mAsset->getEntityCount());
mAsset = nullptr;
mAnimator = nullptr;
}
}
void ViewerGui::setIndirectLight(filament::IndirectLight* ibl,
filament::math::float3 const* sh3) {
using namespace filament::math;
mSettings.view.fog.color = sh3[0];
mIndirectLight = ibl;
if (ibl) {
float3 d = filament::IndirectLight::getDirectionEstimate(sh3);
float4 c = filament::IndirectLight::getColorEstimate(sh3, d);
if (!std::isnan(d.x * d.y * d.z)) {
mSettings.lighting.sunlightDirection = d;
mSettings.lighting.sunlightColor = c.rgb;
mSettings.lighting.sunlightIntensity = c[3] * ibl->getIntensity();
updateIndirectLight();
}
}
}
void ViewerGui::updateRootTransform() {
if (isRemoteMode()) {
return;
}
auto& tcm = mEngine->getTransformManager();
auto root = tcm.getInstance(mAsset->getRoot());
filament::math::mat4f transform;
if (mSettings.viewer.autoScaleEnabled) {
FilamentInstance* instance = mAsset->getInstance();
Aabb aabb = instance ? instance->getBoundingBox() : mAsset->getBoundingBox();
transform = fitIntoUnitCube(aabb, 4);
}
tcm.setTransform(root, transform);
}
void ViewerGui::updateIndirectLight() {
using namespace filament::math;
if (mIndirectLight) {
mIndirectLight->setIntensity(mSettings.lighting.iblIntensity);
mIndirectLight->setRotation(mat3f::rotation(mSettings.lighting.iblRotation, float3{ 0, 1, 0 }));
}
}
void ViewerGui::sceneSelectionUI() {
// Build a list of checkboxes, one for each glTF scene.
bool changed = false;
for (size_t i = 0, n = mAsset->getSceneCount(); i < n; ++i) {
bool isVisible = mVisibleScenes.test(i);
const char* name = mAsset->getSceneName(i);
if (name) {
changed = ImGui::Checkbox(name, &isVisible) || changed;
} else {
char label[16];
snprintf(label, 16, "Scene %zu", i);
changed = ImGui::Checkbox(label, &isVisible) || changed;
}
if (isVisible) {
mVisibleScenes.set(i);
} else {
mVisibleScenes.unset(i);
}
}
// If any checkboxes have been toggled, rebuild the scene list.
if (changed) {
const utils::Entity* entities = mAsset->getRenderableEntities();
const size_t entityCount = mAsset->getRenderableEntityCount();
mScene->removeEntities(entities, entityCount);
mAsset->addEntitiesToScene(*mScene, entities, entityCount, mVisibleScenes);
}
}
void ViewerGui::applyAnimation(double currentTime) {
assert_invariant(!isRemoteMode());
const size_t numAnimations = mAnimator->getAnimationCount();
if (mResetAnimation) {
mPreviousStartTime = mCurrentStartTime;
mCurrentStartTime = currentTime;
mResetAnimation = false;
}
const double elapsedSeconds = currentTime - mCurrentStartTime;
if (numAnimations > 0 && mCurrentAnimation > 0) {
mAnimator->applyAnimation(mCurrentAnimation - 1, elapsedSeconds);
if (elapsedSeconds < mCrossFadeDuration && mPreviousAnimation > 0) {
const double previousSeconds = currentTime - mPreviousStartTime;
const float lerpFactor = elapsedSeconds / mCrossFadeDuration;
mAnimator->applyCrossFade(mPreviousAnimation - 1, previousSeconds, lerpFactor);
}
}
if (mShowingRestPose) {
mAnimator->resetBoneMatrices();
} else {
mAnimator->updateBoneMatrices();
}
}
void ViewerGui::renderUserInterface(float timeStepInSeconds, View* guiView, float pixelRatio) {
if (mImGuiHelper == nullptr) {
mImGuiHelper = new ImGuiHelper(mEngine, guiView, "");
auto& io = ImGui::GetIO();
// The following table uses normal ANSI codes, which is consistent with the keyCode that
// comes from a web "keydown" event.
io.KeyMap[ImGuiKey_Tab] = 9;
io.KeyMap[ImGuiKey_LeftArrow] = 37;
io.KeyMap[ImGuiKey_RightArrow] = 39;
io.KeyMap[ImGuiKey_UpArrow] = 38;
io.KeyMap[ImGuiKey_DownArrow] = 40;
io.KeyMap[ImGuiKey_Home] = 36;
io.KeyMap[ImGuiKey_End] = 35;
io.KeyMap[ImGuiKey_Delete] = 46;
io.KeyMap[ImGuiKey_Backspace] = 8;
io.KeyMap[ImGuiKey_Enter] = 13;
io.KeyMap[ImGuiKey_Escape] = 27;
io.KeyMap[ImGuiKey_A] = 65;
io.KeyMap[ImGuiKey_C] = 67;
io.KeyMap[ImGuiKey_V] = 86;
io.KeyMap[ImGuiKey_X] = 88;
io.KeyMap[ImGuiKey_Y] = 89;
io.KeyMap[ImGuiKey_Z] = 90;
// TODO: this is not the best way to handle high DPI in ImGui, but it is fine when using the
// proggy font. Users need to refresh their window when dragging between displays with
// different pixel ratios.
io.FontGlobalScale = pixelRatio;
ImGui::GetStyle().ScaleAllSizes(pixelRatio);
}
const auto size = guiView->getViewport();
mImGuiHelper->setDisplaySize(size.width, size.height, 1, 1);
mImGuiHelper->render(timeStepInSeconds, [this](Engine*, View*) {
this->updateUserInterface();
});
}
void ViewerGui::mouseEvent(float mouseX, float mouseY, bool mouseButton, float mouseWheelY,
bool control) {
if (mImGuiHelper) {
ImGuiIO& io = ImGui::GetIO();
io.MousePos.x = mouseX;
io.MousePos.y = mouseY;
io.MouseWheel += mouseWheelY;
io.MouseDown[0] = mouseButton != 0;
io.MouseDown[1] = false;
io.MouseDown[2] = false;
io.KeyCtrl = control;
}
}
void ViewerGui::keyDownEvent(int keyCode) {
if (mImGuiHelper && keyCode < IM_ARRAYSIZE(ImGui::GetIO().KeysDown)) {
ImGui::GetIO().KeysDown[keyCode] = true;
}
}
void ViewerGui::keyUpEvent(int keyCode) {
if (mImGuiHelper && keyCode < IM_ARRAYSIZE(ImGui::GetIO().KeysDown)) {
ImGui::GetIO().KeysDown[keyCode] = false;
}
}
void ViewerGui::keyPressEvent(int charCode) {
if (mImGuiHelper) {
ImGui::GetIO().AddInputCharacter(charCode);
}
}
void ViewerGui::updateUserInterface() {
using namespace filament;
auto& tm = mEngine->getTransformManager();
auto& rm = mEngine->getRenderableManager();
auto& lm = mEngine->getLightManager();
// Show a common set of UI widgets for all renderables.
auto renderableTreeItem = [this, &rm](utils::Entity entity) {
bool rvis = mScene->hasEntity(entity);
ImGui::Checkbox("visible", &rvis);
if (rvis) {
mScene->addEntity(entity);
} else {
mScene->remove(entity);
}
auto instance = rm.getInstance(entity);
bool scaster = rm.isShadowCaster(instance);
bool sreceiver = rm.isShadowReceiver(instance);
ImGui::Checkbox("casts shadows", &scaster);
rm.setCastShadows(instance, scaster);
ImGui::Checkbox("receives shadows", &sreceiver);
rm.setReceiveShadows(instance, sreceiver);
auto numMorphTargets = rm.getMorphTargetCount(instance);
if (numMorphTargets > 0) {
bool selected = entity == mCurrentMorphingEntity;
ImGui::Checkbox("morphing", &selected);
if (selected) {
mCurrentMorphingEntity = entity;
mMorphWeights.resize(numMorphTargets, 0.0f);
} else {
mCurrentMorphingEntity = utils::Entity();
}
}
size_t numPrims = rm.getPrimitiveCount(instance);
for (size_t prim = 0; prim < numPrims; ++prim) {
const char* mname = rm.getMaterialInstanceAt(instance, prim)->getName();
if (mname) {
ImGui::Text("prim %zu: material %s", prim, mname);
} else {
ImGui::Text("prim %zu: (unnamed material)", prim);
}
}
};
auto lightTreeItem = [this, &lm](utils::Entity entity) {
bool lvis = mScene->hasEntity(entity);
ImGui::Checkbox("visible", &lvis);
if (lvis) {
mScene->addEntity(entity);
} else {
mScene->remove(entity);
}
auto instance = lm.getInstance(entity);
bool lcaster = lm.isShadowCaster(instance);
ImGui::Checkbox("shadow caster", &lcaster);
lm.setShadowCaster(instance, lcaster);
};
// Declare a std::function for tree nodes, it's an easy way to make a recursive lambda.
std::function<void(utils::Entity)> treeNode;
treeNode = [&](utils::Entity entity) {
auto tinstance = tm.getInstance(entity);
auto rinstance = rm.getInstance(entity);
auto linstance = lm.getInstance(entity);
intptr_t treeNodeId = 1 + entity.getId();
const char* name = mAsset->getName(entity);
auto getLabel = [&name, &rinstance, &linstance]() {
if (name) {
return name;
}
if (rinstance) {
return "Mesh";
}
if (linstance) {
return "Light";
}
return "Node";
};
const char* label = getLabel();
ImGuiTreeNodeFlags flags = 0; // rinstance ? 0 : ImGuiTreeNodeFlags_DefaultOpen;
std::vector<utils::Entity> children(tm.getChildCount(tinstance));
if (ImGui::TreeNodeEx((const void*) treeNodeId, flags, "%s", label)) {
if (rinstance) {
renderableTreeItem(entity);
}
if (linstance) {
lightTreeItem(entity);
}
tm.getChildren(tinstance, children.data(), children.size());
for (auto ce : children) {
treeNode(ce);
}
ImGui::TreePop();
}
};
// Disable rounding and draw a fixed-height ImGui window that looks like a sidebar.
ImGui::GetStyle().WindowRounding = 0;
ImGui::SetNextWindowPos(ImVec2(0, 0));
const float width = ImGui::GetIO().DisplaySize.x;
const float height = ImGui::GetIO().DisplaySize.y;
ImGui::SetNextWindowSize(ImVec2(mSidebarWidth, height), ImGuiCond_Once);
ImGui::SetNextWindowSizeConstraints(ImVec2(20, height), ImVec2(width, height));
ImGui::Begin("Filament", nullptr, ImGuiWindowFlags_NoTitleBar);
if (mCustomUI) {
mCustomUI();
}
DebugRegistry& debug = mEngine->getDebugRegistry();
if (ImGui::CollapsingHeader("View")) {
ImGui::Indent();
ImGui::Checkbox("Post-processing", &mSettings.view.postProcessingEnabled);
ImGui::Indent();
bool dither = mSettings.view.dithering == Dithering::TEMPORAL;
ImGui::Checkbox("Dithering", &dither);
enableDithering(dither);
ImGui::Checkbox("Bloom", &mSettings.view.bloom.enabled);
ImGui::Checkbox("Flare", &mSettings.view.bloom.lensFlare);
ImGui::Checkbox("TAA", &mSettings.view.taa.enabled);
// this clutters the UI and isn't that useful (except when working on TAA)
//ImGui::Indent();
//ImGui::SliderFloat("feedback", &mSettings.view.taa.feedback, 0.0f, 1.0f);
//ImGui::SliderFloat("filter", &mSettings.view.taa.filterWidth, 0.0f, 2.0f);
//ImGui::Unindent();
bool fxaa = mSettings.view.antiAliasing == AntiAliasing::FXAA;
ImGui::Checkbox("FXAA", &fxaa);
enableFxaa(fxaa);
ImGui::Unindent();
ImGui::Checkbox("MSAA 4x", &mSettings.view.msaa.enabled);
ImGui::Indent();
ImGui::Checkbox("Custom resolve", &mSettings.view.msaa.customResolve);
ImGui::Unindent();
ImGui::Checkbox("SSAO", &mSettings.view.ssao.enabled);
if (ImGui::CollapsingHeader("SSAO Options")) {
auto& ssao = mSettings.view.ssao;
int quality = (int) ssao.quality;
int lowpass = (int) ssao.lowPassFilter;
bool upsampling = ssao.upsampling != View::QualityLevel::LOW;
bool halfRes = ssao.resolution == 1.0f ? false : true;
ImGui::SliderInt("Quality", &quality, 0, 3);
ImGui::SliderInt("Low Pass", &lowpass, 0, 2);
ImGui::Checkbox("Bent Normals", &ssao.bentNormals);
ImGui::Checkbox("High quality upsampling", &upsampling);
ImGui::SliderFloat("Min Horizon angle", &ssao.minHorizonAngleRad, 0.0f, (float)M_PI_4);
ImGui::SliderFloat("Bilateral Threshold", &ssao.bilateralThreshold, 0.0f, 0.1f);
ImGui::Checkbox("Half resolution", &halfRes);
ssao.resolution = halfRes ? 0.5f : 1.0f;
ssao.upsampling = upsampling ? View::QualityLevel::HIGH : View::QualityLevel::LOW;
ssao.lowPassFilter = (View::QualityLevel) lowpass;
ssao.quality = (View::QualityLevel) quality;
if (ImGui::CollapsingHeader("Dominant Light Shadows (experimental)")) {
int sampleCount = ssao.ssct.sampleCount;
ImGui::Checkbox("Enabled##dls", &ssao.ssct.enabled);
ImGui::SliderFloat("Cone angle", &ssao.ssct.lightConeRad, 0.0f, (float)M_PI_2);
ImGui::SliderFloat("Shadow Distance", &ssao.ssct.shadowDistance, 0.0f, 10.0f);
ImGui::SliderFloat("Contact dist max", &ssao.ssct.contactDistanceMax, 0.0f, 100.0f);
ImGui::SliderFloat("Intensity##dls", &ssao.ssct.intensity, 0.0f, 10.0f);
ImGui::SliderFloat("Depth bias", &ssao.ssct.depthBias, 0.0f, 1.0f);
ImGui::SliderFloat("Depth slope bias", &ssao.ssct.depthSlopeBias, 0.0f, 1.0f);
ImGui::SliderInt("Sample Count", &sampleCount, 1, 32);
ImGuiExt::DirectionWidget("Direction##dls", ssao.ssct.lightDirection.v);
ssao.ssct.sampleCount = sampleCount;
}
}
ImGui::Checkbox("Screen-space reflections", &mSettings.view.screenSpaceReflections.enabled);
if (ImGui::CollapsingHeader("Screen-space reflections Options")) {
auto& ssrefl = mSettings.view.screenSpaceReflections;
ImGui::SliderFloat("Ray thickness", &ssrefl.thickness, 0.001f, 0.2f);
ImGui::SliderFloat("Bias", &ssrefl.bias, 0.001f, 0.5f);
ImGui::SliderFloat("Max distance", &ssrefl.maxDistance, 0.1, 10.0f);
ImGui::SliderFloat("Stride", &ssrefl.stride, 1.0, 10.0f);
}
ImGui::Unindent();
ImGui::Checkbox("Screen-space Guard Band", &mSettings.view.guardBand.enabled);
}
if (ImGui::CollapsingHeader("Dynamic Resolution")) {
auto& dsr = mSettings.view.dsr;
int quality = (int)dsr.quality;
ImGui::Checkbox("enabled", &dsr.enabled);
ImGui::Checkbox("homogeneous", &dsr.homogeneousScaling);
ImGui::SliderFloat("min. scale", &dsr.minScale.x, 0.25f, 1.0f);
ImGui::SliderFloat("max. scale", &dsr.maxScale.x, 0.25f, 1.0f);
ImGui::SliderInt("quality", &quality, 0, 3);
ImGui::SliderFloat("sharpness", &dsr.sharpness, 0.0f, 1.0f);
dsr.minScale.x = std::min(dsr.minScale.x, dsr.maxScale.x);
dsr.minScale.y = dsr.minScale.x;
dsr.maxScale.y = dsr.maxScale.x;
dsr.quality = (QualityLevel)quality;
}
auto& light = mSettings.lighting;
if (ImGui::CollapsingHeader("Light")) {
ImGui::Indent();
if (ImGui::CollapsingHeader("Indirect light")) {
ImGui::SliderFloat("IBL intensity", &light.iblIntensity, 0.0f, 100000.0f);
ImGui::SliderAngle("IBL rotation", &light.iblRotation);
}
if (ImGui::CollapsingHeader("Sunlight")) {
ImGui::Checkbox("Enable sunlight", &light.enableSunlight);
ImGui::SliderFloat("Sun intensity", &light.sunlightIntensity, 50000.0, 150000.0f);
ImGuiExt::DirectionWidget("Sun direction", light.sunlightDirection.v);
}
if (ImGui::CollapsingHeader("All lights")) {
ImGui::Checkbox("Enable shadows", &light.enableShadows);
int mapSize = light.shadowOptions.mapSize;
ImGui::SliderInt("Shadow map size", &mapSize, 32, 1024);
light.shadowOptions.mapSize = mapSize;
int shadowType = (int)mSettings.view.shadowType;
ImGui::Combo("Shadow type", &shadowType, "PCF\0VSM\0DPCF\0PCSS\0\0");
mSettings.view.shadowType = (ShadowType)shadowType;
if (mSettings.view.shadowType == ShadowType::VSM) {
char label[32];
snprintf(label, 32, "%d", 1 << mVsmMsaaSamplesLog2);
ImGui::SliderInt("VSM MSAA samples", &mVsmMsaaSamplesLog2, 0, 3, label);
light.shadowOptions.vsm.msaaSamples =
static_cast<uint8_t>(1u << mVsmMsaaSamplesLog2);
int vsmAnisotropy = mSettings.view.vsmShadowOptions.anisotropy;
snprintf(label, 32, "%d", 1 << vsmAnisotropy);
ImGui::SliderInt("VSM anisotropy", &vsmAnisotropy, 0, 3, label);
mSettings.view.vsmShadowOptions.anisotropy = vsmAnisotropy;
ImGui::Checkbox("VSM mipmapping", &mSettings.view.vsmShadowOptions.mipmapping);
ImGui::SliderFloat("VSM blur", &light.shadowOptions.vsm.blurWidth, 0.0f, 125.0f);
// These are not very useful in practice (defaults are good), but we keep them here for debugging
//ImGui::SliderFloat("VSM exponent", &mSettings.view.vsmShadowOptions.exponent, 0.0, 6.0f);
//ImGui::SliderFloat("VSM Light bleed", &mSettings.view.vsmShadowOptions.lightBleedReduction, 0.0, 1.0f);
//ImGui::SliderFloat("VSM min variance scale", &mSettings.view.vsmShadowOptions.minVarianceScale, 0.0, 10.0f);
} else if (mSettings.view.shadowType == ShadowType::DPCF || mSettings.view.shadowType == ShadowType::PCSS) {
ImGui::SliderFloat("Penumbra scale", &light.softShadowOptions.penumbraScale, 0.0f, 100.0f);
ImGui::SliderFloat("Penumbra Ratio scale", &light.softShadowOptions.penumbraRatioScale, 1.0f, 100.0f);
}
int shadowCascades = light.shadowOptions.shadowCascades;
ImGui::SliderInt("Cascades", &shadowCascades, 1, 4);
ImGui::Checkbox("Debug cascades",
debug.getPropertyAddress<bool>("d.shadowmap.visualize_cascades"));
ImGui::Checkbox("Enable contact shadows", &light.shadowOptions.screenSpaceContactShadows);
ImGui::SliderFloat("Split pos 0", &light.shadowOptions.cascadeSplitPositions[0], 0.0f, 1.0f);
ImGui::SliderFloat("Split pos 1", &light.shadowOptions.cascadeSplitPositions[1], 0.0f, 1.0f);
ImGui::SliderFloat("Split pos 2", &light.shadowOptions.cascadeSplitPositions[2], 0.0f, 1.0f);
light.shadowOptions.shadowCascades = shadowCascades;
}
ImGui::Unindent();
}
if (ImGui::CollapsingHeader("Fog")) {
ImGui::Indent();
ImGui::Checkbox("Enable fog", &mSettings.view.fog.enabled);
ImGui::SliderFloat("Start", &mSettings.view.fog.distance, 0.0f, 100.0f);
ImGui::SliderFloat("Density", &mSettings.view.fog.density, 0.0f, 1.0f);
ImGui::SliderFloat("Height", &mSettings.view.fog.height, 0.0f, 100.0f);
ImGui::SliderFloat("Height falloff", &mSettings.view.fog.heightFalloff, 0.0f, 10.0f);
ImGui::SliderFloat("Scattering start", &mSettings.view.fog.inScatteringStart, 0.0f, 100.0f);
ImGui::SliderFloat("Scattering size", &mSettings.view.fog.inScatteringSize, 0.1f, 100.0f);
ImGui::Checkbox("Color from IBL", &mSettings.view.fog.fogColorFromIbl);
ImGui::ColorPicker3("Color", mSettings.view.fog.color.v);
ImGui::Unindent();
}
if (ImGui::CollapsingHeader("Scene")) {
ImGui::Indent();
if (ImGui::Checkbox("Scale to unit cube", &mSettings.viewer.autoScaleEnabled)) {
updateRootTransform();
}
ImGui::Checkbox("Automatic instancing", &mSettings.viewer.autoInstancingEnabled);
ImGui::Checkbox("Show skybox", &mSettings.viewer.skyboxEnabled);
ImGui::ColorEdit3("Background color", &mSettings.viewer.backgroundColor.r);
// We do not yet support ground shadow or scene selection in remote mode.
if (!isRemoteMode()) {
ImGui::Checkbox("Ground shadow", &mSettings.viewer.groundPlaneEnabled);
ImGui::Indent();
ImGui::SliderFloat("Strength", &mSettings.viewer.groundShadowStrength, 0.0f, 1.0f);
ImGui::Unindent();
if (mAsset->getSceneCount() > 1) {
ImGui::Separator();
sceneSelectionUI();
}
}
ImGui::Unindent();
}
if (ImGui::CollapsingHeader("Camera")) {
ImGui::Indent();
ImGui::SliderFloat("Focal length (mm)", &mSettings.viewer.cameraFocalLength,
16.0f, 90.0f);
bool dofMedian = mSettings.view.dof.filter == View::DepthOfFieldOptions::Filter::MEDIAN;
int dofRingCount = mSettings.view.dof.fastGatherRingCount;
int dofMaxCoC = mSettings.view.dof.maxForegroundCOC;
if (!dofRingCount) dofRingCount = 5;
if (!dofMaxCoC) dofMaxCoC = 32;
ImGui::SliderFloat("Aperture", &mSettings.viewer.cameraAperture, 1.0f, 32.0f);
ImGui::SliderFloat("Speed (1/s)", &mSettings.viewer.cameraSpeed, 1000.0f, 1.0f);
ImGui::SliderFloat("ISO", &mSettings.viewer.cameraISO, 25.0f, 6400.0f);
ImGui::Checkbox("DoF", &mSettings.view.dof.enabled);
ImGui::SliderFloat("Focus distance", &mSettings.viewer.cameraFocusDistance, 0.0f, 30.0f);
ImGui::SliderFloat("Blur scale", &mSettings.view.dof.cocScale, 0.1f, 10.0f);
ImGui::SliderInt("Ring count", &dofRingCount, 1, 17);
ImGui::SliderInt("Max CoC", &dofMaxCoC, 1, 32);
ImGui::Checkbox("Native Resolution", &mSettings.view.dof.nativeResolution);
ImGui::Checkbox("Median Filter", &dofMedian);
mSettings.view.dof.filter = dofMedian ?
View::DepthOfFieldOptions::Filter::MEDIAN :
View::DepthOfFieldOptions::Filter::NONE;
mSettings.view.dof.backgroundRingCount = dofRingCount;
mSettings.view.dof.foregroundRingCount = dofRingCount;
mSettings.view.dof.fastGatherRingCount = dofRingCount;
mSettings.view.dof.maxForegroundCOC = dofMaxCoC;
mSettings.view.dof.maxBackgroundCOC = dofMaxCoC;
if (ImGui::CollapsingHeader("Vignette")) {
ImGui::Checkbox("Enabled##vignetteEnabled", &mSettings.view.vignette.enabled);
ImGui::SliderFloat("Mid point", &mSettings.view.vignette.midPoint, 0.0f, 1.0f);
ImGui::SliderFloat("Roundness", &mSettings.view.vignette.roundness, 0.0f, 1.0f);
ImGui::SliderFloat("Feather", &mSettings.view.vignette.feather, 0.0f, 1.0f);
ImGui::ColorEdit3("Color##vignetteColor", &mSettings.view.vignette.color.r);
}
// We do not yet support camera selection in the remote UI. To support this feature, we
// would need to send a message from DebugServer to the WebSockets client.
if (!isRemoteMode()) {
const utils::Entity* cameras = mAsset->getCameraEntities();
const size_t cameraCount = mAsset->getCameraEntityCount();
std::vector<std::string> names;
names.reserve(cameraCount + 1);
names.push_back("Free camera");
int c = 0;
for (size_t i = 0; i < cameraCount; i++) {
const char* n = mAsset->getName(cameras[i]);
if (n) {
names.emplace_back(n);
} else {
char buf[32];
sprintf(buf, "Unnamed camera %d", c++);
names.emplace_back(buf);
}
}
std::vector<const char*> cstrings;
cstrings.reserve(names.size());
for (size_t i = 0; i < names.size(); i++) {
cstrings.push_back(names[i].c_str());
}
ImGui::ListBox("Cameras", &mCurrentCamera, cstrings.data(), cstrings.size());
}
ImGui::Unindent();
}
colorGradingUI(mSettings, mRangePlot, mCurvePlot, mToneMapPlot);
// At this point, all View settings have been modified,
// so we can now push them into the Filament View.
applySettings(mEngine, mSettings.view, mView);
mView->setSoftShadowOptions(mSettings.lighting.softShadowOptions);
if (light.enableSunlight) {
mScene->addEntity(mSunlight);
auto sun = lm.getInstance(mSunlight);
lm.setIntensity(sun, light.sunlightIntensity);
lm.setDirection(sun, normalize(light.sunlightDirection));
lm.setColor(sun, light.sunlightColor);
lm.setShadowCaster(sun, light.enableShadows);
lm.setShadowOptions(sun, light.shadowOptions);
} else {
mScene->remove(mSunlight);
}
lm.forEachComponent([this, &lm, &light](utils::Entity e, LightManager::Instance ci) {
lm.setShadowOptions(ci, light.shadowOptions);
lm.setShadowCaster(ci, light.enableShadows);
});
// TODO(prideout): add support for hierarchy, animation and variant selection in remote mode. To
// support these features, we will need to send a message (list of strings) from DebugServer to
// the WebSockets client.
if (!isRemoteMode()) {
if (ImGui::CollapsingHeader("Hierarchy")) {
ImGui::Indent();
ImGui::Checkbox("Show bounds", &mEnableWireframe);
treeNode(mAsset->getRoot());
ImGui::Unindent();
}
if (mAsset->getMaterialVariantCount() > 0 && ImGui::CollapsingHeader("Variants")) {
ImGui::Indent();
int selectedVariant = mCurrentVariant;
for (size_t i = 0, count = mAsset->getMaterialVariantCount(); i < count; ++i) {
const char* label = mAsset->getMaterialVariantName(i);
ImGui::RadioButton(label, &selectedVariant, i);
}
if (selectedVariant != mCurrentVariant) {
mCurrentVariant = selectedVariant;
mAsset->applyMaterialVariant(mCurrentVariant);
}
ImGui::Unindent();
}
if (mAnimator->getAnimationCount() > 0 && ImGui::CollapsingHeader("Animation")) {
ImGui::Indent();
int selectedAnimation = mCurrentAnimation;
ImGui::RadioButton("Disable", &selectedAnimation, 0);
ImGui::SliderFloat("Cross fade", &mCrossFadeDuration, 0.0f, 2.0f,
"%4.2f seconds", ImGuiSliderFlags_AlwaysClamp);
for (size_t i = 0, count = mAnimator->getAnimationCount(); i < count; ++i) {
std::string label = mAnimator->getAnimationName(i);
if (label.empty()) {
label = "Unnamed " + std::to_string(i);
}
ImGui::RadioButton(label.c_str(), &selectedAnimation, i + 1);
}
if (selectedAnimation != mCurrentAnimation) {
mPreviousAnimation = mCurrentAnimation;
mCurrentAnimation = selectedAnimation;
mResetAnimation = true;
}
ImGui::Checkbox("Show rest pose", &mShowingRestPose);
ImGui::Unindent();
}
if (mCurrentMorphingEntity && ImGui::CollapsingHeader("Morphing")) {
const bool isAnimating = mCurrentAnimation > 0 && mAnimator->getAnimationCount() > 0;
if (isAnimating) {
ImGui::BeginDisabled();
}
for (int i = 0; i != mMorphWeights.size(); ++i) {
const char* name = mAsset->getMorphTargetNameAt(mCurrentMorphingEntity, i);
std::string label = name ? name : "Unnamed target " + std::to_string(i);
ImGui::SliderFloat(label.c_str(), &mMorphWeights[i], 0.0f, 1.0);
}
if (isAnimating) {
ImGui::EndDisabled();
}
if (!isAnimating) {
auto instance = rm.getInstance(mCurrentMorphingEntity);
rm.setMorphWeights(instance, mMorphWeights.data(), mMorphWeights.size());
}
}
if (mEnableWireframe) {
mScene->addEntity(mAsset->getWireframe());
} else {
mScene->remove(mAsset->getWireframe());
}
}
mSidebarWidth = ImGui::GetWindowWidth();
ImGui::End();
updateIndirectLight();
}
} // namespace viewer
} // namespace filament